Dart 语法速查表 - Dart 常用语法与 Flutter 开发大全

这套速查给写 Flutter 应用或 Dart 后端的开发者,围绕每天都写的语法:空安全变量(final/const 与 late 之分)、函数与闭包、带混入(mixin)的类,以及 Future/async/await 异步流程。与平铺的 Dart 特性表不同,条目按语言构造分组,并专门点出从 Java/JS 过来的人容易踩的空安全行为。读完应能写出不带散落非空断言的空安全链、用 mixin 组合类能力,并用 Future 和 async/await 处理异步而不阻塞 UI 线程。

编程语言·共 43 条命令·最后更新 2026-07-21
dartflutter移动开发

典型使用场景

面向同时写 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 { }
混入(mixin)
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?.length
安全调用
nullable ?? '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]);
并行等待多个 Future
Completer<int> completer = Completer();
手动控制 Future

集合操作 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)
分组(需导入 collection 包)

参数矩阵

参数作用示例
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 都不可为 nullint? 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 时整个表达式短路为 nullnullable?.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 桥接成 FutureCompleter<int> completer = Completer();
list.where((x) => x > 5).toList()惰性 Iterable,where/map 不立即求值;忘记 toList() 得到的是 Iterable 而非 Listlist.where((x) => x > 5).toList()
list.reduce((a, b) => a + b)对 Iterable 逐个归约且不带初值,空集合直接抛 StateErrorlist.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. 1让分析器暴露所有空安全问题

    dart analyze

    按提示逐个消除警告;严格模式下任何非空引用都必须过空检查。

  2. 2格式化代码,保持与项目风格一致

    dart format .

    统一格式,避免 diff 里混入纯格式变更。

  3. 3刷新依赖解析结果

    dart pub get

    版本冲突或找不到包时先刷新解析,必要时看 pubspec.lock 是否过期。

  4. 4查看实际生效的依赖版本

    dart pub deps

    确认生效版本,而不是 pubspec.yaml 里写的版本范围。

  5. 5开启断言运行,检查不该走到的不变量

    dart --enable-asserts run bin/main.dart

    assert 中的分支一旦被触发就会立刻暴露问题。

  6. 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日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们