Dart typedef and function types
typedef is the type alias mechanism in Dart, which allows you to define short, readable names for complex types.
This chapter introduces two uses of typedef: the traditional function type alias, and the generic type alias introduced in Dart 3.0.
typedef function type alias
When a function type is used frequently, typedef can give it a clear name.
This is particularly useful for scenarios such as callback functions and event handlers.
Example
// Represents: a function that takes two int parameters and returns int
typedef IntOperation = int Function(int a, int b);
// Define a callback type
// Represents: a function that receives a String message and returns no value
typedef MessageCallback = void Function(String message);
// Use typedef as parameter type
int performOperation(int x, int y, IntOperation operation) {
return operation(x, y);
}
void logMessage(String msg, MessageCallback callback) {
print('Preparing to output message...');
callback('[EXAMPLE] $msg');
}
void main() {
// Pass in a function that matches the IntOperation signature
int add(int a, int b) => a + b;
int multiply(int a, int b) => a * b;
print('10 + 5 = ${performOperation(10, 5, add)}');
print('10 × 5 = ${performOperation(10, 5, multiply)}');
// You can also directly pass in an anonymous function
int result = performOperation(20, 4, (a, b) => a ~/ b);
print('20 ÷ 4 = $result');
// Use MessageCallback
logMessage('Operation completed', (msg) {
print('Received message: $msg');
});
}
10 + 5 = 15 10 × 5 = 50 20 ÷ 4 = 5 准备输出消息... 收到消息: [EXAMPLE] 操作完成
Without typedef, you need to repeatedly write verbose function type signatures at every parameter position.
With typedef, type declarations become clear and unified, and modifications only need to be made in one place.
typedef is just an alias for a type; it does not create a new type. IntOperation and int Function(int, int) are completely equivalent in the type system.
Function type as parameter
In Dart, functions are first-class citizens and can be passed around like ordinary values.
Example
void processNumbers(
List<int> numbers,
bool Function(int) filter,
String Function(int) formatter,
) {
var filtered = numbers.where(filter);
for (var n in filtered) {
print(formatter(n));
}
}
// A higher-order function that returns a function
int Function(int) makeMultiplier(int factor) {
// The returned closure captures factor
return (int n) => n * factor;
}
void main() {
var scores = [55, 78, 92, 60, 45, 88];
print('--- Passing Score ---');
processNumbers(
scores,
(n) => n >= 60, // Filter condition
(n) => 'EXAMPLE Score: $n points', // Formatting
);
print('--- High Scores (> 80) ---');
processNumbers(
scores,
(n) => n > 80,
(n) => 'High Score: $n points',
);
// Function as return value
var doubler = makeMultiplier(2);
var tripler = makeMultiplier(3);
print('5 × 2 = ${doubler(5)}');
print('5 × 3 = ${tripler(5)}');
}
--- 及格分数 --- EXAMPLE 分数: 78 分 EXAMPLE 分数: 92 分 EXAMPLE 分数: 60 分 EXAMPLE 分数: 88 分 --- 高分(> 80)--- 高分: 92 分 高分: 88 分 5 × 2 = 10 5 × 3 = 15
Callback Pattern
The callback pattern is the most common application scenario for function types.
It delegates the control of "what to do" to the caller, making code more flexible and reusable.
Example
typedef ResultCallback<T> = void Function(T result);
typedef ErrorCallback = void Function(String error);
// Simulate asynchronous operation
void fetchUserData(
String userId, {
required ResultCallback<Map<String, dynamic>> onSuccess,
required ErrorCallback onError,
}) {
// Simulate network request
print('Fetching user data...');
// Simulate success/failure
if (userId == 'example') {
var data = {
'id': 'example',
'name': 'EXAMPLE user',
'level': 'VIP',
};
onSuccess(data);
} else {
onError('User $userId does not exist');
}
}
void main() {
// Use callback to handle result
fetchUserData(
'example',
onSuccess: (data) {
print('Fetch successful!');
print('Username: ${data['name']}');
print('Level: ${data['level']}');
},
onError: (error) {
print('Fetch failed: $error');
},
);
print('---');
// Test failure scenario
fetchUserData(
'unknown',
onSuccess: (data) {
print('Fetch successful');
},
onError: (error) {
print('Fetch failed: $error');
},
);
}
正在获取用户数据... 获取成功! 用户名: EXAMPLE 用户 等级: VIP --- 正在获取用户数据... 获取失败: 用户 unknown 不存在
Common application scenarios for the callback pattern:
| Scenarios | Callback Type Example |
|---|---|
| Network request | onSuccess(data) / onError(error) |
| UI event | onTap() / onLongPress() |
| Data conversion | Callbacks in map(), where(), reduce() |
| Timer | Timer(callback, duration) |
| Animation completed | onComplete() |
Although the callback pattern is flexible, it leads to "callback hell" when handling multi-layered asynchronous operations. Dart provides async/await to handle asynchronous flows more elegantly, which we will cover in detail in Chapter 17.
Dart 3.0 Generic Type Alias
Dart 3.0 extends the capabilities of typedef; now you can create aliases for any type, not just function types.
Example
// Aliases for complex collection types
typedef JsonMap = Map<String, dynamic>;
typedef UserList = List<Map<String, dynamic>>;
// Generic aliases
typedef Result<T> = ({T data, String? error});
// Function type aliases (traditional usage)
typedef Validator<T> = String? Function(T value);
// Using type aliases
void processJson(JsonMap json) {
print('Processing JSON: $json');
print('Number of keys: ${json.length}');
}
void validateAndPrint<T>(T value, Validator<T> validator) {
var error = validator(value);
if (error != null) {
print('Validation failed: $error');
} else {
print('Validation passed: $value');
}
}
void main() {
// Using the JsonMap alias
JsonMap userData = {
'name': 'example',
'age': 10,
'isVip': true,
};
processJson(userData);
// Using the Result alias
Result<String> successResult = (data: 'Operation successful', error: null);
Result<String> errorResult = (data: '', error: 'Network connection timed out');
print('Success: ${successResult.data}');
print('Failure: ${errorResult.error}');
// Using the generic Validator
Validator<String> nameValidator = (value) {
if (value.isEmpty) return 'Name cannot be empty';
if (value.length < 3) return 'Name must be at least 3 characters';
return null; // null means validation passed
};
validateAndPrint('EXAMPLE', nameValidator);
validateAndPrint('AB', nameValidator);
}
处理 JSON: {name: example, age: 10, isVip: true}
键的数量: 3
Success: 操作成功
失败: 网络连接超时
验证通过: EXAMPLE
验证失败: 名称至少 3 个字符
Dart 3.0's type aliases greatly reduce verbose type declarations, making code more concise and readable.
other extensionsType aliases (typedef) and the types themselves are completely equivalent at runtime; they are only compile-time "nicknames." This means you cannot use typedef to distinguish between two types that are structurally identical but semantically different—they are treated as the same type.