Dart 语法速查表 - Dart 常用语法与 Flutter 开发大全
这套速查给写 Flutter 应用或 Dart 后端的开发者,围绕每天都写的语法:空安全变量(final/const 与 late 之分)、函数与闭包、带混入(mixin)的类,以及 Future/async/await 异步流程。与平铺的 Dart 特性表不同,条目按语言构造分组,并专门点出从 Java/JS 过来的人容易踩的空安全行为。读完应能写出不带散落非空断言的空安全链、用 mixin 组合类能力,并用 Future 和 async/await 处理异步而不阻塞 UI 线程。
典型使用场景
面向同时写 Flutter UI 与纯 Dart 后端/脚本的工程师,尤其是从 Java、TypeScript 或 JavaScript 转过来的人。Dart 里最容易产生「我以为我知道」错觉的是三处:空安全是编译期强制而非运行时检查,null 与空集合、空字符串在语义上完全不同;类型是运行期检查,dynamic 一旦引入就放弃了静态保证;集合 API 是链式的,where/map 之后一定要 toList() 才能拿回 List,reduce 与 fold 的初值行为完全不同。本表覆盖变量与类型、函数(可选/命名参数、typedef)、类与 mixin、空安全运算符、Future/Stream 异步、以及集合操作。适合在做 code review 判断「这里该不该用 !」以及 Flutter 里 setState 与 Stream 生命周期时对照。
变量与类型 10
var name = 'Dart';String name = 'Dart';final name = 'Dart';const pi = 3.14;int? nullableInt;late String lateVar;dynamic anything = 42;List<int> numbers = [1, 2, 3];Map<String, int> ages = {'Alice': 25};Set<int> unique = {1, 2, 3};函数与闭包 6
int add(int a, int b) => a + b;void greet(String name, [String? title]) { }void greet({required String name, int age = 0}) { }Function fn = (x) => x * 2;void Function(int) callback;typedef Comparator<T> = int Function(T a, T b);类与混入 7
class Person { String name; Person(this.name); }class Student extends Person { }class Flyable mixin Fly { }class Bird extends Animal with Flyable { }abstract class Shape { double area(); }class Singleton { static final instance = Singleton._(); Singleton._(); }class Immutable { final int value; const Immutable(this.value); }空安全 6
String? nullable;nullable?.lengthnullable ?? 'default'nullable!if (nullable != null) { nullable.length; }late String lateVar;异步编程 6
Future<int> fetchData() async { return 42; }int result = await fetchData();Stream<int> countStream() async* { yield 1; }await for (var value in stream) { }Future.wait([future1, future2]);Completer<int> completer = Completer();集合操作 8
list.where((x) => x > 5).toList()list.map((x) => x * 2).toList()list.reduce((a, b) => a + b)list.fold(0, (sum, x) => sum + x)list.any((x) => x > 0)list.every((x) => x > 0)list.sort((a, b) => a.compareTo(b))list.groupBy((x) => x.category)参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
var name = 'Dart'; | 类型推断,后续赋值必须与推断类型兼容;不要用它逃避类型设计 | var name = 'Dart'; |
final name = 'Dart'; | 运行期不可重新赋值但可 late 初始化,类的首选字段修饰 | final name = 'Dart'; |
const pi = 3.14; | 编译期常量,必须是已知值;可内联进使用点,是 switch case 的要求 | const pi = 3.14; |
int? nullableInt; | 显式可空类型;Dart 3 起默认非空,任何裸 int 都不可为 null | int? nullableInt; |
dynamic anything = 42; | 关闭静态类型检查,运行期才发现类型错误;只在与 JS/JSON 动态互操作时使用 | dynamic anything = 42; |
List<int> / Map<String, int> / Set<int> | 三种集合类型,Set 去重且无序,Map 键唯一;选择错误的 Set 会悄悄丢数据 | List<int> numbers = [1, 2, 3]; |
void greet(String name, [String? title]) | 可选位置参数用方括号,必须放在必需参数之后,调用时可省略 | void greet(String name, [String? title]) { } |
void greet({required String name, int age = 0}) | 命名参数用花括号,可带默认值与 required;参数含义复杂时优于位置参数 | void greet({required String name, int age = 0}) { } |
typedef Comparator<T> = int Function(T a, T b); | 函数类型别名,让回调签名可复用与命名 | typedef Comparator<T> = int Function(T a, T b); |
class Bird extends Animal with Flyable { } | mixin 在 extends 之后混入行为,是 Dart 实现多态复用的主路径 | class Bird extends Animal with Flyable { } |
abstract class Shape { double area(); } | 抽象类只声明不实现,不能直接实例化 | abstract class Shape { double area(); } |
const Immutable(this.value) | const 构造器让实例编译期确定,可用作集合键与默认值 | class Immutable { final int value; const Immutable(this.value); } |
nullable?.length | 空安全调用,接收者为 null 时整个表达式短路为 null | nullable?.length |
nullable ?? 'default' | 空合并操作符,左侧为 null 时取右侧;与 ! 相反,优先用它而不是强制解包 | nullable ?? 'default' |
nullable! | 断言非空。编译期放行但运行期若为 null 直接抛错,是把编译错误推迟到线上 | nullable! |
Future<int> fetchData() async { return 42; } | 异步函数返回 Future,需 await 才能取值;不 await 会静默丢弃异常 | Future<int> fetchData() async { return 42; } |
Future.wait([future1, future2]) | 并发等待多个 Future;任一失败则整体抛错,不适合需要「部分成功」的场景 | Future.wait([future1, future2]); |
Stream<int> countStream() async* { yield 1; } | async* 生成单订阅 Stream,用 await for 或 listen 消费 | Stream<int> countStream() async* { yield 1; } |
Completer<int> completer = Completer(); | 手动完成 Future,用于把回调式 API 桥接成 Future | Completer<int> completer = Completer(); |
list.where((x) => x > 5).toList() | 惰性 Iterable,where/map 不立即求值;忘记 toList() 得到的是 Iterable 而非 List | list.where((x) => x > 5).toList() |
list.reduce((a, b) => a + b) | 对 Iterable 逐个归约且不带初值,空集合直接抛 StateError | list.reduce((a, b) => a + b) |
list.fold(0, (sum, x) => sum + x) | 带初值的归约,空集合返回初值而非抛错,做累加时比 reduce 安全 | list.fold(0, (sum, x) => sum + x) |
list.any((x) => x > 0) / list.every((x) => x > 0) | 存在性与全称判定,可在遍历中途短路,比 filter 后判 isEmpty 更省 | list.any((x) => x > 0) |
list.sort((a, b) => a.compareTo(b)) | 原地排序并要求比较器返回负/零/正,直接返回 bool 会抛类型错误 | list.sort((a, b) => a.compareTo(b)) |
list.groupBy((x) => x.category) | 按 key 分组成 Map(collection 包提供),是分组统计最直接的一行 | list.groupBy((x) => x.category) |
易错点与避坑指南
现象运行时报 "Null check operator used on a null value",崩在某个看起来不可能为空的位置。
原因使用了 ! 强制解包。编译器已把 null 检查让过去,运行期遇到 null 直接抛错,空安全本该由编译器强制。
处置把 ! 换成 ?. 或 ?? 兜底;若确实不可能为 null,在赋值边界用局部 final 变量完成类型收窄再使用。
现象where / map 之后想按下标取值或传给需要 List 的 API,报 "The method [] was called on Iterable"。
原因where 与 map 返回惰性的 Iterable,索引操作需要 List 接口。
处置链尾补 .toList();只做遍历或再次 map 时可以保留惰性,省一次中间分配。
现象空列表上做累加直接抛出 "Bad state: No element"。
原因reduce 没有初值,内部靠取第一个元素做种子,空集合无处可取。
处置改用 fold 并显式给初值(0 / 0.0 / []),处理可能为空的集合时这是默认写法。
现象sort 之后顺序完全不对,或运行期抛 "Invalid argument(s)"。
原因比较器返回了 bool 或 String,而比较器函数要求返回 int。
处置用 a.compareTo(b) 而不是 a > b;需要倒序时写成 b.compareTo(a)。
现象用 dynamic 接收 JSON 后编译期全绿,跑线上某条特殊数据才抛类型转换异常。
原因dynamic 关闭了静态检查,类型错误只在运行到该分支时才暴露。
处置解析后立刻做显式转换与校验,不要让 dynamic 流入业务逻辑。
现象Future 有 await,但异常没被捕获,程序静默跳过一段逻辑继续跑。
原因未被 await 的 Future 异常不会冒泡;而 Future.wait 里任一失败会整体失败,若没有 try 就直接落到 zone。
处置所有异步调用都要 await 或显式 catchError;应用入口用 runZonedGuarded / 框架错误钩子兜底。
现象同一个 Stream 被处理了多遍事件,或者第二次订阅直接抛错。
原因async* 生成的是单订阅 Stream,被 listen 两次会出错;而在 build 里反复创建新 Stream 又导致重复订阅。
处置需要多处消费时改用 broadcast Stream,并在组件销毁时 close,避免 UI 层重复挂监听。
现象用 Set 去重后取出来的顺序变了。
原因Set 不保证插入顺序(默认实现是哈希集),只有 List 保留顺序。
处置需要有序去重时写 list.toSet().toList(),或借助 Map 的插入顺序特性。
排障路径
1让分析器暴露所有空安全问题
dart analyze按提示逐个消除警告;严格模式下任何非空引用都必须过空检查。
2格式化代码,保持与项目风格一致
dart format .统一格式,避免 diff 里混入纯格式变更。
3刷新依赖解析结果
dart pub get版本冲突或找不到包时先刷新解析,必要时看 pubspec.lock 是否过期。
4查看实际生效的依赖版本
dart pub deps确认生效版本,而不是 pubspec.yaml 里写的版本范围。
5开启断言运行,检查不该走到的不变量
dart --enable-asserts run bin/main.dartassert 中的分支一旦被触发就会立刻暴露问题。
6把 info 也当错误做上线门禁
dart analyze --fatal-infos比默认更严格,适合在 CI 上卡住新增告警。
命令示例
可空类型的四种取值姿势,别混用
String? nullable;
final len = nullable?.length; // 安全调用:为 null 时整体为 null
final name = nullable ?? "default"; // 空合并:为 null 时用兜底值
// nullable!.length // 强制非空:为 null 时直接抛错
if (nullable != null) {
print(nullable.length); // 类型提升:此处 nullable 已是非空
}?. 只挡住「调用链上的 null」,只要链上有一处为 null,结果整条就是 null,int 类型的接收方会因隐式转换而编译失败。?? 返回非空值,适合提供兜底;! 在生产数据缺字段时会直接抛异常,只适合你确知非空的场景。
final / const / late 的取舍
var name = "Dart"; // 类型推导,可重新赋值
final name = "Dart"; // 运行期确定一次,之后不可变
const pi = 3.14; // 编译期常量,必须是字面量
late String cfg; // 延迟到首次使用前赋值
cfg = load();const 只能赋编译期常量(字面量或 const 表达式),用 const 装一个运行时算出来的值会编译失败。late 变量在赋值前被读到会抛 LateInitializationError,这类错误只在特定分支触发,比 null 安全问题更难复现。
Future 并行等待,而不是串行 await
Future<int> fetchA() async => 1;
Future<int> fetchB() async => 2;
final results = await Future.wait([fetchA(), fetchB()]);
// 任一失败即整体失败
try {
await Future.wait([fetchA(), fetchB()]);
} catch (e) {
rethrow;
}连续写两个 await 是串行的,总耗时相加;把 Future 放进 Future.wait 并行发起,耗时取最慢的那个。默认任一失败整个就失败,需要「部分成功」时得逐个捕获,而不是指望 wait 帮你兜底。
集合链式处理:filter → map → reduce
List<int> nums = [1, 2, 3, 4, 5, 6];
final evens = nums.where((x) => x.isEven).toList();
final doubled = nums.map((x) => x * 2).toList();
final total = nums.reduce((a, b) => a + b);
final hasNeg = nums.any((x) => x < 0);
final allPos = nums.every((x) => x > 0);
nums.sort((a, b) => a.compareTo(b));where / map 是惰性的,不调 toList() 就不会真正遍历,链很长时可省中间列表。需要一个累加值时用 fold(0, ...) 起步更直观;any / every 可短路,条件越可能在前面的判断顺序会影响性能。
不可变类 + 单例:const 构造与 static final 实例
class Immutable {
final int value;
const Immutable(this.value);
}
class Config {
static final instance = Config._();
Config._();
}final 字段 + const 构造函数让实例可被编译器常量折叠,并顺带禁掉所有字段修改;缺一个 final 就退回可变对象。static final 只保证「只初始化一次」,不是线程安全或延迟初始化的承诺,单例里做重活仍需自己加锁。
常见坑与注意事项
- 使用 ! 强制非空绕过编译器检查:本地测试数据齐全,一旦线上某条记录缺字段,运行到这行就是未捕获异常,接口整体 500 且日志里只有一行 null check operator。
- const 要求编译期常量,对运行时计算的值使用 const 会编译失败;对需要「一次赋值、不可变」的语义用 final 即可,混用会让意图难以判断。
- late 变量在首次赋值前被读取会抛 LateInitializationError;若赋值发生在异步回调或构造函数之后的流程里,报错只在特定执行顺序下出现,极难复现。
- 连续多个 await 是串行的,误以为并行会让接口耗时成倍累加;需要并行时把 Future 收集起来一次性等待。
- Stream 的 await for 在遍历过程中抛错会静默终止订阅,外层的 try/catch 未必能捕获到;取消订阅必须放在 finally 里,否则监听会一直挂着。
- subtype 之间用 dynamic 或跨 dynamic 调用成员时,编译器不再做空安全检查,运行时才抛错;把 dynamic 当逃逸口用,原本会被静态拦下的空指针问题全部推迟到线上。
提示
- Dart 的空安全是编译期检查,尽量用 ? 和 ?? 避免运行时异常。
- async/await 是异步编程的核心,避免阻塞 UI 线程。
- mixin 是 Dart 实现多继承的方式,适合复用代码。
常见问题
Dart 的空安全和 JS 的可选链,思路差在哪?
Dart 把空安全做成编译期检查:声明 String? nullable 时这个变量就明确允许为 null,用 nullable.length 会直接编译报错,必须先处理。处理手段有四类。nullable?.length 是安全调用,链上任一环为 null 整个表达式短路返回 null。nullable ?? 'default' 是空合并,只在 null 时取默认值。Dart 里的类型提升让 if (nullable != null) { ... } 块内的 nullable 自动被视为非空,无需强转。nullable! 是强非空断言,等于告诉编译器「我保证它不为 null」,用错会在运行时抛异常,应尽量少用。整条原则是:让 null 在类型里可见,而不是等到运行时才崩。
final 和 const 有什么区别?
两者都阻止重新赋值,但决定时机不同。const pi = 3.14 是编译期常量:值在编译时就已经确定,可以参与 const 构造(比如放进 const 列表或用作 switch 分支),而 final name = 'Dart' 是运行时常量,只保证这个变量在初始化之后不再变,指引的对象内容仍可修改。类里 const Immutable(this.value) 配合 final int value 字段,就构造出一个不可变类——实例创建后无法被改写。要表达「运行时算出来但不再变」,用 final;要用它参与编译期计算或放进常量上下文,用 const。
Dart 想实现多继承,为什么用 mixin 而不是复制代码?
Dart 的单继承限制在 class Student extends Person { } 体现,而需要横向复用行为时用 mixin:class Flyable mixin Fly { } 定义一份能力,class Bird extends Animal with Flyable { } 就把它混进继承链。这样同一个 Fly 行为可以被任意多个互不相关的类复用,不必在每个子类里重复粘贴方法,也不会撞上单继承「只能有一个父类」的限制。关键约束是 mixin 不能声明构造函数——它是行为片段而不是完整的类。如果只是想共享几个工具函数而不想引入继承关系,typedef Comparator<T> = int Function(T a, T b) 这样的类型别名会更合适。
Future 和 Stream 什么时候该用哪个?
Future 表示「一次结果」,如 Future<int> fetchData() async { return 42; },用 int result = await fetchData() 等待,适合单次请求或单次计算。Stream 表示「一串结果」,用 Stream<int> countStream() async* { yield 1; } 定义,消费方式是 await for (var value in stream) { },适合持续产出数据:分页加载、进度推送、多条记录逐条到达。需要并行等待多个独立的 Future 时用 Future.wait([future1, future2]);若需要一个由外部条件手动完成的 Future,则用 Completer<int>()。异步生成器配合 async* 和 yield 才能被 await for 逐个消费。
reduce 和 fold 都能归约一个列表,区别是什么?
差别只在有没有初始值。list.reduce((a, b) => a + b) 要求列表非空,直接用第一个元素当累加器,返回值类型必须与元素类型一致,所以把字符串列表求和会编译不过。list.fold(0, (sum, x) => sum + x) 显式提供初始值,因此空列表也有结果(就是那个初值),而且累加器类型可以与元素类型不同——fold<String>('', (s, x) => s + x) 能把 [1, 2, 3] 拼成 "123"。选择规则:处理非空集合且类型一致用 reduce,需要空集合兜底或跨类型归约用 fold。配套的还有 list.any 和 list.every 做条件判断,以及 list.where / map 串联后再归约。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。