一、什么是Record?
Record 是匿名、不可变、复合聚合类型,用来把多个不同类型数据打包成单个对象。 和 List / Set / Map 对比核心区别:
- 固定长度:一旦创建,字段数量不能增减 异构存储:
- 同一个 Record 里可以放 int、String、bool 等完全不同类型
- 强类型约束:每个字段类型被编译器识别,不会丢失类型安全
- 只读不可改:创建后字段值无法重新赋值
Record 是完整合法变量,支持所有常规操作:存变量、函数传参 / 多返回值、嵌套、放进 List/Map/Set。
二、Record 基础语法
1. Record 字面量
括号包裹,逗号分隔,分为位置字段、命名字段两类;位置字段写最前面,命名字段带 key: 写在后面。
// 位置字段 + 命名字段混合
var info = ('Jim', age: 20, flag: true, 'Student');Code language: JavaScript (javascript)
2. Record 用法
1 位置字段
// 声明:两个位置字段,依次 String、int
(String, int) user;
// 赋值匹配结构
user = ('李四', 22);
Code language: JavaScript (javascript)
2 命名字段
用 {} 包裹类型声明
// 声明两个命名字段 a(int)、b(bool)
({int a, bool b}) data;
data = (a: 100, b: false);Code language: JavaScript (javascript)
3 混合使用
位置字段 + 命名字段混合标注
// 1个位置String,2个命名字段score(int)、pass(bool)
(String, {int score, bool pass}) exam;
exam = ('期末考', score: 90, pass: true);
Code language: JavaScript (javascript)
三、易错
1:命名字段的名字属于类型结构,名字不一样就是两种完全不同类型
// 类型:({int a, int b})
({int a, int b}) pointAB = (a: 1, b: 2);
// 类型:({int x, int y})
({int x, int y}) pointXY = (x: 3, y: 4);
// 报错!类型不匹配,不能互相赋值
// pointAB = pointXY;
Code language: JavaScript (javascript)
对于命名字段,虽然都是int int 但是因为名字不应用 一个是ab 一个是xy 所以不能互相赋值。除非是位置字段,看下面
2:位置字段可以在类型注解里写别名,仅做注释,不影响类型匹配
括号里给位置字段起的名字只是给人看的文档标注,编译器忽略,结构一致就能互相赋值:
(int a, int b) p1 = (10, 20);
(int x, int y) p2 = (30, 40);
// 合法,底层都是两个int位置字段,类型完全一致
p1 = p2;Code language: JavaScript (javascript)
类比函数形参:函数参数起名不改变函数签名,逻辑一致。
四、读取 Record 内部字段
Record 不可变,只有 getter 没有 setter,无法修改字段值。
- 命名字段:直接
.字段名获取 - 位置字段:用
$数字获取,数字跳过所有命名字段,只统计位置字段顺序
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // 第一个位置字段:first
print(record.a); // 命名字段a:2
print(record.b); // 命名字段b:true
print(record.$2); // 第二个位置字段:last
Code language: PHP (php)
五、Record 结构类型判定规则
Record 没有单独 class 定义,依靠结构形状判定同类型:
形状 = 字段数量 + 每个字段的类型 + 命名字段的名称
只要形状完全一致,就算来自不同文件、不同库,也属于同一个类型。
每个字段静态类型编译器全程识别,不会丢失类型信息:
// 位置1 num类型,位置2 Object类型
(num, Object) pair = (42, '一段文字');
var firstVal = pair.$1; // 静态类型 num,运行实际 int
var secondVal = pair.$2;// 静态类型 Object,运行实际 StringCode language: JavaScript (javascript)
六、Record 相等判断 ==
满足两点才会相等:
- 两个 Record 形状完全一致
- 对应位置 / 命名字段的值全部相等
命名字段书写顺序不影响相等判断;但命名字段名称不同直接判定类型不同,一定不相等。
// 位置字段,形状相同,数值相等 → true
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // true
// 命名字段名字不一样,类型不同 → false
({int x, int y, int z}) p = (x:1, y:2, z:3);
({int r, int g, int b}) c = (r:1, g:2, b:3);
print(p == c); // falseCode language: PHP (php)
Record 自动内置重写 == 和 hashCode,不用手动实现。
七、函数多返回值
Dart 函数只能单个返回值,用 Record 打包多组不同类型数据,搭配模式解构快速拆分成独立变量,完美替代 List/Map(不会丢失类型)。
1位置字段解构
// 返回 姓名(String)、年龄(int)
(String name, int age) getUserInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
void main() {
final userJson = {'name': 'Dash', 'age': 10, 'color': 'blue'};
// 直接解构,一行拆分两个变量
var (userName, userAge) = getUserInfo(userJson);
print(userName); // Dash
print(userAge); // 10
}
Code language: JavaScript (javascript)
2命名字段解构
:字段名
({String name, int age}) getUserInfo(Map<String, dynamic> json) {
return (name: json['name'] as String, age: json['age'] as int);
}
void main() {
final userJson = {'name': 'Dash', 'age': 10};
// 命名解构写法
final (:name, :age) = getUserInfo(userJson);
print(name);
}
Code language: JavaScript (javascript)
对比劣势
如果以前,像其他的编程语言,例如 C++ C# JAVA 这些 不用 Record 需要如下操作:
- 新建 class:代码冗余,要写构造、字段;
- List 返回:丢失类型安全,全部变成 dynamic;
- Map 返回:键容易写错,类型无法静态校验。
八、轻量结构化数据
不用新建类,只需存储数据、不需要方法时,直接用 Record,省去定义 class 的冗余代码,适合批量列表数据。
示例
页面按钮配置列表(如下例子是flutter中的例子,读者了解就可以,等学了flutter,就会理解)
import 'package:flutter/material.dart';
void main() {
final buttonList = [
(
label: "上传文件",
icon: const Icon(Icons.upload_file),
onPressed: () => print("点击上传按钮"),
),
(
label: "查看详情",
icon: const Icon(Icons.info),
onPressed: () => print("点击详情按钮"),
)
];
}
Code language: JavaScript (javascript)
九、typedef 给 Record 类型起别名
简化长类型
Record 类型书写较长,可以用 typedef 定义类型别名,复用、方便后期统一修改字段结构。
基础使用
// 给按钮Record起别名,onPressed允许为空
typedef ButtonConfig = ({
String label,
Icon icon,
void Function()? onPressed
});
// 直接使用别名声明列表
List<ButtonConfig> buttons = [
(label: "提交", icon: const Icon(Icons.check), onPressed: () {}),
(label: "取消", icon: const Icon(Icons.close), onPressed: null),
];
Code language: PHP (php)
后续
如果后续需要给数据增加方法、封装逻辑,不用修改业务遍历代码,仅替换类型即可:
- 方案 1:替换为普通 Class
class ButtonConfig {
final String label;
final Icon icon;
final void Function()? onPressed;
ButtonConfig({required this.label, required this.icon, this.onPressed});
// 新增自定义方法
bool get hasClickEvent => onPressed != null;
}
Code language: PHP (php)
- 方案 2:替换为 Extension Type 包装原有 Record
extension type ButtonConfig._(({String label, Icon icon, void Function()? onPressed}) _) {
String get label => _.label;
Icon get icon => _.icon;
void Function()? get onPressed => _.onPressed;
ButtonConfig({required String label, required Icon icon, void Function()? onPressed})
: this._((label: label, icon: icon, onPressed: onPressed));
bool get hasClickEvent => _.onPressed != null;
}
Code language: PHP (php)
两种改造方式下,页面循环渲染按钮的业务代码完全不用改动。
十、总结
- 版本门槛:Dart 3.0+;
- 三大核心属性:匿名、不可变、聚合;固定长度、异构、强类型;
- 字段两类:位置字段
$1/$2、命名字段.name; - 类型判定:结构形状决定类型,命名字段名称参与形状判定;位置字段别名仅注释;
- 相等判断:形状相同、所有字段值相等即为 true;
- 核心优势:函数多返回值、轻量数据容器、完整类型安全;
- 拓展方案:typedef 简化类型别名,后期可无缝替换 class /extension type。