ГлавнаяБлогDart 3.13 и BlocSignal: код без шаблонов
Алгоритмы

Dart 3.13 и BlocSignal: код без шаблонов

Узнайте, как Dart 3.13 и BlocSignal устраняют шаблонный код в управлении состоянием. Оптимизируйте разработку уже сегодня!

Al
Редакция Algolitalgolit.ru
8 мин чтения14 августа 2026 г.

Dart 3.13 и BlocSignal: пишем код без шаблонов

Годами одним из главных недостатков паттерна BLoC считался шаблонный код: объявление классов событий, иерархий состояний, параметров конструкторов, приватных полей, супер-инициализаторов и реестров обработчиков. На всё это уходили десятки строк до того, как вы обработаете хоть одно реальное действие пользователя. С выходом Dart 3.13 всё меняется: новые возможности языка в сочетании с библиотекой BlocSignal позволяют писать ультра-краткий, полностью типобезопасный код без бойлерплейта. В этой статье разберём, как это работает.

Основные конструкторы в Dart 3.13: меньше кода, больше смысла

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: просто и элегантно

В 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()));
    }
  }
}

Никаких повторных объявлений полей, дублирования имён параметров. Зависимости сразу доступны во всех методах.

Регистрация обработчиков через блок this в BlocSignal

Одна из самых мощных функций 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 настраивает конвейер обработки событий.

Реактивное связывание с createEffect в BlocSignal

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);
}

Это делает тестовые сценарии, начальные значения и конфигурации по умолчанию краткими и читаемыми.

Как включить Dart 3.13 в проекте

Чтобы воспользоваться новыми возможностями, выполните два шага:

  1. Установите SDK-ограничение в pubspec.yaml:
    environment:
      sdk: ^3.13.0
    
    dependencies:
      bloc_signals: ^1.0.0
      bloc_signals_flutter: ^1.0.0
  2. Включите правила линтера Dart 3.13 в analysis_options.yaml:
    include: 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 даёт:

  • Синхронные обновления за 0 мс: распространение состояний происходит в текущем кадре без задержек микротасков.
  • Минимум церемоний: заголовки классов объявляют поля и супер-инициализаторы одновременно.
  • Эффективность графа сигналов: автоматическая дедупликация через == и тонкая перестройка UI.
  • Строгость стандартного BLoC: чистая диспетчеризация событий, переходы состояний и наблюдаемость через OpenTelemetry.

Практический вывод: что делать прямо сейчас

Не ждите — начните использовать основные конструкторы в своих классах событий, состояний и кубитов уже сегодня. Обновите SDK до Dart 3.13, добавьте зависимости bloc_signals и включите рекомендованные правила линтера. Вы сразу заметите, как сократится объём кода, а читаемость и поддерживаемость возрастут. Если у вас есть legacy-код с шаблонными классами — постепенно рефакторите его, используя новые возможности. Поделитесь в комментариях, какие паттерны у вас остались классическими, и посмотрим, сколько кода можно сэкономить вместе с Dart 3.13 и BlocSignal!

#Dart 3.13#BlocSignal#управление состоянием#основные конструкторы#шаблонный код
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →