Узнайте, как Dart 3.13 и BlocSignal устраняют шаблонный код в управлении состоянием. Оптимизируйте разработку уже сегодня!
Годами одним из главных недостатков паттерна BLoC считался шаблонный код: объявление классов событий, иерархий состояний, параметров конструкторов, приватных полей, супер-инициализаторов и реестров обработчиков. На всё это уходили десятки строк до того, как вы обработаете хоть одно реальное действие пользователя. С выходом Dart 3.13 всё меняется: новые возможности языка в сочетании с библиотекой BlocSignal позволяют писать ультра-краткий, полностью типобезопасный код без бойлерплейта. В этой статье разберём, как это работает.
Dart 3.13 вводит основные конструкторы (primary constructors), которые позволяют объявлять поля и параметры прямо в заголовке класса. Это радикально сокращает шаблонный код при определении событий и состояний.
В классическом BLoC для каждого типа события или состояния нужно было писать повторяющиеся конструкторы и поля. Сравните:
До Dart 3.13:
sealed class UserEvent {}
class UserFetchRequested extends UserEvent {
final String userId;
UserFetchRequested(this.userId);
}
class UserUpdated extends UserEvent {
final String name;
final int age;
UserUpdated({required this.name, required this.age});
}
class UserLoggedOut extends UserEvent {}С Dart 3.13:
sealed class UserEvent {}
class UserFetchRequested(final String userId) extends UserEvent;
class UserUpdated({required final String name, required final int age}) extends UserEvent;
class UserLoggedOut() extends UserEvent;Вся иерархия событий теперь объявляется в несколько чистых строк, сохраняя типобезопасность и полноту проверок в switch-выражениях.
В CubitSignal обычно внедряются репозитории, API-клиенты или аналитические трекеры. Раньше приходилось объявлять каждое поле, принимать аргументы конструктора и передавать начальное состояние в super. Теперь всё это делается прямо в заголовке класса.
До Dart 3.13:
class UserCubit extends CubitSignal<UserState> {
final UserRepository _repository;
final AnalyticsService _analytics;
UserCubit({
required UserRepository repository,
required AnalyticsService analytics,
UserState initial = const UserInitial(),
}) : _repository = repository,
_analytics = analytics,
super(initialState: initial);
Future<void> loadUser(String id) async {
emit(const UserLoading());
try {
final user = await _repository.fetchUser(id);
_analytics.track('user_loaded', {'id': id});
emit(UserSuccess(user));
} catch (e, st) {
onError(e, st);
emit(UserError(e.toString()));
}
}
}С Dart 3.13:
class UserCubit(
final UserRepository repository,
final AnalyticsService analytics, {
final UserState initial = const UserInitial(),
}) extends CubitSignal<UserState>(initialState: initial) {
Future<void> loadUser(String id) async {
emit(const UserLoading());
try {
final user = await repository.fetchUser(id);
analytics.track('user_loaded', {'id': id});
emit(UserSuccess(user));
} catch (e, st) {
onError(e, st);
emit(UserError(e.toString()));
}
}
}Никаких повторных объявлений полей, дублирования имён параметров. Зависимости сразу доступны во всех методах.
Одна из самых мощных функций Dart 3.13 — синтаксис тела конструктора this. При использовании основных конструкторов логика тела конструктора (например, регистрация обработчиков событий через on<E>() или проверка предусловий) помещается в блок this { ... } в теле класса.
class SearchBloc(
final SearchRepository repository, {
final SearchState initial = const SearchInitial(),
}) extends BlocSignal<SearchEvent, SearchState>(initialState: initial) {
// Тело основного конструктора Dart 3.13
this {
on<SearchQueryChanged>((event, emit) async {
if (event.query.trim().isEmpty) return emit(const SearchEmpty());
emit(const SearchLoading());
final results = await repository.search(event.query);
emit(SearchSuccess(results));
}, transformer: restartable()); // Конкурентность событий без потоков!
}
}Заголовок класса чётко объявляет контракт, а блок this настраивает конвейер обработки событий.
BlocSignal включает createEffect, который автоматически отслеживает зависимости сигналов и управляет очисткой при удалении контейнера. Благодаря параметрам основного конструктора, производные кубиты могут синхронно подключаться к вышестоящим контейнерам состояния прямо в блоке this:
class CartSummaryCubit(final CartBloc cartBloc)
extends CubitSignal<CartSummary>(initialState: const CartSummary.zero()) {
this {
// Автоматически реагирует на сигналы cartBloc.state синхронно:
createEffect(() {
final items = cartBloc.state.value.items;
final total = items.fold<double>(0, (sum, item) => sum + item.price);
emit(CartSummary(count: items.length, total: total));
});
}
}Dart 3.13 также вводит сокращённые конструкторы: можно определить вторичные именованные конструкторы через new name(), не повторяя имя класса:
class CounterCubit(var int count) extends CubitSignal<int>(initialState: count) {
// Сокращённые именованные конструкторы:
new zero() : this(0);
new seeded(int initial) : this(initial);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}Это делает тестовые сценарии, начальные значения и конфигурации по умолчанию краткими и читаемыми.
Чтобы воспользоваться новыми возможностями, выполните два шага:
environment:
sdk: ^3.13.0
dependencies:
bloc_signals: ^1.0.0
bloc_signals_flutter: ^1.0.0include: package:very_good_analysis/analysis_options.yaml
linter:
rules:
- use_primary_constructors
- use_declaring_parameters
- unnecessary_type_name_in_constructor
- unnecessary_primary_constructor_bodyСочетание Dart 3.13 и BlocSignal даёт:
== и тонкая перестройка UI.Не ждите — начните использовать основные конструкторы в своих классах событий, состояний и кубитов уже сегодня. Обновите SDK до Dart 3.13, добавьте зависимости bloc_signals и включите рекомендованные правила линтера. Вы сразу заметите, как сократится объём кода, а читаемость и поддерживаемость возрастут. Если у вас есть legacy-код с шаблонными классами — постепенно рефакторите его, используя новые возможности. Поделитесь в комментариях, какие паттерны у вас остались классическими, и посмотрим, сколько кода можно сэкономить вместе с Dart 3.13 и BlocSignal!
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →