参考链接
Records 在dart中的最低版本
Records require a language version of at least 3.0.
基本写法
Records是用小括号包裹的集合,这一点倒是和python有点相似。
var record = ('first', a: 2, b: true, 'last');Records可以直接在方法返回参数,函数参数定义。Records好像在dart没有具体类型包装,不像有int或者double,可以直接变量申明,如下:
(int, int) swap((int, int) record) { var (a, b) = record; return (b, a);}
// Record type annotation in a variable declaration:(String, int) record;
// Initialize it with a record expression:record = ('A string', 123);构造可命名字段的Records
可命名字段Records,是由小括号作为大包围,大括号作为小包围,并可定义成员变量名。赋值可直接引用变量名赋值,如下所示:
// Record type annotation in a variable declaration:({int a, bool b}) record;
// 初始化这个record的变量,a和b分别指定并赋值record = (a: 123, b: true);下面是不一样,如果把recordXY赋值给recordAB,是会报错的,因为它们是不一样类型,可命名字段的Records是Records类型定义概念的一部分,所以即使里面类型一样,也是不相同的Records类型。
({int a, int b}) recordAB = (a: 1, b: 2);({int x, int y}) recordXY = (x: 3, y: 4);
// Compile error! These records don't have the same type.// recordAB = recordXY;但是你可以定义成位置字段,这个函数中位置形参是相似的
(int a, int b) recordAB = (1, 2);(int x, int y) recordXY = (3, 4);
recordAB = recordXY; // OK.下面这种也是位置字段
(num, Object) pair = (42, 'a');
var first = pair.$1; // Static type `num`, runtime type `int`.var second = pair.$2; // Static type `Object`, runtime type `String`.Record中元素的调用方式,其中a和b是可命名字段
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'print(record.a); // Prints 2print(record.b); // Prints trueprint(record.$2); // Prints 'last'Records没有单独的类型申明
Records没有单独类型申明,Records变量的唯一性取决于Records中的字段数量、字段类型、record变量名,三个条件决定这个变量是否重复、唯一,三个条件官网称之为Record’s shape
While you can’t declare a unique type for a record shape, you can create type aliases for readability and reuse. To learn how and when to do so, check out Records and typedefs.
Record可命名字段和位置字段的区别
因为比较反直觉,所以还是着重写两种区别,要注意下面这两种的区别
//这个叫可命名字段,可根据字段名进行赋值,不需要顺序一致({int a, int b}) recordA;//这个位置字段,赋值时,可以固定顺序给相应字段赋值(int a int b) recordB;例如下面:
//即使你赋值顺序打乱,只要保证引用的字段不出错,也是无碍的 ({int a, int b}) recordA = (b: 1, a: 2); print("recordA.a: ${recordA.a}, recordA.b: ${recordA.b}");//recordA.a: 2, recordA.b: 1//要注意下面这个编译是会出错的,因为位置字段,不允许你引用字段名进行赋值(int a int b) recordB = (b: 1, a: 2);//error
//下面这个是正确的,但是要注意位置字段,在调用字段时,不允许指定字段名。 (int a, int b) recordB = (2, 1); print("recordB.a: ${recordB.$1}, recordB.b: ${recordB.$2}");//recordB.a: 2, recordB.b: 1不过可命名字段是只可用于申明的类型,或者方法的返回参数、形参。挺反直觉的,所以上面的内容还要与下面的再区分一下
final buttons = [ ( // ← 这里创建的是命名字段 Record! label: "Button I", // 使用 label: 语法 icon: "test", onPressed: () => ..., ), ...];
// 因为创建时用了 label: / icon: / onPressed: 语法// Dart 推断类型为:({String label, String icon, void Function() onPressed})// 所以可以用名字访问print(buttons[0].label); // ✅ 正确print(buttons[0].icon); // ✅ 正确buttons[0].onPressed(); // ✅ 正确final buttons = [ ( // ← 没有名字,纯位置 "Button I", // 只有值,没有 label: "test", () => print("..."), ),];
// 类型推断为:(String, String, void Function())print(buttons[0].$1); // ✅ "Button I"print(buttons[0].$2); // ✅ "test"print(buttons[0].label); // ❌ 编译错误!没有 label 这个名字Record的比较
Record’s shape 跟刚刚讲过是由字段数量、字段类型、record变量名决定的,可命名字段的顺序是不影响判断Record是否相同的,如下
(int x, int y, int z) point = (1, 2, 3);(int r, int g, int b) color = (1, 2, 3);
print(point == color); // Prints 'true'.({int x, int y, int z}) point = (x: 1, y: 2, z: 3);({int r, int g, int b}) color = (r: 1, g: 2, b: 3);
print(point == color); // Prints 'false'. Lint: Equals on unrelated types.解构
Dart应该借鉴了JS或者python的解构语法,将Record赋值时,可分解为多个变量。由于返回的类型可定义为命名字段和位置字段,所以解构的也分两种
// Returns multiple values in a record:(String name, int age) userInfo(Map<String, dynamic> json) { return (json['name'] as String, json['age'] as int);}
final json = <String, dynamic>{'name': 'Dash', 'age': 10, 'color': 'blue'};
// 因为userInfo返回的Record的位置字段,这里解构只是按顺序返回了,将第一个元素给name,第二元素给agevar (name, age) = userInfo(json);print("$name,$age");//Dash,10*/因为是命名字段所以可引用对应的命名字段并分解为多个变量
// Returns multiple values in a record:({String name, int age}) userInfo(Map<String, dynamic> json) { return (name: json['name'] as String, age: json['age'] as int);}
final json = <String, dynamic>{'name': 'Dash', 'age': 10, 'color': 'blue'};
//下面这个相当于var (name: name, age: age) = userInfo(json); 只不过省略了对命名字段的引用,实际由于可命名字段引用的解构,所以即使顺序互换也无妨:var (age: age,name: name,) = userInfo(json); 但是顺序互换了就不能采用下面的省略命名的方式var (: name, : age) = userInfo(json);print("name: $name, age: $age");//name: Dash, age: 10Record的数据结构
官方文档突然又称是tuple了,果然是借鉴python或者其他语言的,还不如直接称之tuple呢,学过其他语言的一眼就看出来是tuple
final buttons = [ ( label: "Button I", icon: const Icon(Icons.upload_file), onPressed: () => print("Action -> Button I"), ), ( label: "Button II", icon: const Icon(Icons.info), onPressed: () => print("Action -> Button II"), )];//不过print( "Buttons: $buttons[0].label, ${buttons[0].icon}, ${buttons[0].onPressed}");为Record定义一个方便的类型
因为Record没有自己类型,每次申明都需要完整字段申明。所以,可以typedef关键字,为Record定义一个类,表面看像是一个类,但是实际内部是一个Record
typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed});final List<ButtonItem> buttons = [ // ...];更多的应用,如定义一个Container
List<Container> widget = [ for (var button in buttons) Container( margin: const EdgeInsets.all(4.0), child: OutlinedButton.icon( onPressed: button.onPressed, icon: button.icon, label: Text(button.label), ), ),];将Record作为类的构造参数,那么你在调用时就可以类来申明变量
class ButtonItem { final String label; final Icon icon; final void Function()? onPressed; ButtonItem({required this.label, required this.icon, this.onPressed}); bool get hasOnpressed => onPressed != null;}或者用extension type关键实现类型抽象。但是要注意dart怎么必须是3.3.0或者更高版本(This requires the ‘inline-class’ language feature to be enabled. Try updating your pubspec.yaml to set the minimum SDK constraint to 3.3.0 or higher, and running ‘pub get’.)
extension type ButtonItem._(({String label, Icon icon, void Function()? onPressed}) _) { String get label => _.label; Icon get icon => _.icon; void Function()? get onPressed => _.onPressed; ButtonItem({required String label, required Icon icon, void Function()? onPressed}) : this._((label: label, icon: icon, onPressed: onPressed)); bool get hasOnpressed => _.onPressed != null;}无论上面哪种实现,都可以用下面方式调用
final List<ButtonItem> buttons = [ ButtonItem( label: "Button I", icon: const Icon(Icons.upload_file), onPressed: () => print("Action -> Button I"), ), ButtonItem( label: "Button II", icon: const Icon(Icons.info), onPressed: () => print("Action -> Button II"), )];