Dart 3 引入了几个极其强大的语言特性,其中最耀眼、对我们日常开发改变最大的就是 记录(Records)密封类(Sealed classes),它们配合 模式匹配(Pattern Matching),让 Dart 的表现力达到了现代编程语言(如 Swift、Kotlin、Rust)的水平。

下面为你详细介绍这两个特性以及它们在 Flutter 开发中的实战场景:


一、 记录(Records)

记录 是一种匿名的、不可变的、轻量级的数据结构,允许你将多个不同类型的值捆绑在一起。

在 Dart 3 之前,如果一个函数需要返回多个值,我们要么定义一个专门的类,要么使用第三方库(如 tupledartz),非常麻烦。有了 Records,一切变得极其简单。

1. 基本语法

你可以使用小括号 () 来创建记录,它可以包含位置字段命名字段

// 1. 位置字段 (Positional fields)
var userInfo = ('Alice', 25);
print(userInfo.$1); // 输出: Alice
print(userInfo.$2); // 输出: 25

// 2. 命名字段 (Named fields)
var product = (name: 'MacBook', price: 1999.0);
print(product.name);  // 输出: MacBook
print(product.price); // 输出: 1999.0

// 3. 混合使用
var combined = ('Success', code: 200);
print(combined.$1);   // 输出: Success
print(combined.code); // 输出: 200

2. 最强用法:解构(Destructuring)

Records 真正的威力在于配合模式匹配进行解构赋值。当你从函数获取多返回值时,可以直接在声明时拆解它们:

// 定义一个返回 Record 的函数
(double lat, double lon) getLocation() {
  return (34.0522, -118.2437);
}

void main() {
  // 直接解构返回值,不需要通过 $1, $2 访问
  var (latitude, longitude) = getLocation();

  print('纬度: $latitude, 经度: $longitude');
}

实战意义:彻底淘汰了 Tuple 库。在处理 API 请求时,经常需要同时返回“数据”和“分页信息”,现在直接用 (List<Data>, PageInfo) 即可。


二、 密封类(Sealed Classes)

密封类 是一种特殊的类,它限制了类的继承体系。当你声明一个类为 sealed 时,它的所有子类都必须定义在同一个文件中

这听起来只是个限制,但它的目的是为了让编译器知道这个类到底有多少个子类(穷举性 Exhaustiveness)

1. 核心优势:穷举模式匹配 (Exhaustive Pattern Matching)

在 Dart 3 之前,处理不同状态(如加载中、成功、失败)通常用 enum,但 enum 不能携带复杂数据。如果用普通抽象类,在 switch 时编译器无法保证你处理了所有情况,往往被强制要求写一个没用的 default 分支。

有了 sealed,配合 Dart 3 的 switch 表达式,不仅可以携带数据,还能得到编译器的绝对安全保证

// 1. 定义一个密封类及其在同一个文件下的子类
sealed class AuthState {}

class Unauthenticated extends AuthState {}
class Loading extends AuthState {}
class Authenticated extends AuthState {
  final String userName;
  final String token;
  Authenticated(this.userName, this.token); // 可以携带复杂数据!
}
class Error extends AuthState {
  final String message;
  Error(this.message);
}

// 2. 使用 switch 表达式处理状态
Widget buildAuthUI(AuthState state) {
  // 注意:这里是一个 switch 表达式,直接返回值
  return switch (state) {
    Unauthenticated() => const LoginScreen(),
    Loading() => const CircularProgressIndicator(),
    // 自动解构出 Authenticated 类里的参数
    Authenticated(userName: var name) => WelcomeScreen(name: name),
    Error(:var message) => ErrorScreen(message: message), // 简写语法
  };
}

编译器的魔法:在上面的 switch 中,如果你漏写了 Loading() 的情况,代码会直接编译报错,提示你 "Non-exhaustive switch" (未穷尽的 switch)。这意味着你永远不会因为忘记处理某个状态而在运行时遇到 Bug。

2. 实战意义:替代部分 Freezed 和 Dartz 功能

如果你用过 freezed@freezed 联合类型(Union types)和 .when() / .map() 方法,或者 dartzEither<Left, Right>,你会发现 Dart 3 的 sealed class 配合 switch 完美地在语言原生层面实现了相同的功能。

例如,实现原生的 Result 类型(替代 Either):

sealed class Result<T, E> {}

class Success<T, E> extends Result<T, E> {
  final T data;
  Success(this.data);
}

class Failure<T, E> extends Result<T, E> {
  final E error;
  Failure(this.error);
}

// 使用
String handleApi(Result<User, Exception> result) {
  return switch (result) {
    Success(:var data) => '欢迎, ${data.name}',
    Failure(:var error) => '出错了: $error',
  };
}

总结

  • Records(记录):轻量级地组合和返回多个数据,无需定义冗余的数据类。
  • Sealed Classes(密封类):创建安全的、可控的类层级,完美表达“状态机”。
  • 两者结合(模式匹配):让 Flutter 开发者能够用原生语法写出极度安全、精简的 UI 状态分发逻辑和业务逻辑,大幅度降低了对外部库(如 freezed 中的联合类型特性、dartztuple)的依赖。这就是为什么目前推荐的 Flutter 项目都会强调 "要求 Dart 3+" 的原因。

关键维度横向对比表

表格
特性 Record List Class 对象
长度 ✅ 固定,编译确定 ❌ 动态可变 字段数量由类定义固定
访问方式 $1/$2(位置)、自定义命名 数字下标 [index] . 成员名 .name
值相等 == ✅ 原生支持深度值比较 ❌ 默认引用比较 ❌ 默认引用比较,需手动重写
是否可添加 / 删除元素 ❌ 不能增删字段 ✅ 可变 List 支持 add/remove ❌ 不能动态新增字段
能否拥有方法 ❌ 只能存数据 ✅ 大量内置方法 ✅ 自定义任意方法
类型安全 极强,每个位置独立类型 统一泛型类型 List<T> 强类型,自定义成员类型
可变能力 Record 本身不可变;内部对象可变 可变 / 不可变两种模式 看字段是否 final
解构支持 ✅ 原生解构 ✅ 支持解构 ❌ 不支持原生解构(Dart 暂不支持对象解构)
适用场景 临时多返回值、简易数据打包 一组同类型集合(列表、数组) 业务实体、复杂数据模型