Dart 3.0 语法新特性 | 模式匹配 Patterns

4,793 阅读3分钟

一、 Patterns 是什么

下面是官方对 Patterns 特性的说明 patterns :
从下面的第一句中可以知道,Patterns 是一种语法级的特性,而语法特性是一种语言的根基。

Patterns are a syntactic category in the Dart language, like statements and expressions.
Patterns 是 Dart 语言中的一个语法类别,就像语句和表达式一样。

从下面第二句话在可以看出,Pattern 和数据的特征匹配相关。

A pattern represents the shape of a set of values that it may match against actual values.
Pattern 表示可能与实际值相匹配的一组值的特征。

在英文中 Pattern 一词有:模式、形式、图样的意思。说句题外话:String 字符串和 Regex 正则表达式都实现 Pattern 接口,就说明 Pattern 一词和模式匹配的渊源。这里强调一句:Dart 3.0 的 Patterns 语法和上面提及的 Pattern 类型没有半毛钱关系。

在日常开发中,我们使用的类型都是具有一定的结构特征,而结构正是类中数据的栖身之地。Patterns 像是一种在语法层面,对类型结构特征提取的规则,结合匹配来更方便地完成一些工作。在类型之中, Record、List、Map 三种类型,有着非常明显的结构特征:

记录 Record 类型
   |--- 普通值 (v1, v2 ,...)
   |--- 命名值 (k1:v1,k2:v2 ,...)

列表 List 类型 
   |--- 值列表 [v1,v2,...]
   
映射 Map 类型 
   |--- 键值对 {k1:v1, k2:v2 ,...}

二、 Patterns 的解构能力

解构(Destructuring) 就是访问并提取对象的某些数据,为某些指定的变量进行赋值的过程。其中提取数据就需要运用到 Patterns 的匹配特性。下面通过几个小例子了解一下:

1.对 Record 类型的解构
  • 非命名 Record 的类型

如下 foo 中 : 默认情况下,想要访问记录对象中的数据,需要通过 $1$2 :

void foo(){
  var user = ('toly',29);
  String name = user.$1;
  int age = user.$2;
  print('======$name====${age}===');
}

如下 foo1 中 : 可以使用 Patterns 的特性,直接将 user 对象解构,为 nameage 赋值。这就是 Patterns 最重要的能力之一,

void foo1() {
  var user = ('toly',29);
  var (name, age) = user; // 直接解构对象
  print('======$name====${age}===');
}

Record 的解构语法是 :

var ( 变量 1 , 变量 2 , ... ) = Record 对象


  • 命名 Record 类型

对于元素被命名的 Record 类型而言, 可以通过如下 Patterns 语法进行解构。比如下面,一句代码就可以调试为 abc 三个变量赋值:

  var position = (x:1,y:3, 'p0');
  var (x:a, y:b ,c) = position;
  print('====$a====$b====$c====');

var ( k1: 变量 1, k2: 变量 2 , ... ) = Record 对象

对于命名的数值而言,可以通过 :key 进行简写。比如下面的 :x 含义就是 x:x ,表示:将右侧对象中的名称为 x 的数据,为左侧的 x 变量赋值。

var position = (x:1,y:3, 'p0');
var (:x,:y,d) = position;
print('====$x====$y====$d====');

这样的好处是能少起个变量名,适合 起名困难症者;但与此同时,这样你无法为变量名起别的名字。


2. 对 List 和 Map 的解构

除了 Record 类型 ,还有 List 和 Map 也支持解构。效果上类似,都是访问对象的数据,并直接为变量赋值。List 的结构语法是 :

var [ 变量1, 变量2, ... ] = List 对象

void foo2(){
  List<int> numList = [1, 2, 3];
  var [a, b, c] = numList;
  print('====$a====$b====$c=');
}

如下是对 Map 对象的解构,语法是:

var { key : 变量1, key: 变量2, ...} = Map 对象

void foo3(){
  Map<String,dynamic> data = {
      'name': 'toly',
      'age': 29,
    };
  var {'name': name,'age': age}= data;
  print('======$name====${age}===');
}

同理,对于 Map 元素组成的 List 列表,也可以通过对应的语法进行解构,只要左侧变量结构符合右侧对象结构即可 :

void foo4(){
  var data = [
    {
      'name': 'toly',
      'age': 29,
    },
    {
      'name': 'ls',
      'age': 28,
    },
  ];
  var [{'name': name,'age': age},{'name': name1,'age': age2}] = data;
  print('======$name====${age}===$name1====${age2}====');
}

3. 对普通对象的解构

除了可以解构特定的对象之外,还可以对普通对象进行解构,但要注意 只有构造函数中的命名参数字段支持解构。如下所示,定义了 Person 类:

class Person {
  final String name;
  final int age;


  Person({
    required this.name,
    required this.age,
  });
}

对象结构语法为:

var 类名( 命名字段1 : 变量1 , 命名字段2 : 变量2, ...) = 对象

void foo5(){
  Person person = Person(name: 'toly', age: 29);
  var Person(name : a, age: b) = person;
  print('======$a====${b}===');
}

同样,如果懒得为变量起名字,也可以直接让字段名称为变量名:

var 类名( : 命名字段1 , : 命名字段2, ...) = 对象

void foo5(){
  Person person = Person(name: 'toly', age: 29);
  var Person(:name, :age) = person;
  print('======$name====${age}===');
}

对于普通对象而言, get 方法也可以被模式匹配,用于解构。如下所示:Person 类中添加一个 nameLen 的get 方法,用于返回名字的长度:此时可以通过 Person(nameLen: len) 匹配,将名称长度解构为 len 变量赋值:

image.png

class Person {
  final String name;
  final int age;
  
  Person({
    required this.name,
    required this.age,
  });

  int get nameLen => name.length;
}

三、解构时需要注意的问题

1、解构结构的一致性

首先要注意的是,对于 List 、Record、Map 对象来说,左侧的解构结构要和对象数据结构 完全一致。 比如下面列表有三个元素,你只解构了两个,在运行时会报错。我觉得比较坑的是:

如果不一致的话,在 编辑期间 无法发觉,问题只能在运行时暴露,这就或多或少存在一定的代码隐患。

image.png

同理,如果在解构 Map 对象时 key 写错了,在运行时也会报错:

image.png


2、忽略解构单元

List 、Record、Map 对象的解构需要保持结构的一致性,但有时候并不需要完全结构所有的数据,此时可以使用 _ 来忽略对应的结构单元。

void foo8() {
  List<int> numList = [1, 2, 3];
  var [first, _,_] = numList;
  print('====$first====');
}

3、小结

本文从 解构 的角度,认识了一些常用类型的 Pattern 语法,下图是一个小结:

image.png

从这里我们或多或少可以体会出 Patterns 是一种 对模式的匹配。而解构是运用模式匹配的能力,从对象中提取数据为对应变量赋值。我们一开始就说了 Patterns 是一种语法级的特性,解构只是它的作用之一。而且模式也不只是针对于类型,某些运算符也可以作为模式的一部分。

本文简单认识一下 Patterns 的概念和在解构中的应用。另外,在流程控制中和匹配相关的有一个关键字 ---- switch 。下一篇将从 switch 语法的变化,继续了解 Patterns 的作用。谢谢观看 ~