关于 dart-3-updates
为 Dart 开发者提供 Dart 3 语言特性的使用指南,涵盖分支、模式匹配与记录,帮助编写或重构代码并迁移旧代码。
主要功能
- 01使用 switch 与 if-case 分支
- 02应用模式匹配和解构
- 03理解记录及其适用场景
- 04用 sealed 类型检查穷尽性
- 05按步骤迁移旧版 Dart 代码
适用场景
- 01重构 if-else 链与 switch 语句
- 02从集合或对象解构数据
- 03选择记录或数据类
- 04将旧代码升级为 Dart 3 写法
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ evanca/ flutter-ai-rules --skill dart-3-updates安装后在 Claude Code 中输入/dart-3-updates确认已加载。
详细介绍
Dart 3 更新技能
正确且惯用地应用 Dart 3 语言特性——分支、模式、模式类型和记录。
使用时机
在以下情况下使用此技能:
- 编写或重构
switch语句或if-else链。 - 创建新的数据承载类,并决定使用密封类、记录还是普通类。
- 从映射、列表、记录或对象中解构值。
- 将 Dart 3 之前的代码现代化,使用模式、穷尽性检查或
switch表达式。
1. 分支
if / if-case
// Standard if
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
// if-case: match and destructure against a single pattern
if (pair case [int x, int y]) {
print('$x, $y');
}
if条件必须计算出bool值。- 在
if-case中,模式中声明的变量的作用域限定于匹配的分支。 - 如果模式不匹配,控制流会转到
else分支(如果存在)。
switch 语句
switch (command) {
case 'quit':
quit();
case 'start' || 'begin': // logical-or pattern
startGame();
default:
print('Unknown command');
}
- 每个匹配的
case主体执行后都会跳转到末尾——不需要break。 - 非空的 case 可以以
continue、throw或return结束。 - 使用
default或_处理不匹配的值。 - 空 case 会贯穿执行;在空 case 中使用
break可阻止贯穿执行。 - 使用带标签的
continue实现非顺序贯穿执行。 - 使用逻辑或模式(
case a || b)让多个 case 共用一个主体。
switch 表达式
final color = switch (shape) {
Circle() => 'red',
Square() => 'blue',
_ => 'unknown',
};
- 省略
case;使用=>编写主体;用逗号分隔各个 case。 - 默认分支必须使用
_(而不是default)。 - 会生成一个值。
穷尽性
- Dart 会在编译时检查
switch语句和表达式的穷尽性。 - 使用
default/_、枚举或sealed类型来满足穷尽性要求。
sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}
// Dart knows all subtypes — no default needed:
String describe(Shape s) => switch (s) {
Circle() => 'circle',
Square() => 'square',
};
守卫子句
switch (point) {
case (int x, int y) when x == y:
print('Diagonal: $x');
case (int x, int y):
print('$x, $y');
}
- 在模式后添加
when condition,进一步限制匹配条件。 - 可用于
if-case、switch语句和switch表达式。 - 如果守卫条件为
false,执行会继续到下一个 case。
2. 模式
模式表示用于匹配和解构的值的形状。
用途
// Variable declaration
var (a, [b, c]) = ('str', [1, 2]);
// Variable assignment (swap)
(b, a) = (a, b);
// for-in loop destructuring
for (final MapEntry(:key, :value) in map.entries) { ... }
// switch / if-case (see Branches section)
- 通配符
_会忽略匹配值中的部分内容。 - 列表模式中的剩余元素(
...)会忽略其余元素。 - Case 模式是可失败的:如果不匹配,执行会继续到下一个 case。
- 在 case 中解构的值会成为局部变量,其作用域限定于该 case 主体。
对象模式
var Foo(:one, :two) = myFoo;
JSON / 嵌套数据验证
if (data case {'user': [String name, int age]}) {
print('$name, $age');
}
3. 模式类型
| 模式 | 语法 | 描述 |
|---|---|---|
| 逻辑或 | p1 || p2 | 任一分支匹配时即匹配。所有分支必须绑定相同的变量。 |
| 逻辑与 | p1 && p2 | 两者都匹配时即匹配。变量名不能重复。 |
| 关系 | == c、< c、>= c | 将值与常量进行比较。与 && 结合可表示范围。 |
| 类型转换 | subpattern as Type | 断言类型,然后匹配内部模式。类型不匹配时会抛出异常。 |
| 空值检查 | subpattern? | 匹配非空值;绑定为非空类型。 |
| 空值断言 | subpattern! | 匹配非空值,否则抛出异常。在声明中使用可消除空值。 |
| 常量 | 42、'str'、const Foo() | 值等于该常量时即匹配。 |
| 变量 | var name、final Type name | 将匹配的值绑定到新变量。带类型的形式只匹配声明的类型。 |
| 通配符 | _、Type _ | 匹配任意值,但不进行绑定。 |
| 带括号 | (subpattern) | 控制优先级。 |
| 列表 | [p1, p2] | 按位置匹配列表。除非使用剩余元素,否则长度必须相同。 |
| 剩余元素 | ...、...rest | 匹配任意长度的尾部,或收集剩余元素。 |
| 映射 | {'key': subpattern} | 按键匹配映射。缺少键时会抛出 StateError。 |
| 记录 | (p1, p2)、(x: p1, y: p2) | 按形状匹配记录;如果可以推断字段名,则可省略。 |
| 对象 | ClassName(field: p) | 按类型匹配,并通过 getter 解构。额外字段会被忽略。 |
- 使用括号对优先级较低的模式进行分组。
- 所有模式类型都可以嵌套和组合。
4. 记录
// Create
var record = ('first', a: 2, b: true, 'last');
// Type annotation
({int a, bool b}) namedRecord;
// Access
print(record.$1); // positional: 'first'
print(record.a); // named: 2
- 记录是匿名、不可变、固定大小的聚合。
- 每个字段都可以有不同的类型(异构)。
- 通过内置 getter(
$1、$2、.name)访问字段;没有 setter。 - 如果两个记录的形状相同且字段值相等,则它们相等。
hashCode和==会自动定义。
多个返回值
(String name, int age) userInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
var (name, age) = userInfo(json);
// Named fields:
final (:name, :age) = userInfo(json);
记录与数据类的区别
在以下情况下使用记录:
- 从单个函数中返回多个值(小规模、一次性使用)。
- 在局部将少量值组合在一起,且不会在整个代码库中重复使用。
- 需要结构相等性,且不需要额外行为。
在以下情况下使用类:
- 该类型会在多个文件或功能中重复使用。
- 需要方法、封装、继承或
copyWith。 - 该类型属于公共 API 或长期使用的数据模型。
- 更改类型形状时,需要由类型系统在整个代码库中进行检查。
其他最佳实践
- 使用
typedef定义记录类型,以提高可读性和可维护性。 - 更改记录类型别名并不能保证整个代码库的类型安全——只有类才能提供完整的抽象。
5. 迁移工作流
现代化 Dart 3 之前的代码时,请遵循以下步骤:
步骤 1 — 将 if-else 链替换为 switch 表达式
// Before (pre-Dart 3)
String label;
if (status == Status.loading) {
label = 'Loading...';
} else if (status == Status.success) {
label = 'Done';
} else {
label = 'Error';
}
// After (Dart 3)
final label = switch (status) {
Status.loading => 'Loading...',
Status.success => 'Done',
Status.error => 'Error',
};
步骤 2 — 将抽象类层次结构转换为密封类
// Before
abstract class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }
// After — enables exhaustive switch
sealed class Result {}
final class Success extends Result { const Success(this.data); final String data; }
final class Failure extends Result { const Failure(this.error); final String error; }
步骤 3 — 使用解构返回多个值
将仅用于返回多个值的包装类替换为记录。
步骤 4 — 验证
每次更改后运行 dart analyze,确认穷尽性和类型安全。
评论
支持 Markdown