Dart typedef und Funktionstypen
typedef ist der Typ-Alias-Mechanismus in Dart, mit dem Sie kurze, lesbare Namen für komplexe Typen definieren können.
Dieses Kapitel stellt zwei Verwendungen von typedef vor: den traditionellen Funktionstyp-Alias und den generischen Typ-Alias, der in Dart 3.0 eingeführt wurde.
typedef Funktionstyp-Alias
Wenn ein Funktionstyp häufig verwendet wird, kann typedef ihm einen klaren Namen geben.
Dies ist besonders nützlich für Szenarien wie Callback-Funktionen und Event-Handler.
Beispiel
// Stellt dar: eine Funktion, die zwei int-Parameter annimmt und int zurückgibt.
typedef IntOperation = int Function(int a, int b);
// Definieren Sie einen Callback-Typ
// Stellt dar: eine Funktion, die eine String-Nachricht empfängt und keinen Wert zurückgibt
typedef MessageCallback = void Function(String message);
// Verwenden Sie typedef als Parametertyp
int performOperation(int x, int y, IntOperation operation) {
return operation(x, y);
}
void logMessage(String msg, MessageCallback callback) {
print('Ausgabe der Nachricht wird vorbereitet...');
callback('[BEISPIEL] $msg');
}
void main() {
// Übergeben Sie eine Funktion, die der IntOperation-Signatur entspricht
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)}');
// Du kannst auch direkt eine anonyme Funktion übergeben
int result = performOperation(20, 4, (a, b) => a ~/ b);
print('20 ÷ 4 = $result');
// Verwende MessageCallback
logMessage('Operation abgeschlossen', (msg) {
print('Empfangene Nachricht: $msg');
});
}
10 + 5 = 15 10 × 5 = 50 20 ÷ 4 = 5 准备输出消息... 收到消息: [EXAMPLE] 操作完成
Ohne typedef musst du an jeder Parameterposition wiederholt ausführliche Funktionstyp-Signaturen schreiben.
Mit typedef werden Typdeklarationen klar und einheitlich, und Änderungen müssen nur an einer Stelle vorgenommen werden.
typedef ist nur ein Alias für einen Typ; es erzeugt keinen neuen Typ. IntOperation und int Function(int, int) sind im Typsystem vollständig äquivalent.
Funktionstyp als Parameter
In Dart sind Funktionen First-Class-Bürger und können wie gewöhnliche Werte weitergegeben werden.
Beispiel
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));
}
}
// Eine Funktion höherer Ordnung, die eine Funktion zurückgibt
int Function(int) makeMultiplier(int factor) {
// Die zurückgegebene Closure fängt factor ein
return (int n) => n * factor;
}
void main() {
var scores = [55, 78, 92, 60, 45, 88];
print('--- Bestehensgrenze ---');
processNumbers(
scores,
(n) => n >= 60, // Filterbedingung
(n) => 'BEISPIEL Punktzahl: $n Punkte', // Formatierung
);
print('--- Bestpunktzahlen (> 80) ---');
processNumbers(
scores,
(n) => n > 80,
(n) => 'Bestpunktzahl: $n Punkte',
);
// Funktion als Rückgabewert
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-Muster
Das Callback-Muster ist das häufigste Anwendungsszenario für Funktionstypen.
Es delegiert die Kontrolle darüber, "was zu tun ist", an den Aufrufer und macht den Code flexibler und wiederverwendbarer.
Beispiel
typedef ResultCallback<T> = void Function(T result);
typedef ErrorCallback = void Function(String error);
// Asynchrone Operation simulieren
void fetchUserData(
String userId, {
required ResultCallback<Map<String, dynamic>> onSuccess,
required ErrorCallback onError,
}) {
// Netzwerkanfrage simulieren
print('Benutzerdaten werden abgerufen...');
// Erfolg/Fehler simulieren
if (userId == 'example') {
var data = {
'id': 'example',
'name': 'BEISPIEL Benutzer',
'level': 'VIP',
};
onSuccess(data);
} else {
onError('Benutzer $ existiert nicht');
}
}
void main() {
// Callback verwenden, um das Ergebnis zu verarbeiten
fetchUserData(
'example',
onSuccess: (data) {
print('Abruf erfolgreich!');
print('Benutzername: ${data['name']}');
print('Level: ${data['level']}');
},
onError: (error) {
print('Abruf fehlgeschlagen: $error');
},
);
print('---');
// Testszenario für einen Fehlerfall
fetchUserData(
'unknown',
onSuccess: (data) {
print('Abruf erfolgreich');
},
onError: (error) {
print('Abruf fehlgeschlagen: $error');
},
);
}
正在获取用户数据... 获取成功! 用户名: EXAMPLE 用户 等级: VIP --- 正在获取用户数据... 获取失败: 用户 unknown 不存在
Häufige Anwendungsszenarien für das Callback-Muster:
| Szenarien | Callback-Typ-Beispiel |
|---|---|
| Netzwerkanfrage | onSuccess(data) / onError(error) |
| UI-Ereignis | onTap() / onLongPress() |
| Datenkonvertierung | Callbacks in map(), where(), reduce() |
| Timer | Timer(callback, duration) |
| Animation abgeschlossen | onComplete() |
Obwohl das Callback-Muster flexibel ist, führt es bei der Verarbeitung mehrschichtiger asynchroner Operationen zur „Callback-Hölle“. Dart bietet async/await, um asynchrone Abläufe eleganter zu handhaben, was wir in Kapitel 17 ausführlich behandeln werden.
Dart 3.0 Generischer Typalias
Dart 3.0 erweitert die Möglichkeiten von typedef; jetzt können Aliase für jeden Typ erstellt werden, nicht nur für Funktionstypen.
Beispiel
// Aliase für komplexe Sammlungstypen
typedef JsonMap = Map<String, dynamic>;
typedef UserList = List<Map<String, dynamic>>;
// Generische Aliase
typedef Result<T> = ({T data, String? error});
// Funktionstyp-Aliase (traditionelle Verwendung)
typedef Validator<T> = String? Function(T value);
// Typ-Aliase verwenden
void processJson(JsonMap json) {
print('JSON wird verarbeitet: $json');
print('Anzahl der Schlüssel: ${json.length}');
}
void validateAndPrint<T>(T value, Validator<T> validator) {
var error = validator(value);
if (error != null) {
print('Validierung fehlgeschlagen: $error');
} else {
print('Validierung bestanden: $value');
}
}
void main() {
// Verwendung des JsonMap-Alias
JsonMap userData = {
'name': 'example',
'age': 10,
'isVip': true,
};
processJson(userData);
// Verwendung des Result-Alias
Result<String> successResult = (data: 'Operation erfolgreich', error: null);
Result<String> errorResult = (data: '', error: 'Zeitüberschreitung der Netzwerkverbindung');
print('Erfolg: ${successResult.data}');
print('Fehler: ${errorResult.error}');
// Verwendung des generischen Validators
Validator<String> nameValidator = (value) {
if (value.isEmpty) return 'Name darf nicht leer sein';
if (value.length < 3) return 'Name muss mindestens 3 Zeichen lang sein';
return null; // null bedeutet, dass die Validierung bestanden wurde
};
validateAndPrint('EXAMPLE', nameValidator);
validateAndPrint('AB', nameValidator);
}
处理 JSON: {name: example, age: 10, isVip: true}
键的数量: 3
Success: 操作成功
失败: 网络连接超时
验证通过: EXAMPLE
验证失败: 名称至少 3 个字符
Die Typaliase von Dart 3.0 reduzieren ausführliche Typdeklarationen erheblich und machen den Code prägnanter und lesbarer.
Weitere ErweiterungenTypaliase (typedef) und die Typen selbst sind zur Laufzeit vollständig äquivalent; sie sind nur "Spitznamen" zur Kompilierzeit. Das bedeutet, dass Sie typedef nicht verwenden können, um zwischen zwei Typen zu unterscheiden, die strukturell identisch, aber semantisch unterschiedlich sind – sie werden als derselbe Typ behandelt.