mcpField
D
dart-3-updates

dart-3-updates

github.com/evanca开发663223 次安装3 周前更新
代码仓库

关于 dart-3-updates

为 Dart 开发者提供 Dart 3 语言特性的使用指南,涵盖分支、模式匹配与记录,帮助编写或重构代码并迁移旧代码。

主要功能

  1. 01使用 switch 与 if-case 分支
  2. 02应用模式匹配和解构
  3. 03理解记录及其适用场景
  4. 04用 sealed 类型检查穷尽性
  5. 05按步骤迁移旧版 Dart 代码

适用场景

  1. 01重构 if-else 链与 switch 语句
  2. 02从集合或对象解构数据
  3. 03选择记录或数据类
  4. 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