跳到主要内容

Typedefs(类型别名)

typedef 用于给已有类型起一个更简短、更能表达用途的名称。它不会创建新类型,而是让复杂的集合类型、record type 或函数类型更容易阅读和复用。

typedef JsonMap = Map<String, Object?>;

void main() {
JsonMap course = {
'name': 'Dart',
'lessons': 12,
};

print(course['name']); // Dart
}

这里的 JsonMapMap<String, Object?> 表示同一个类型。使用别名后,变量和函数签名能够直接说明这份映射的用途。

声明和使用类型别名

类型别名的基本语法是在 typedef 后写别名、等号和原类型:

typedef 别名 = 原类型;

别名可以出现在任何需要原类型的位置,包括变量类型、参数类型和返回类型:

typedef Scores = Map<String, int>;

Scores addScore(Scores current, String topic, int score) {
return {
...current,
topic: score,
};
}

void main() {
Scores scores = {'types': 90};
scores = addScore(scores, 'typedefs', 95);

print(scores); // {types: 90, typedefs: 95}
}

Scores 只替换类型的写法,不会改变 Map 的创建、访问或相等性规则。

为 record type 命名

record type 结构较长,并且可能在多个函数签名中重复。此时使用 typedef 可以集中表达字段含义:

typedef CourseProgress = ({
String topic,
int completed,
int total,
});

CourseProgress completeLesson(CourseProgress progress) {
return (
topic: progress.topic,
completed: progress.completed + 1,
total: progress.total,
);
}

void main() {
CourseProgress progress = (
topic: 'Types',
completed: 3,
total: 5,
);

print(completeLesson(progress));
}

别名让签名更容易阅读,但 record 的类型仍由字段结构决定。另一个具有相同字段名称和类型的 record 可以直接赋给 CourseProgress

为函数类型命名

函数类型需要同时描述返回值和参数。签名被多处使用时,可以用 typedef 为它提供名称:

typedef Formatter = String Function(String value);

String formatTitle(String title, Formatter formatter) {
return formatter(title);
}

String addPrefix(String value) => 'Topic: $value';

void main() {
print(formatTitle('Typedefs', addPrefix)); // Topic: Typedefs
print(formatTitle('Dart', (value) => value.toUpperCase())); // DART
}

Formatter 表示“接收一个 String,并返回一个 String 的函数”。命名后的函数类型仍然可以接收顶层函数、静态方法或签名兼容的匿名函数。

没有别名时,也可以直接写函数类型:

String formatTitle(
String title,
String Function(String value) formatter,
) {
return formatter(title);
}

只使用一次且较短的函数类型通常适合直接写在签名中;相同签名反复出现,或签名本身具有明确业务含义时,再使用 typedef

保留完整的函数签名

定义函数类型别名时,应写清返回类型和每个参数的类型。不要为了缩短代码而使用宽泛的 Function

typedef Operation = int Function(int left, int right);

int calculate(int left, int right, Operation operation) {
return operation(left, right);
}

void main() {
print(calculate(6, 2, (left, right) => left ~/ right)); // 3

// calculate(6, 2, (value) => value * 2);
// 编译错误:匿名函数的参数数量与 Operation 不匹配
}

明确的函数签名能让静态分析器检查参数数量、参数类型和返回类型;Function 只能说明值是函数,无法提供这些调用信息。

泛型类型别名

类型别名也可以声明类型参数。这样可以保留类型关系,而不必为每一种具体类型分别声明别名:

typedef Converter<Input, Output> = Output Function(Input value);

Output convert<Input, Output>(
Input value,
Converter<Input, Output> converter,
) {
return converter(value);
}

void main() {
Converter<String, int> countCharacters = (value) => value.length;
Converter<int, String> describeScore = (value) => 'score: $value';

print(convert('Dart', countCharacters)); // 4
print(convert(95, describeScore)); // score: 95
}

Converter<String, int>Converter<int, String> 使用同一个别名,但它们表示不同的具体函数类型。

泛型别名也能用于集合等非函数类型:

typedef Index<Key, Value> = Map<Key, List<Value>>;

void main() {
Index<String, int> scoresByTopic = {
'types': [90, 95],
'functions': [88],
};

print(scoresByTopic['types']); // [90, 95]
}

如果类型参数需要满足某个上界,可以像其他泛型声明一样使用 extends

typedef NumberParser<T extends num> = T Function(String source);

void main() {
NumberParser<int> parseCount = int.parse;
NumberParser<double> parseRatio = double.parse;

print(parseCount('12')); // 12
print(parseRatio('0.75')); // 0.75
}

typedef 不会创建新类型

类型别名不会在原类型之外建立新的类型身份。别名与原类型之间可以直接赋值,也不会增加额外的运行时包装:

typedef UserId = int;

void main() {
int rawId = 42;
UserId userId = rawId;

int copiedId = userId;
print(copiedId); // 42
print(userId is int); // true
}

因此,UserId 不能阻止普通 int 被误当成用户编号。如果需要构造校验、封装行为,或需要让两个底层表示相同的值保持类型隔离,应声明类,而不是依赖 typedef

class UserId {
const UserId(this.value);

final int value;
}

void main() {
const userId = UserId(42);

// UserId invalid = 42; // 编译错误:int 不是 UserId
print(userId.value);
}

可以按下面的原则选择:

需求更合适的方式
缩短复杂类型的写法typedef
为重复的函数签名命名typedef
为简单数据结构提供局部名称record type 加 typedef
创建具有独立身份、校验或方法的类型

与 TypeScript 的关键差异

Dart 的 typedef 可以类比 TypeScript 的 type alias:二者都能为已有类型命名,也都不会仅因为声明别名就创建新的运行时类型。

typedef StringList = List<String>;
typedef Predicate<T> = bool Function(T value);
type StringList = string[]
type Predicate<T> = (value: T) => boolean
对比项DartTypeScript
声明关键字typedeftype
函数类型ReturnType Function(Parameters)(parameters) => ReturnType
集合别名List<T>Map<K, V> 等类型命名为数组、对象等类型命名
新的类型身份不会创建不会创建
运行时模型别名引用 Dart 类型,泛型类型参数会保留类型通常在编译为 JavaScript 后被擦除

语法虽然相似,但不要根据 TypeScript 的运行时类型擦除推断 Dart 行为。Dart 的泛型会在运行时保留具体类型信息,typedef 并不会改变这一点。

常见误区

误区一:认为别名能够隔离相同底层类型

typedef OrderId = int 不会让 OrderIdint 互不兼容。需要独立类型身份时应声明类。

误区二:为所有短类型创建别名

别名应当减少阅读成本或表达稳定含义。为只出现一次的 List<String> 创建一个含义模糊的别名,反而会让读者需要跳转查找定义。

误区三:用 Function 代替具体函数类型

Function 丢失参数与返回值信息。已知回调签名时,应写出完整函数类型,必要时再使用 typedef 命名。

误区四:认为 record 别名改变了结构化类型规则

为 record type 添加别名只改善可读性,不会把它变成名义类型。字段结构相同的 record 仍属于同一类型。

误区五:把别名当作运行时对象

typedef 是类型声明,不能像构造函数一样被调用,也不会产生可供业务逻辑读取的独立包装对象。

小结

  • typedef 为已有类型提供名称,适合复用复杂类型和表达用途。
  • 别名可以用于集合、record type、函数类型以及其他已有类型。
  • 函数类型别名应保留完整的参数和返回值信息,避免退化为 Function
  • 泛型类型别名可以声明类型参数和 extends 上界。
  • 类型别名不会创建新类型,也不会增加运行时包装。
  • 需要独立类型身份、校验或行为时,应使用类。
  • Dart typedef 与 TypeScript type alias 相似,但两种语言的泛型运行时模型不同。