为什么选择 Dart?

如果您是 TypeScript 开发者,您已经了解类型的价值。Dart 通过 TypeScript 由于设计限制而无法提供的特性,将这种价值进一步提升。

如果您是 Flutter 开发者,您已经熟悉 Dart。现在您可以在整个 JavaScript 生态系统中使用它。

TypeScript:出色的折中方案

TypeScript 是一项工程奇迹。它在不破坏与庞大 JS 生态系统兼容性的情况下,为 JavaScript 添加了静态类型。这是一个有意的选择 - 对于 TypeScript 的目标来说是正确的选择。

然而,这种选择伴随着在大型应用程序中变得明显的权衡。

类型擦除问题

TypeScript 类型仅在编译时存在。当您的代码运行时,它们就消失了。

interface User {
  id: number;
  name: string;
  email: string;
}

// 这可以编译通过
const user: User = JSON.parse(apiResponse);

// 但在运行时,无法保证 `user` 匹配接口。
// 如果 API 返回 { id: "123", name: null },TypeScript 无法帮助您。
console.log(user.name.toUpperCase()); // 如果 name 为 null 则运行时错误!

Dart 在运行时保留类型:

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,       // 如果类型错误立即失败
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }
}

// 验证在边界处发生
final user = User.fromJson(jsonDecode(apiResponse));

// 如果我们到达这里,我们知道 user.name 是一个非空字符串
print(user.name.toUpperCase()); // 安全!

健全的空安全

TypeScript 有 strictNullChecks,这很好。但"严格"仍然允许逃生舱口。

// TypeScript:! 运算符信任您(有时是错误的)
function processUser(user: User | null) {
  console.log(user!.name); // 您断言 user 不是 null
  // TypeScript 信任您。运行时可能不会。
}

Dart 的空安全是健全的:

// Dart:编译器确保这一点
void processUser(User? user) {
  print(user.name); // 编译错误!user 可能为 null

  // 您必须处理 null 情况
  if (user != null) {
    print(user.name); // 现在是安全的
  }

  // 或使用空感知运算符
  print(user?.name ?? '匿名');
}

Dart 中也存在 ! 运算符,但在 null 值上使用它会立即抛出异常 - 您不能静默地继续未定义行为。

现实世界的影响

序列化

在 TypeScript 中,您通常需要 Zod 或 io-ts 等运行时验证库:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

// 您定义了两次结构:一次给 Zod,一次给 TypeScript
const user = UserSchema.parse(apiData);

在 Dart 中,类定义就是运行时验证:

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as int,
    name: json['name'] as String,
    email: json['email'] as String,
  );
}

// 一个定义,同时用于类型和验证
final user = User.fromJson(apiData);

泛型

TypeScript 泛型被擦除:

class Box<T> {
  constructor(public value: T) {}

  isString(): boolean {
    // 运行时无法检查 T 是否为 string!
    // typeof this.value === 'string' 检查的是值,而不是 T
    return typeof this.value === 'string';
  }
}

Dart 泛型在运行时存在:

class Box<T> {
  final T value;
  Box(this.value);

  bool isString() {
    // 我们可以检查实际的类型参数!
    return T == String;
  }

  // 更好:类型安全的操作
  R map<R>(R Function(T) fn) => fn(value);
}

一种语言,多个平台

使用 dart_node,您可以在任何地方编写 Dart:

平台 TypeScript Dart (dart_node)
后端 Node.js + TypeScript dart_node_express
Web 前端 React + TypeScript dart_node_react
移动端 React Native + TypeScript dart_node_react_native
桌面端 Electron + TypeScript Flutter Desktop

在所有平台之间共享模型、验证逻辑和业务规则 - 具有运行时类型安全。

更简单的构建流程

典型的 TypeScript 项目:

源码 → TypeScript 编译器 → Babel → Webpack/Rollup → 包
      (tsconfig.json)    (.babelrc) (webpack.config.js)

dart_node 项目:

源码 → dart compile js → 包
      (开箱即用)

没有配置迷宫。没有工具间的兼容性问题。一个命令。

对于 Flutter 开发者

如果您已经从 Flutter 了解 Dart,dart_node 为您打开了 JavaScript 生态系统:

  • 使用庞大的 React 组件生态系统
  • 访问 npm 包(数百万个)
  • 部署到 Node.js 托管(比服务器端 Dart 更便宜)
  • 使用熟悉的工具构建(相同的语言,相同的模式)

何时 TypeScript 更合适

在以下情况下,TypeScript 仍然是绝佳选择:

  • 您正在处理现有的 JavaScript 代码库
  • 您的团队深度投资于 TypeScript 生态系统
  • 您需要与 JS 库的最大兼容性
  • 您更喜欢 TypeScript 的结构类型而非 Dart 的名义类型

结论

Dart 和 TypeScript 都为动态语言添加了类型安全。TypeScript 选择了最大的 JavaScript 兼容性。Dart 选择了最大的类型安全。

对于运行时安全至关重要的新项目,Dart 提供了 TypeScript 无法提供的保证 - 不是因为 TypeScript 有缺陷,而是因为它是在不同的约束下设计的。

使用 dart_node,您可以两全其美:Dart 的类型安全加上 JavaScript 生态系统的访问。


准备好尝试了吗?开始使用 dart_node