一、前言:为什么值得花时间学 Dart

几年前问"要不要学 Dart",答案通常取决于"你做不做 Flutter"。到 2026 年,这个绑定关系松了不少。Dart 现在有自己的服务端框架、命令行工具链,既能把代码编译成原生机器码,也能编译成 JavaScript。当然,它最出圈的舞台依然是 Flutter——Google 的跨平台 UI 框架。

这篇文章写给两类人。一类是有其他语言基础、想快速把 Dart 摸个遍的开发者;另一类是正准备写 Flutter、却被 Dart 的语法细节绊住的人。整体思路是:先说清它是门什么语言,再讲怎么写,最后讲怎么用得顺手、用得好。文中代码都可以直接丢进 DartPad 或本地环境跑起来。

按照惯例交代一下时效:截至 2026 年 9 月,Dart 的稳定主版本是 3.13(8 月正式发布,SDK 补丁已跟进到 3.13.x),配套 Flutter 3.47 系列。本文以 3.13 为准,同时会把 3.x 一路走来的关键变化串起来讲。

二、Dart 是一门什么样的语言

2.1 出身与定位

Dart 由 Google 开发,2011 年首次发布。它是一门面向对象、类 C 风格、单继承的静态强类型语言。说人话就是:语法跟 Java、JavaScript、C# 有几分像,有类型检查兜底,但写起来比 Java 轻快。

它有几个绕不开的特点:

  • 静态类型 + 类型推断。你可以写全类型,也可以让编译器自己猜,安全和效率兼顾。
  • 双编译模式。开发时用 JIT,改完代码热重载,秒级看到效果;发布时用 AOT,编译成机器码,跑得快。
  • 一切皆对象。连数字、函数、null 都是对象,所有类型最终都继承自 Object。
  • 空安全。从 Dart 2.12 起默认开启,非空变量不能塞 null,把空指针异常挡在编译期。
  • 跨平台。同一套代码可以跑在移动端、Web、桌面端、服务端和嵌入式设备上。

2.2 双编译模式:JIT 与 AOT

这是 Dart 区别于很多语言的地方,值得单独说。

JIT(Just-In-Time,即时编译) 在程序运行时编译代码,代价是启动慢一点,好处是支持热重载。你改一行 UI,界面立刻更新,状态还能保留。这个特性撑起了 Flutter 的开发体验。

AOT(Ahead-Of-Time,提前编译) 在打包时就把代码编译成目标平台的机器码。启动快、运行快,代价是没有热重载。上线发布用的就是它。

同一门语言,两套编译路径,各管一段——这是 Dart 设计里比较讨巧的一步。

2.3 空安全:把空指针挡在门外

空指针异常大概是最让人头疼的运行时错误之一。Dart 的做法很直接:变量默认不可为空,你要么保证它有值,要么显式声明它"可以为空"。

String name = 'Dart';      // 非空,永远是字符串
String? nickname = null;   // 可空,可能是字符串,也可能没有

声明成可空类型之后,编译器就会盯着你,逼你在使用前先处理 null。这套机制一上来会让人觉得别扭,写久了反而省心——因为那些原本会在半夜炸掉的 bug,很多在敲代码时就暴露了。

三、准备好你的环境

3.1 安装 Dart SDK

装 Dart 有两条路,看你的目标选。

如果专注 Flutter,装 Flutter SDK 就够了,它会自带一份匹配版本的 Dart SDK,不用单独折腾。

如果只想先学语言本身,或者做后端、命令行工具,那就单独装 Dart SDK:

# macOS(Homebrew)
brew install dart

# Windows(Chocolatey)
choco install dart-sdk

# Linux(Debian/Ubuntu 官方源)
sudo apt-get update
sudo apt-get install dart

装完验证一下:

dart --version

能打印出类似 Dart SDK version: 3.13.x (stable) 的信息,就说明成了。

3.2 不想装软件?用 DartPad

如果只是想快速试一段代码,不必安装任何东西。打开浏览器访问 dartpad.dev(国内可用 dartpad.cn),在线写、在线跑。本文里不少片段都可以直接粘过去试。

3.3 编辑器

首推 VS Code,装官方 Dart 插件就行,代码补全、错误提示、调试都有。做 Flutter 的话再补一个 Flutter 插件。Android Studio / IntelliJ IDEA 同样支持,适合习惯了 JetBrains 全家桶的人。

3.4 第一个程序

按传统,先来一句问候。新建 hello.dart:

void main() {
  print('Hello, Dart!');
}

在终端里跑:

dart hello.dart

几点说明:main() 是程序入口,每个 Dart 程序都从它开始;print() 往控制台输出内容;每行语句以分号结尾。就这么多,你已经跑通第一个 Dart 程序了。

四、语法基础

4.1 变量与常量

Dart 声明变量有好几种方式,用在什么场景是有讲究的。

// 显式声明类型:清晰,适合团队协作和大型项目
String name = 'Alice';
int age = 25;
double height = 1.75;
bool isStudent = true;

// var:让编译器推断类型,最常用
var city = 'Beijing';   // 推断为 String

// final:运行时常量,只能赋一次值
final now = DateTime.now();

// const:编译时常量,值必须在编译期就确定
const double pi = 3.14159;

var 和 dynamic 容易搞混,但差别很大。var 只是省去了你手写类型,一旦推断出来,类型就固定了;dynamic 则是真的关掉类型检查,变量运行时能装任何东西。后者用多了会丢掉类型安全,容易招来运行时错误,没事别碰。

var value = 10;
// value = 'hello';   // 报错:类型不匹配,String 不能赋给 int

dynamic flexible = 'hello';
flexible = 123;         // 运行期允许,但类型安全没了

final 和 const 的区别也常被问到。简单记:final 是"运行期确定、只赋一次",const 是"编译期就确定、完全不可变"。所以 final now = DateTime.now(); 合法,const now = DateTime.now(); 不行——因为 DateTime.now() 的值编译时算不出来。

4.2 内置类型

Dart 的内置类型不算多,常用的就下面这些:

int count = 0;                       // 整数
double price = 19.99;                // 浮点数
String title = 'Dart';               // 字符串
bool done = false;                   // 布尔值

List<int> numbers = [1, 2, 3];                     // 有序列表(类似数组)
Set<String> tags = {'dart', 'flutter'};            // 无序、元素唯一
Map<String, int> scores = {'math': 90, 'english': 85};  // 键值对

int 和 double 都是 num 的子类型,两者能一起参与算术运算。List、Set、Map 这三个集合类型,在 Dart 里出现频率极高,后面写业务代码基本离不开它们。

late 关键字值得提一句,用来延迟初始化:

late String description;

void init() {
  description = '稍后才会赋值';
}

它适合"确定会在使用前赋值、但没法在声明时给值"的场景。不过要小心——如果在赋值前就访问 late 变量,会抛 LateInitializationError。

4.3 字符串与插值

Dart 的字符串支持单引号也支持双引号,日常用单引号更常见。真正方便的是字符串插值:

String user = 'Tom';
String greeting = 'Hello, $user!';            // 简单插值
String detail = '名字长度:${user.length}';    // 表达式插值

String multi = '''
第一行
第二行
''';

$变量名 直接取值,${表达式} 可以塞进更复杂的计算。多行字符串用三个引号包起来,写模板、写 SQL 都很顺手。

4.4 运算符与空安全

Dart 有几个跟空安全配套的运算符,用熟了能省很多 if:

String? nickname;

print(nickname?.length);      // 安全访问:nickname 为 null 时整体返回 null,不报错
print(nickname ?? '匿名');     // 空值合并:左边为 null 就用右边
nickname ??= '默认昵称';        // 空值赋值:仅当左边为 null 时才赋值

int? maybe;
int value = maybe!;           // 非空断言:告诉编译器"它一定不为空"

! 这个非空断言要谨慎用。它相当于对编译器说"别管了,我保证有值",一旦你保证错了,运行时照样崩。能用 if 判断或者 ?? 兜底的地方,就别图省事用 !。

另外两个常用符号是展开运算符 ... 和级联运算符 ..:

var base = [1, 2, 3];
var more = [0, ...base, 4];   // 展开后是 [0, 1, 2, 3, 4]

final buffer = StringBuffer()
  ..write('Hello')
  ..write(', ')
  ..write('Dart');            // 级联:连续调用,不用反复写变量名

4.5 控制流

if、for、while 这些跟大多数语言差不多。switch 在 Dart 3 里被大幅强化,可以直接当表达式返回结果:

String describe(int code) {
  return switch (code) {
    200 => '成功',
    404 => '未找到',
    _ => '未知状态',
  };
}

_ 是通配符,兜住所有没列举的情况。这种写法比一串 if-else 干净不少,而且因为要覆盖所有分支,漏了情况编译器会提醒你。

五、函数

5.1 基本写法与箭头函数

int add(int a, int b) {
  return a + b;
}

// 单表达式可以简写成箭头函数
int multiply(int a, int b) => a * b;

5.2 可选参数

Dart 的可选参数分两种,位置可选和命名可选。

// 位置可选参数:用方括号包起来
void greet(String name, [String? title]) {
  print('Hello, $title $name');
}

// 命名可选参数:用花括号包起来,调用时必须写参数名
void createUser({String name = 'Guest', int age = 18}) {
  print('$name, $age');
}

void main() {
  greet('Tom');
  greet('Tom', 'Mr.');

  createUser();
  createUser(name: 'Alice', age: 30);
}

命名参数配上默认值,可读性非常好,尤其是在参数比较多的时候。Flutter 里几乎满屏都是这种写法。如果某个命名参数不给默认值又必须传,用 required 标记:

void update({required String id, String? note}) {
  // id 必传,note 可选
}

5.3 函数也是对象

在 Dart 里,函数是一等公民,可以赋值给变量、当参数传、当返回值返回。

final nums = [1, 2, 3, 4, 5];
final even = nums.where((n) => n.isEven).toList();   // 传入匿名函数做过滤

int apply(int x, int Function(int) transform) => transform(x);

这种把函数当参数传递的风格,在集合操作里特别常见。where、map、reduce 之类的链式调用,写起来比手写循环清楚得多。

六、面向对象

6.1 类与构造函数

class User {
  final String name;
  int age;

  // 构造函数的语法糖:直接给字段赋值
  User(this.name, this.age);

  // 命名构造函数
  User.guest()
      : name = 'Guest',
        age = 0;

  @override
  String toString() => 'User($name, $age)';
}

void main() {
  final u = User('Alice', 25);
  print(u);
}

User(this.name, this.age) 这种简写省掉了构造函数体里一堆 this.name = name,Dart 里非常常用。

6.2 继承、抽象与接口

Dart 是单继承,一个类只能 extends 一个父类,但可以实现多个接口。

abstract class Shape {
  double area();          // 抽象方法,没有方法体

  void describe() {
    print('面积是 ${area()}');   // 抽象类里也可以有具体方法
  }
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14 * radius * radius;

  @override
  void describe() => print('圆的面积是 ${area()}');
}

extends 是"继承并复用父类的实现",implements 是"只认规矩、不复用实现"。如果子类想用父类的现成逻辑,用 extends;如果只是把抽象类当接口用,就 implements。

6.3 mixin:组合能力的利器

Dart 不支持多继承,但提供了 mixin 来"混入"功能,把一组能力插到多个毫不相干的类里。

mixin Walker {
  void walk() => print('walking');
}

mixin Swimmer {
  void swim() => print('swimming');
}

class Duck with Walker, Swimmer {}

void main() {
  final d = Duck();
  d.walk();
  d.swim();
}

一个类可以 with 多个 mixin。当多个 mixin 有同名方法时,写在后面的会覆盖前面的——顺序是有意义的。Flutter 框架里到处能看到 mixin 的影子,很多"功能组合"就是靠它实现的。

6.4 扩展方法

扩展方法能在不改动原类的前提下,给它"加"方法。这招对付第三方库特别管用。

extension StringX on String {
  bool get isEmail => contains('@') && contains('.');
}

void main() {
  print('a@b.com'.isEmail);   // true
}

6.5 泛型

泛型把"类型"也变成参数,保证类型安全的同时复用代码。

T first<T>(List<T> items) => items.first;

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

void main() {
  final b = Box<String>('hello');
  print(b.value);
}

泛型还能加约束,限制 T 必须是某个类的子类:

abstract class Animal {
  void eat();
}

void feed<T extends Animal>(T animal) => animal.eat();

这样写的好处是,在 feed 里可以直接调用 Animal 才有的方法,编译期就把类型错误拦住了。

七、Dart 3 以来的现代特性

Dart 3 是这门语言历史上改动最大的一次。它一口气带来了 Records、Patterns、Class modifiers 等特性。这些不是纯粹的语法糖,它们实实在在改变了组织代码的方式。我挑几个最值得掌握的讲。

7.1 Records:函数返回多个值不再尴尬

以前一个函数想返回多个值,要么专门造个类,要么用 Map 凑合,要么引第三方 Tuple 库。Dart 3 的 Record 直接把这个痛点解决了。

// 位置记录
(String, int) getUser() => ('Alice', 25);

// 命名记录,字段有名字,可读性更好
({String name, int age}) getProfile() => (name: 'Bob', age: 30);

void main() {
  final user = getUser();
  print(user.$1);          // Alice
  print(user.$2);          // 25

  final profile = getProfile();
  print(profile.name);     // Bob
}

位置记录用 .$1、.$2 取值,命名记录直接用字段名。返回多个值又不想为此定义类的时候,Record 是首选。

7.2 Patterns:解构与匹配

模式匹配让解构数据变得非常自然,尤其是配合 Record 和集合使用。

var json = {'code': 200, 'data': [1, 2, 3]};

if (json case {'code': 200, 'data': List<int> data}) {
  print('数据长度 ${data.length}');
}

final list = [1, 2, 3];
final [a, b, c] = list;   // 直接解构
print('$a $b $c');

上面那段 if ... case 的做法,等于一边检查结构、一边把值取出来。以前得写好几句判空和类型转换,现在一行就能搞定。

7.3 Sealed class:让状态建模更严谨

sealed 密封类常用来表示"有限的几种可能状态",每个子类可以携带完全不同的数据。

sealed class Result {}

class Success extends Result {
  final String data;
  Success(this.data);
}

class Failure extends Result {
  final String message;
  Failure(this.message);
}

String render(Result r) => switch (r) {
  Success(:var data) => 'OK: $data',
  Failure(:var message) => 'Error: $message',
};

妙处在于:因为 Result 是密封的,编译器知道它只有 Success 和 Failure 两种子类,switch 一旦漏掉某一种,就会在编译期报错。这种"穷尽性检查"能把大量的状态遗漏问题提前暴露出来。

7.4 Class modifiers:把 API 的边界说清楚

Dart 3 给类加了一组修饰符,用来表达"这个类允许别人怎么用"。

// 不允许被继承、也不允许被实现,就是个完整实现
final class Config { ... }

// 不允许被实现,但可以被继承
base class Service { ... }

// 纯接口,只允许被实现
interface class Repository { ... }

这些修饰符看似不起眼,用在库的设计上却很关键——它让"哪些类能被继承、哪些只能被实现"变成写死在代码里的约定,而不是靠文档口口相传。

7.5 3.12 的私有命名参数

写不可变数据类时有个老麻烦:字段是私有的(下划线开头),但命名参数又不能以 _ 开头,于是只能加一层公开参数再手动赋值。Dart 3.12 把这个别扭劲消掉了:

class User {
  final String _name;
  final int _age;

  // 字段是私有的,调用方却能用干净的 name、age 传参
  User({required this._name, required this._age});
}

void main() {
  final u = User(name: 'Asher', age: 18);
}

保留私有字段的同时,构造 API 依然整洁,对数据模型、DTO、配置类这类场景很实用。

7.6 3.13 的 Primary Constructors

这是 3.13 里我个人觉得最顺手的一项。以前写个普通数据类,字段和构造函数总要把同一套信息写两遍:

// 老写法
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}

现在可以一行搞定:

// 3.13 新写法
class Point(final int x, final int y);

final int x 既声明了构造参数,也顺手定义了实例字段;改成 var 就是可变字段。3.13 还顺带给了构造函数的简写形式——在类里可以用 new 代替重复的类名:

class Point {
  double x;
  double y;

  new(this.x, this.y);              // 等价于 Point(this.x, this.y)
  new origin() : x = 0, y = 0;      // 等价于 Point.origin()
}

官方还配了一堆 lint 和 IDE 重构,帮你把旧构造自动迁到新写法上。要不要立刻全换,看团队习惯就好,但这项能力值得先了解。

八、异步编程

Dart 是单线程语言,但它的异步能力相当扎实。理解这一块,是写好 Flutter 应用的关键。

8.1 事件循环

Dart 内部跑着一个事件循环,处理两类队列:

  • 微任务队列(microtask queue),优先级高,主要装 Dart 内部生成的小任务。
  • 事件队列(event queue),装 I/O、网络请求、定时器、用户输入这些外部事件。

每一轮循环,Dart 先把微任务清空,再取一个事件队列的任务执行,如此往复。这也解释了一个常见现象:如果微任务堆得太多,就会拖慢后续的界面渲染,造成卡顿。

8.2 Future 与 async、await

Future 表示"一个将来才会有结果的计算"。async 和 await 是用来写异步代码的语法糖,让异步逻辑看起来像同步的。

Future<String> fetchName() async {
  await Future.delayed(const Duration(seconds: 1));
  return 'Dart';
}

Future<void> main() async {
  print('开始');
  final name = await fetchName();
  print('拿到: $name');
}

关键点:await 会暂停当前函数的执行,但不会阻塞整个程序——事件循环该处理别的事情照样处理。等 Future 完成,函数再从暂停处继续。这就是为什么异步代码能让界面保持流畅。

8.3 异步里的错误处理

异步代码的错误,用 try-catch 抓就行:

Future<int> parse(String raw) async {
  try {
    return int.parse(raw);
  } on FormatException catch (e) {
    print('解析失败: $e');
    rethrow;
  } finally {
    print('完成');
  }
}

有一点容易踩坑:await 之前抛出的错误不会被这个 try 捕获,所以异步逻辑要尽量写在 try 里面。

8.4 Stream:处理连续的事件流

Future 对应"一次性的结果",Stream 对应"一串陆续到达的事件"。读文件、监听传感器、接收用户输入,都是 Stream 的典型场景。

Stream<int> countUp(int max) async* {
  for (var i = 1; i <= max; i++) {
    await Future.delayed(const Duration(milliseconds: 200));
    yield i;
  }
}

Future<void> main() async {
  await for (final n in countUp(3)) {
    print(n);
  }
}

async* 加 yield 用来生成流,await for 用来消费流。也可以用 listen 订阅:

final controller = StreamController<int>();

controller.stream
    .where((n) => n.isEven)
    .map((n) => n * 10)
    .listen((value) => print(value));

controller.add(1);
controller.add(2);

Stream 分两种:单订阅流(默认,只能被监听一次,适合文件读取这类不能丢数据的场景)和广播流(可以被多个监听器订阅,适合事件分发)。

九、并发与 Isolate

异步解决的是"等待外部结果",但它并不会把计算搬到别的线程。如果有个 CPU 密集任务——解析超大 JSON、加密、图像处理——放在主 Isolate 里跑,界面照样会卡。

Dart 的解法是 Isolate。它是一种独立的执行单元,有自己的内存和事件循环,Isolate 之间不共享可变内存,只能通过消息传递通信。代价是通信有成本,收益是彻底避开了共享状态带来的竞态问题。

创建 Isolate 最省事的方式是 Isolate.run:

import 'dart:isolate';

Future<void> main() async {
  final result = await Isolate.run(() {
    var sum = 0;
    for (var i = 0; i < 1000000; i++) {
      sum += i;
    }
    return sum;
  });
  print(result);
}

如果需要在 Isolate 之间长期通信,就用底层的 Isolate.spawn,手动管理收发端口:

import 'dart:isolate';

void worker(SendPort mainPort) {
  final receive = ReceivePort();
  mainPort.send(receive.sendPort);
  receive.listen((msg) => mainPort.send('收到: $msg'));
}

Future<void> main() async {
  final receive = ReceivePort();
  await Isolate.spawn(worker, receive.sendPort);
  final sendPort = await receive.first as SendPort;
  sendPort.send('hello');
}

在 Flutter 里还有个 compute() 函数,适合一次性的 CPU 密集任务,会自动创建和销毁 Isolate,连端口都不用管。

判断标准很简单:一个任务耗时明显超过一帧(约 16 毫秒),就该考虑丢进 Isolate。

十、包管理与工程化

10.1 pubspec.yaml:项目的心脏

Dart 用 pub 管理依赖,配置文件是 pubspec.yaml。

name: my_app
description: 一个演示项目
version: 1.0.0

environment:
  sdk: ^3.13.0

dependencies:
  http: ^1.2.0

dev_dependencies:
  test: ^1.25.0
  lints: ^4.0.0

版本号前面的 ^ 表示"兼容这个主版本下的最新版",比如 ^1.2.0 允许装到 1.x 的最新,但不会越到 2.0。dependencies 是生产依赖,dev_dependencies 只在开发和测试时用。公共包都托管在 pub.dev 上。

常用命令:

dart pub get          # 拉取依赖
dart pub add http     # 添加一个依赖
dart pub upgrade      # 升级到允许范围内的最新版本

10.2 库的组织与导入

Dart 用 import 引入代码。常见几种写法:

import 'dart:async';                        // 引入 SDK 内置库
import 'dart:math' as math;                 // 起别名,避免命名冲突
import 'package:http/http.dart' as http;    // 引入第三方包

import 'src/utils.dart' show formatDate;    // 只引入指定内容
import 'src/legacy.dart' hide oldApi;       // 排除指定内容

以 dart: 开头的是 SDK 库,以 package: 开头的是包,其余按相对或绝对路径找。一个库里的私有标识符以下划线 _ 开头,外部访问不到。

10.3 测试、格式化与静态分析

Dart 的工具链是它的一大加分项,几乎开箱即用。

写测试:

import 'package:test/test.dart';

void main() {
  test('加法正确', () {
    expect(1 + 1, 2);
  });
}

跑测试和做检查:

dart test        # 运行测试
dart format .    # 统一代码风格
dart analyze     # 静态分析,提前发现潜在问题

dart format 会按官方风格把代码排版好,团队协作时省掉大量格式争论。dart analyze 则能揪出不少隐藏问题,建议纳入日常流程。

10.4 编译与部署

dart compile exe bin/main.dart   # 编译成原生命令行可执行文件
dart compile js web/main.dart    # 编译成 JavaScript,跑在浏览器
dart compile aot-snapshot main.dart   # 编译成 AOT 快照

dart compile exe 产出的可执行文件自带运行时,双击就能跑,不需要目标机器另外装 Dart。发布服务端工具或者命令行程序时很方便。

十一、学习路线:从入门到精通

把上面这些串成一条路线,大致可以分四个阶段。

第一阶段:写出能跑的代码。 把环境装好,搞懂变量、类型、控制流、函数。这个阶段的重点是"手感",多敲多改,别只看不练。

第二阶段:把面向对象吃透。 类、继承、抽象类、接口、mixin、扩展方法、泛型,这些是读懂 Dart 代码库的基础。Flutter 代码里到处都是它们的影子。

第三阶段:掌握异步与并发。 Future、async/await、Stream、Isolate,这是从"会写"到"写得好"的分水岭。很多卡顿、崩溃、内存问题,根子都在异步。

第四阶段:拥抱现代特性和工程化。 Records、Patterns、sealed class、class modifiers,加上依赖管理、测试、静态分析、编译发布。到了这一步,你面对的不只是语法,而是一整套工程习惯。

如果目标是 Flutter,那在第二、三阶段之后,就该把重心逐步挪到 widget 体系、状态管理和平台能力上了。Dart 是地基,Flutter 是楼。

十二、Dart 的生态与未来

聊到最后,绕不开 2026 年的大背景:AI。

Flutter 和 Dart 这两年明显在往 AI 工程化方向靠。官方的 Dart MCP 让 AI 助手能读懂项目结构、执行热重载调试、管理依赖、跑测试;Genkit-Dart 支持在 Dart 里编排 Flow、Tool、Prompt 和 RAG;再叠加上 Flutter Skills 之类的能力,AI 辅助开发的体验提升了不少。官方在 2025 年 Flutter 用户调查里给出的数字是:79% 的 Flutter 开发者会用 AI 助手。这股趋势短期内不会停。

当然,Dart 的根基还是那套跨平台能力。移动端、桌面端、Web、服务端,一套代码覆盖多个平台,配合成熟的工具链和活跃的包生态,这是它这些年能把位置坐稳的原因。语言本身也在往前走——3.x 系列每年都在加实用的语法糖和编译优化,节奏比前几年活跃得多。

十三、结语

Dart 不是那种一眼惊艳的语言。它更像一个踏实的手艺人:类型系统稳妥,异步模型清晰,工具链齐全,遇到问题查文档基本能解决。真要说门槛,大概就是空安全和异步这两块需要花点时间适应——但这两块恰恰是它能帮你少踩坑的地方。

如果你从这篇文章里只带走一句话,我希望是:先把基础语法和空安全写熟,再啃异步和并发,最后用 Dart 3 的现代特性把代码写得又短又稳。 剩下的,交给手上的项目去磨。

顺手记一下手边常用的命令,需要的时候翻出来用:

dart --version                      # 查看版本
dart create -t console my_app       # 新建控制台项目
dart run                            # 运行当前项目
dart test                           # 跑测试
dart format .                       # 格式化
dart analyze                        # 静态分析
dart compile exe bin/main.dart      # 编译成可执行文件

愿你在 Dart 和 Flutter 的世界里,少踩几个坑,多留几分从容。

发表评论

验证码图片,点击可更换