Records, patterns и sealed-классы Dart 3
Главные новинки Dart 3 — records, паттерны и sealed-классы — делают код короче, безопаснее и выразительнее.
Суть: records возвращают несколько значений без отдельного класса, паттерны (patterns) разбирают данные на части одной строкой, а sealed-классы дают компилятору проверять, что вы обработали все варианты.
Паттерны работают не только в присваивании, но и в условиях через конструкцию if-case. Запись if (json case {'name': String name}) { ... } одновременно проверяет, что в Map есть ключ name со строковым значением, и сразу извлекает его в переменную. Это резко сокращает разбор данных из сети, где раньше требовалась цепочка проверок типов и приведений. Паттерны делают такой код и короче, и безопаснее.
Раньше, чтобы вернуть из функции два значения, приходилось городить отдельный класс или возвращать список и помнить, что под каким индексом. Dart 3 решает это records — лёгкими кортежами с именованными или позиционными полями. А паттерны позволяют тут же разложить record на переменные.
// record как тип возврата: координаты
(int, int) findPosition() => (10, 20);
// деструктуризация через pattern:
final (x, y) = findPosition();
print('x=$x y=$y'); // x=10 y=20
// record с именованными полями:
({String name, int age}) getUser() => (name: 'Аня', age: 20);
final user = getUser();
print(user.name); // Аня
Круглые скобки (10, 20) создают record, а final (x, y) = ... — это паттерн, который мгновенно раскладывает его по переменным. Никаких индексов и классов ради двух чисел.
Как работают sealed-классы под капотом
Sealed-класс — это запечатанный класс: все его наследники должны лежать в том же файле. Это даёт компилятору суперсилу: он знает все возможные варианты и в switch требует обработать каждый. Если вы добавите новый подтип и забудете его в switch — код не скомпилируется. Это идеально для моделирования состояний экрана: «загрузка», «успех», «ошибка».
sealed class Result
/ | \
Loading Success Failure все подтипы известны
компилятору
switch (result) {
Loading() => spinner,
Success(:final data) => show(data), деструктуризация в switch
Failure(:final msg) => error(msg),
}
// забыли вариант? -> ОШИБКА компиляции (исчерпывающесть)
sealed class Result {}
class Loading extends Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String msg; Failure(this.msg); }
String render(Result r) => switch (r) {
Loading() => 'Загрузка...',
Success(:final data) => 'Данные: $data',
Failure(:final msg) => 'Ошибка: $msg',
};
Запись Success(:final data) — это паттерн с извлечением поля: он одновременно проверяет тип и достаёт значение data. Так состояние экрана превращается в интерфейс безопасно и без единого if-is.
# Аналог sealed + switch на Python: реестр состояний экрана
def render(result):
kind = result['kind']
if kind == 'loading':
return 'Загрузка...'
elif kind == 'success':
return f"Данные: {result['data']}"
elif kind == 'failure':
return f"Ошибка: {result['msg']}"
else:
raise ValueError('необработанный вариант: ' + kind)
print(render({'kind': 'loading'}))
print(render({'kind': 'success', 'data': '42 поста'}))
print(render({'kind': 'failure', 'msg': 'нет сети'}))
Частые ошибки
- Класть наследников sealed-класса в другой файл — это запрещено, вся защита держится на том, что варианты собраны вместе.
- Добавлять
defaultв switch по sealed-классу без нужды — он гасит проверку исчерпывающести. - Путать record и класс. Record — лёгкий безымянный кортеж; для богатой логики всё же нужен класс.
Best practices
- Используйте records для функций, которым нужно вернуть пару-тройку значений, — без церемоний с классами.
- Моделируйте состояния (loading/success/error) sealed-классами — это устранит целый класс багов «забыл обработать вариант».
- Применяйте паттерны в
switchдля одновременной проверки типа и извлечения данных.
Связка sealed-классов с паттернами особенно сияет при моделировании результата операции. Вместо булевых флагов isLoading, hasError и отдельного поля данных, которые легко рассинхронизировать, вы описываете ровно три взаимоисключающих состояния — и компилятор не даст вам забыть обработать любое из них. Этот приём, известный как «сделай недопустимые состояния невыразимыми», — одна из главных причин, по которой опытные разработчики так любят Dart 3.
Итог: records, паттерны и sealed-классы — это лицо современного Dart 3. Они особенно сильны в управлении состоянием Flutter-экранов, где важно гарантированно обработать все ситуации. Запомните связку sealed + switch — вернёмся к ней в разделе про состояние.