State: что это такое, управление состоянием и принцип работы

Автор: IT Sectr Опубликовано: 2026-07-01 Время чтения: 9 мин

State — центральный объект управления данными во Flutter, ассоциированный с StatefulWidget и отвечающий за хранение изменяемой информации и построение интерфейса. По данным официальной документации Flutter (Flutter.dev, 2026), State существует на протяжении всего жизненного цикла виджета и переживает его перестроения, обеспечивая консистентность данных между обновлениями UI. В отличие от самого виджета, State может изменять свои поля и инициировать перестроение через вызов setState.

Главное

  • State — объект, хранящий изменяемые данные StatefulWidget и управляющий его перестроением через setState
  • Жизненный цикл — State проходит через initState, didChangeDependencies, build, didUpdateWidget и dispose, каждый этап с чётким назначением
  • mounted — флаг, указывающий, что State всё ещё находится в дереве виджетов и может безопасно вызывать setState
  • widget — ссылка на связанный StatefulWidget, доступная через свойство State для чтения параметров родителя
  • Изоляция — State изолирован от других State; для обмена данными используются InheritedWidget или внешние инструменты управления состоянием

Что такое State во Flutter?

State — это объект в архитектуре Flutter, который хранит изменяемые данные StatefulWidget и определяет, как эти данные отображаются в интерфейсе. Каждый StatefulWidget при встраивании в дерево создаёт ровно один объект State через метод createState. State существует независимо от виджета: если родитель перестраивает StatefulWidget с новыми параметрами, State остаётся прежним и получает обновлённый виджет через свойство widget.

По данным Flutter Architectural Overview (Google, 2026), разделение Widget и State — сознательное архитектурное решение, позволяющее фреймворку переиспользовать элементы дерева. Виджет (лёгкое описание) может быть создан и уничтожен многократно, но State (тяжёлый объект с данными) остаётся в памяти, пока элемент находится в дереве. Это предотвращает потерю данных при частых перестроениях родительских виджетов.

State реализует интерфейс StatefulWidget через дженерик: class _MyState extends State<MyWidget>. Дженерик связывает State с конкретным типом StatefulWidget, обеспечивая типобезопасный доступ к его полям через свойство widget.

Где хранится State?

Объект State хранится в StatefulElement — промежуточном слое между Widget и RenderObject. StatefulElement создаёт State через createState, сохраняет ссылку на него и передаёт State в качестве владельца. Element уничтожается, только когда виджет удаляется из дерева — до этого момента State живёт в памяти.

Жизненный цикл State

Жизненный цикл State детерминирован и состоит из строгой последовательности вызовов. Понимание этой последовательности — основа корректной работы с ресурсами и предотвращения утечек памяти.

initState — инициализация

initState вызывается первым при создании State. В этом методе инициализируются контроллеры, подписки на потоки данных, таймеры и начальные значения полей. Обязателен вызов super.initState() в первой строке. На этапе initState дерево виджетов ещё не полностью смонтировано, поэтому методы вроде MediaQuery.of(context) могут работать некорректно.

didChangeDependencies

didChangeDependencies вызывается после initState и при каждом изменении InheritedWidget-зависимостей. Именно здесь, а не в initState, следует вызывать MediaQuery.of(context) или Theme.of(context), так как к этому моменту дерево уже смонтировано. Этот метод также вызывается, если виджет перемещается в другой контекст, где InheritedWidget предоставляет другие значения.

build — построение UI

build — основной метод State, возвращающий дерево виджетов. Вызывается после initState, после didChangeDependencies и после каждого setState. Метод build не должен иметь побочных эффектов — он только описывает интерфейс на основе текущих значений полей State.

didUpdateWidget

didUpdateWidget вызывается, когда родитель перестраивает StatefulWidget с новыми параметрами. State получает доступ к старому виджету через oldWidget и может сравнить его с новым. Если параметры изменились, можно обновить состояние, загрузить новые данные или перезапустить анимацию.

dispose — освобождение ресурсов

dispose — завершающий метод, в котором освобождаются все ресурсы: контроллеры, подписки, таймеры. После dispose State помечается как мёртвый: mounted возвращает false, вызов setState выбрасывает исключение. Обязателен вызов super.dispose() в последней строке метода.

МетодКогда вызываетсяОбязательный super
initStateПри создании StateДа, в первой строке
didChangeDependenciesПосле initState и при смене InheritedWidgetДа
buildПосле initState, didChangeDependencies, setStateНет
didUpdateWidgetПри новом виджете от родителяДа
setStateПо вызову разработчикаНет
disposeПри удалении из дереваДа, в последней строке

Как работает State?

Механизм работы State основан на трёх ключевых принципах: ассоциация с Element, реактивность через setState и доступ к родителю через свойство widget. Когда Flutter строит дерево элементов и встречает StatefulElement, он вызывает createState связанного виджета. Созданный State сохраняется в элементе и существует до тех пор, пока элемент не будет удалён.

При вызове setState State помечает себя как «грязный» (dirty) и планирует перестроение на следующий кадр. Важно: setState не вызывает build немедленно — он лишь регистрирует необходимость перестроения. Flutter собирает все dirty-элементы за текущий кадр и перестраивает их пакетно, что оптимизирует производительность. После вызова build State возвращается в состояние «чистый» (clean).

Свойство widget позволяет State читать параметры, переданные в конструктор StatefulWidget. Поскольку StatefulWidget иммутабелен (как StatelessWidget), его поля не меняются — при изменении параметров родитель создаёт новый виджет, а State получает его через didUpdateWidget. Это гарантирует, что State всегда работает с актуальными данными родителя.

Примеры кода на Dart

Базовый пример State с полем, изменяемым по таймеру. Демонстрирует initState, setState и dispose:

dart
class _TimerWidgetState extends State<TimerWidget> {
  int _seconds = 0;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(
      const Duration(seconds: 1),
      (_) => setState(() => _seconds++),
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('$_seconds seconds elapsed');
  }
}

Пример с использованием widget свойства для доступа к параметрам родителя и реагирования на их изменения через didUpdateWidget:

dart
class _GreetingState extends State<GreetingWidget> {
  String _displayName = '';

  @override
  void initState() {
    super.initState();
    _displayName = _formatName(widget.name);
  }

  @override
  void didUpdateWidget(GreetingWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.name != oldWidget.name) {
      setState(() {
        _displayName = _formatName(widget.name);
      });
    }
  }

  String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $_displayName!');
  }
}

Во втором примере State отслеживает изменение входного параметра name и переформатирует отображение только при реальном изменении. Без проверки widget.name != oldWidget.name метод вызывался бы при каждом перестроении родителя, даже если имя не изменилось — это лишняя работа для фреймворка.

State vs StatefulWidget

State и StatefulWidget — два разных класса в архитектуре Flutter, выполняющие разные роли. StatefulWidget — это лёгкая иммутабельная обёртка, которая описывает конфигурацию виджета и создаёт State. State — это тяжёлый объект, который хранит изменяемые данные, управляет подписками и строит UI. Такое разделение позволяет Flutter уничтожать и создавать виджеты без потери состояния.

Все поля StatefulWidget должны быть final и задаваться в конструкторе — они не меняются после создания. State, напротив, может изменять свои поля в любой момент, но все изменения должны предваряться вызовом setState, чтобы Flutter узнал о необходимости перестроения. Это ключевое отличие: StatefulWidget — это «что показать», State — «как показать и какие данные использовать».

По данным Flutter source code analysis (Flutter SDK, 2026), StatefulWidget содержит всего одно обязательное поле — createState, в то время как State имеет доступ к BuildContext, может подписываться на потоки, управлять анимациями и контроллерами. Рекомендуется держать StatefulWidget максимально простым, перенося всю логику в State.

Почему StatefulWidget не может быть State?

Разделение Widget и State — архитектурное решение, обеспечивающее иммутабельность конфигурации. Если бы StatefulWidget сам хранил состояние, при каждом перестроении родителя состояние бы терялось. Вынося состояние в отдельный объект, Flutter гарантирует, что данные переживают перестроения, а виджеты остаются лёгкими и сравнимыми.

Управление состоянием между виджетами

Объект State изолирован — он не имеет прямого доступа к State других виджетов. Для обмена данными между виджетами используются InheritedWidget или внешние инструменты управления состоянием: Provider, Riverpod, Bloc, Redux. Каждый подход решает задачу по-своему: InheritedWidget работает через дерево виджетов, Provider — через DI-контейнер, Bloc — через потоки событий.

Выбор инструмента зависит от масштаба проекта. Для маленького приложения достаточно InheritedWidget и локального State. Для среднего и крупного проекта рекомендуется Riverpod или Bloc — они обеспечивают тестируемость, предсказуемость и отделение логики от UI. State при этом используется только для локальных данных виджета (фокус, скролл, анимация).

По данным Flutter Community Survey 2025 (Flutter Foundation, декабрь 2025), Riverpod является самым популярным решением для управления состоянием в новых проектах (38%), за ним следуют Bloc (31%) и Provider (22%). Все три инструмента совместимы со State и не требуют отказа от стандартного жизненного цикла.

Локальное vs глобальное состояние

  • Локальное состояние — в State конкретного виджета (позиция скролла, состояние фокуса)
  • Глобальное состояние — во внешнем хранилище (данные пользователя, настройки, кэш)
  • Правило: если данные используются только одним виджетом — храните в State
  • Если данными пользуются 2+ виджета — выносите в Riverpod/Bloc/Provider

Типовые ошибки

Первая ошибка — забыть проверить mounted перед setState в асинхронном колбэке. Когда виджет удалён из дерева (например, пользователь ушёл с экрана), но асинхронная операция (HTTP-запрос) ещё выполняется, после её завершения State уже мёртв. Вызов setState в мёртвом State выбрасывает исключение. Проверка if (mounted) setState(...) решает проблему.

Вторая ошибка — инициализация InheritedWidget-зависимостей в initState вместо didChangeDependencies. В initState контекст ещё не смонтирован, поэтому MediaQuery.of(context) выбросит исключение. Все зависимости от InheritedWidget должны настраиваться в didChangeDependencies или в build.

Третья ошибка — мутация полей без вызова setState. Если разработчик изменяет поле State без setState, Flutter не узнает об изменении и UI не обновится. Например: _list.add(item) без последующего setState((){}) изменит список, но экран останется прежним.

Проверка mounted перед setState

Паттерн безопасности для асинхронных операций в State:

dart
Future<void> _fetchData() async {
  final data = await ApiService.fetch();
  if (mounted) {
    setState(() => _data = data);
  }
}

Проверка mounted гарантирует, что setState вызывается только для живого State, предотвращая исключение «setState called after dispose».

Часто задаваемые вопросы

Чем State отличается от StatefulWidget?

StatefulWidget — иммутабельная конфигурация виджета, а State — изменяемый объект, хранящий данные и управляющий жизненным циклом. Widget может быть пересоздан, State — нет. StatefulWidget создаёт State через createState.

Сколько объектов State создаётся для одного StatefulWidget?

Ровно один. Метод createState вызывается однократно при первом встраивании StatefulWidget в дерево. Даже если родитель перестраивается многократно, объект State остаётся тем же самым, пока не изменится тип или Key виджета.

Что такое mounted в State?

mounted — булев флаг, показывающий, находится ли State в дереве виджетов. После вызова dispose mounted становится false. Используется для проверки перед setState в асинхронных колбэках, чтобы избежать исключения.

Можно ли использовать State без StatefulWidget?

Нет. State всегда привязан к конкретному StatefulWidget через дженерик: State<T extends StatefulWidget>. Создать State напрямую, без ассоциации с виджетом, архитектурно невозможно.

Что произойдёт при вызове setState в dispose?

Выбросится исключение «setState called after dispose». После вызова dispose State считается мёртвым, и любые попытки перестроить UI через setState запрещены. Решение — проверять mounted перед каждым setState.

Итоги

  • State — объект управления данными StatefulWidget, хранящий изменяемые поля и инициирующий перестроение UI через setState
  • Жизненный цикл включает обязательные методы initState, didChangeDependencies, build, didUpdateWidget и dispose, каждый со своим назначением
  • mounted — критический флаг безопасности, предотвращающий вызов setState после удаления виджета из дерева
  • widget — свойство State для доступа к параметрам связанного StatefulWidget, обновляемое через didUpdateWidget
  • Изоляция — State не имеет доступа к другим State; межвиджетное взаимодействие реализуется через InheritedWidget или внешние инструменты
  • setState — не вызывает build немедленно, а лишь помечает State как грязный для перестроения в следующем кадре
  • Правило — используйте State для локальных данных виджета; глобальное состояние выносите во внешние слои (Riverpod, Bloc)

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также