深色模式
Dart 模式语法(Patterns Matching & Destructuring)
概念
什么是模式
模式是Dart语言中的一种语法范畴,就像语句和表达式一样。模式表示一组值的形状,它可以与实际值进行匹配。
参考Dart文档的原文描述会更好理解:
Patterns are a syntactic category in the Dart language, like statements and expressions. A pattern represents the shape of a set of values that it may match against actual values.
模式的作用
简单来说,2个作用:
- 匹配:判断值的特征
- 解构:取值
模式可以用来匹配一个值、解构一个值、或同时应用解构与匹配。
什么是模式匹配和模式解构
当我们匹配一个值的时候,称为模式匹配,解构一个值的时候,称为模式解构。但是很多时候,会同时应用到匹配和解构,所以,我认为应该使用模式这个词来称呼这一语法,而不是说模式匹配或模式解构。
举例:
dart
var [x, y] = [1, 2];
以上代码,首先,[x, y]
是一个模式,它匹配一个含有2个元素的数组,匹配成功以后,将数组元素解构,并分别赋值给x
、y
。简单的一行代码,包含了模式匹配与模式解构。
模式语法的使用范围
模式可以出现在Dart代码中的以下地方:
本地变量定义和本地变量赋值
- 变量定义
- 变量赋值
for-in循环
- 普通for-in
- collection-for中的for-in
if-case和switch-case
- 普通if-case
- collection-if中的if-case
- switch-case声明
- switch-case表达式
其它不允许的地方(目前Dart3.2),则不能使用模式,比如参数解构(已经有人提出,可能会在以后的版本中支持)。
使用范围举例
下面举一些简单的示例,说明模式的用法。
为了方便示例,先定义一个简单的类型:
dart
class Rect {
const Rect({
required this.width,
required this.height,
});
final int width;
final int height;
}
本地变量定义和本地变量赋值
定义本地变量时,解构:
dart
var (a, [b, c]) = ('str', [1, 2]); // record
var [x, y] = [1, 2]; // list
var {"name": name} = {"name": "Jack"}; // map
var Rect(width: j, height: k) = Rect(width: 2, height: 4); // obj
var Rect(:width, :height) = Rect(width: 2, height: 4); // 简写
解构赋值到已有的变量:
dart
var a, b;
(a, b) = (3, 4);
var x, y;
[x, y] = [3, 4];
var name;
{"name": name} = {"name": "Jack"};
for-in循环
在for-in循环中,解构集合中的元素:
dart
List<Rect> list = [
Rect(width: 2, height: 4),
Rect(width: 3, height: 6),
Rect(width: 4, height: 8),
];
for (final Rect(:width, :height) in list) {
print("$width, $height");
}
在collection-for的for-in循环中:
dart
List<Rect> list = [
Rect(width: 2, height: 4),
Rect(width: 3, height: 6),
Rect(width: 4, height: 8),
];
var list2 = [
1,
for (final Rect(:width) in list) width,
];
print(list2); // [1, 2, 3, 4]
if-case和switch-case
在if-case中:
dart
Rect rect = Rect(width: 2, height: 4);
if (rect case Rect(width: 2, height: 4)) {
// match
}
if (rect case Rect(width: 2)) {
// match
}
if (rect case Rect(width: 2, height: 10)) {
// not match
}
在collection-if的if-case中:
dart
Rect rect = Rect(width: 2, height: 4);
var list2 = [
1,
if (rect case Rect(width: 2, :final height)) height,
if (rect case Rect(width: 2, height: 10)) 100,
if (rect case Rect(width: 2)) 7,
];
print(list2); // [1, 2, 7]
在switch-case声明中:
dart
Rect rect = Rect(width: 2, height: 4);
switch (rect) {
case Rect(width: 2, height: 10):
// not match
case Rect(width: 2, height: 4):
// match
case Rect(width: 2):
// match
}
在switch-case表达式中:
dart
Rect rect = Rect(width: 2, height: 4);
var height = switch (rect) {
Rect(width: 2, height: 10) => 100, // not match
Rect(width: 3) => 20, // not match
Rect(width: 2, :final height) => height, // match
_ => -1,
};
print(height);
模式语法参考
配合Dart的语法,模式有更复杂的使用方式。
逻辑或
多个条件,满足1个,即匹配成功。
dart
Color color = Colors.blue;
var isPrimary = switch (color) {
Colors.red || Colors.yellow || Colors.blue => true,
_ => false,
};
逻辑与
多个条件必须同时匹配。
dart
var r = (1, 2);
switch (r) {
case (final a, final b) && (final x, final y):
print("$a, $b, $x, $y"); // 1, 2, 1, 2
}
关系运算
dart
var score = 77;
var result = switch (score) {
< 60 => '不及格',
>= 60 && < 80 => '及格',
>= 80 && < 100 => '优秀',
== 100 => '满分',
_ => '-',
};
print(result); // 及格
类型转换
在解构的过程中进行类型转换。
dart
(num, Object) record = (1, 's');
var (i as int, s as String) = record;
空检查
以下代码,将可空类型的mayNullString
转换为不可空类型s
。如果传入的变量mayNullString
是一个字符串,则匹配成功,如果变量mayNullString
是null
,则匹配失败。
dart
String? mayNullString = "hello";
switch (mayNullString) {
case var s?:
// 此s的类型是String
print(s);
}
空断言
如果row
的第2个元素是null
,则抛出异常。
dart
List<String?> row = ["user", "Jack"];
switch (row) {
case ['user', var name!]:
print(name);
}
常量
匹配一个值是否等于一个常量,也可以匹配它的某个元素是否等于一个常量。
dart
var obj;
switch (obj) {
case 100:
}
变量
匹配时指定变量的类型。
dart
var obj;
switch (obj) {
case (int a, String b):
}
标识符
解构的时候忽略第3个元素。
dart
var obj;
switch (obj) {
case [1, var middle, _]:
}
加括号
加上括号可以提高匹配的优先级。
通配符
通配符_
,匹配成功以后不会绑定值。
可以用于忽略某个值:
dart
var list = [1, 2, 3];
var [_, two, _] = list;
用于判断类型:
dart
(int, String) record = (1, '2');
if (record case (int _, String _)) {
// 匹配成功
}
模式语法参考(重要)
下面举例说明模式语法应用在List
、Map
、Record
、Object
类型上的场景。
List
基本的:
dart
var list = [1, 2, 3];
var [a, b, c] = list;
如果模式不匹配,进行赋值会抛出异常:
dart
var list = [1, 2, 3];
var [a, b] = list; // 异常
避免异常:
dart
var list = [1, 2, 3];
var [a, b, _] = list;
如果是在case中,匹配失败则不执行该分支代码,不会抛出异常:
dart
var list = [1, 2, 3, 4, 5, 6, 7];
if (list case [final a, final b]) {
// 匹配失败,不执行
}
忽略剩余元素:
dart
var list = [1, 2, 3, 4, 5, 6, 7];
var [a, b, ...] = list;
var [a, b, ..., f, g] = list;
var [..., f, g] = list;
匹配剩余元素:
dart
var list = [1, 2, 3, 4, 5, 6, 7];
var [a, b, ...rest, f, g] = list;
print(rest); // [3, 4, 5]
Map
基本的:
dart
final scores = {
"Jack": 99,
"Kara": 98,
};
var {
"Jack": score1,
"Kara": score2,
} = scores;
不必匹配所有元素(注意:这一点与List、Record不同):
dart
final scores = {
"Jack": 99,
"Kara": 98,
};
final {"Jack": score} = scores;
如果模式不匹配,进行赋值会抛出异常:
dart
final scores = {
"Jack": 99,
"Kara": 98,
};
var {
"Jacky": score1, // key错误
"Kara": score2,
} = scores;
如果是在case中,匹配失败则不执行该分支代码,不会抛出异常:
dart
final scores = {
"Jack": 99,
"Kara": 98,
};
switch (scores) {
case {
"Jacky": final score1, // key错误
"Kara": final score2,
}:
// 匹配失败,不执行
}
Record
必须匹配Record
值的shape,即:元素个数、每个元素的类型、位置元素(positional fields)的顺序、命名元素(named fields)的名称。
由于
Record
类型的特点,位置元素的名称不影响其shape,命名元素的顺序不影响其shape。
简单的:
dart
(int, int, int) record = (1, 2, 3);
final (a, b, c) = record;
含有命名元素:
dart
(int, int, {int c}) record = (1, 2, c: 3);
final (x, y, c: z) = record;
命名简写:
dart
(int, int, {int c}) record = (1, 2, c: 3);
final (a, b, :c) = record; // 解构到同名的变量
另外,位置元素的命名可以忽略:
dart
(int m, int n, {int c}) record = (1, 2, c: 3); // 位置元素的名称不是shape的一部分
final (x, y, c: z) = record;
命名元素的顺序可以随意:
dart
(int, int, {int c}) record = (1, 2, c: 3);
final (a, :c, b) = record; // 匹配成功
如果模式不匹配,进行赋值会抛出异常:
dart
(int a, String b, {int c}) record = (1, 'hello', c: 3);
final (int a, String b, :c) = record; // 匹配成功
final (a, b) = record; // 元素个数不对
final (a, int b, :String c) = record; // 元素类型不对
final (String b, int a, :c) = record; // 位置元素的顺序不对(可以归为元素类型不对)
final (a, b, x: c) = record; // 命名元素的名称不对
如果是在case中,匹配失败则不执行该分支代码,不会抛出异常:
dart
(int a, String b, {int c}) record = (1, 'hello', c: 3);
switch (record) {
case (final a, final b): // 元素个数不对
case (final a, final b, :String c): // 元素类型不对
case (String b, int a, :final c): // 位置元素的顺序不对(可以归为元素类型不对)
case (final a, final b, :final d): // 命名元素的名称不对
case (final a, final b, :final c): // 匹配成功
}
Object
可以匹配一个对象暴露的所有getters属性,包括公开的:字段、getters、方法,不必匹配所有属性。
为了演示,先对前面定义的Rect类略加改造:
dart
class Rect {
const Rect({
required this.width,
required this.height,
});
final width;
final height;
int get perimeter => (width + height) * 2;
int area() {
return width * height;
}
}
基本的匹配:
dart
Rect rect = Rect(width: 2, height: 4);
var Rect(:width, :height, :area, :perimeter) = rect;
print("宽:$width, 高:$height, 面积:${area()}, 周长:$perimeter");
不必匹配所有属性:
dart
Rect rect = Rect(width: 2, height: 4);
var Rect(width: a) = rect;
如果尝试匹配不存在的属性,代码会报错:
dart
Rect rect = Rect(width: 2, height: 4);
var Rect(a: a) = rect; // 代码报错
可以在case语句中匹配特定的属性值:
dart
Rect rect = Rect(width: 2, height: 4);
switch (rect) {
case Rect(width: 3, height: final b): // 匹配失败
case Rect(width: 2, height: final b): // 匹配成功
}