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

// Definiere einen Funktionstyp-Alias
// 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

// Verwende Funktionstypen direkt, um Parameter zu deklarieren (ohne typedef)
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

// Callback-Typ definieren
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:

SzenarienCallback-Typ-Beispiel
NetzwerkanfrageonSuccess(data) / onError(error)
UI-EreignisonTap() / onLongPress()
DatenkonvertierungCallbacks in map(), where(), reduce()
TimerTimer(callback, duration)
Animation abgeschlossenonComplete()

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

// Dart 3.0: typedef kann Aliase für jeden Typ erstellen

// 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.

Typaliase (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.

Weitere Erweiterungen