StatefulWidget: що це, життєвий цикл та принцип роботи

Автор: IT Sectr Опубліковано: 2026-07-01 Час читання: 9 хв

StatefulWidget — віджет Flutter зі змінним станом, що дозволяє UI реагувати на дії користувача, асинхронні події та потоки даних. Згідно з офіційною документацією Flutter (Flutter.dev, 2026), StatefulWidget використовується для всіх інтерактивних елементів застосунку: форм введення, анімацій, прапорців, перемикачів та екранів, що завантажують дані з мережі. На відміну від StatelessWidget, він створює окремий об'єкт State, який зберігається протягом усього життєвого циклу і може бути перебудований без перестворення самого віджета.

Головне

  • StatefulWidget — віджет, який може змінювати свій стан під час роботи, викликаючи перебудову UI через setState
  • Життєвий цикл — StatefulWidget проходить через етапи createState, initState, didChangeDependencies, build, didUpdateWidget, dispose
  • State object — окремий об'єкт, що зберігає стан і існує незалежно від віджета протягом усього його життя
  • setState — єдиний легітимний спосіб повідомити Flutter про необхідність перебудови віджета після зміни даних
  • Продуктивність — надмірне використання StatefulWidget збільшує споживання пам'яті та час рендерингу

Що таке StatefulWidget?

StatefulWidget — це клас Flutter, який може змінювати свій стан у відповідь на дії користувача, системні події або асинхронні операції. На відміну від StatelessWidget, StatefulWidget не рендериться безпосередньо — він створює об'єкт State, який відповідає за рендеринг. Розділення на два класи (Widget і State) дозволяє Flutter перебудовувати UI без перестворення самого віджета, що дає значну перевагу в продуктивності під час частих оновлень.

Архітектура StatefulWidget слідує патерну «розділення змінного та незмінного»: сам віджет залишається незмінним (як StatelessWidget), а весь змінний стан зберігається в окремому об'єкті State. Це дозволяє Flutter перевикористовувати віджети, порівнюючи їх за типом і Key, і при цьому зберігати актуальний стан між перебудовами.

За даними Google (Flutter Architectural Overview, 2026), StatefulWidget оптимальний для сценаріїв, де стан змінюється більше одного разу за час життя віджета: текстові поля, анімації, таймери, потоки даних, асинхронні завантаження. Для одноразової ініціалізації достатньо StatelessWidget.

Коли StatefulWidget необхідний

StatefulWidget обов'язковий, коли віджет повинен реагувати на зовнішні події: натискання кнопки, завершення HTTP-запиту, оновлення даних з бази, підписка на WebSocket. Він також необхідний для віджетів з анімацією, текстових полів з контролерами та компонентів, що керують фокусом. Якщо віджет лише відображає дані і не породжує подій — використовуйте StatelessWidget.

Внутрішній устрій

StatefulWidget складається з двох класів: власне StatefulWidget (легкий, незмінний) і State (важкий, змінний). Фреймворк створює State через метод createState(), який викликається одноразово при вбудовуванні в дерево. State отримує посилання на віджет через властивість widget і може звертатися до його полів у будь-який момент життєвого циклу.

Життєвий цикл StatefulWidget

Життєвий цикл StatefulWidget складається з шести основних етапів, кожен з яких надає перевизначуваний метод для виконання специфічних завдань. Розуміння цих етапів критично важливе для коректної роботи з ресурсами та уникнення витоків пам'яті.

createState

createState — перший метод життєвого циклу, що викликається при вбудовуванні StatefulWidget у дерево. Він повинен повертати новий екземпляр State, асоційований з даним віджетом. Цей метод викликається рівно один раз за весь час життя елемента. Важливо не виконувати тут важких операцій — createState має бути максимально легким.

initState

initState — викликається відразу після створення State, до першої побудови UI. Тут виконуються: ініціалізація контролерів (TextEditingController, AnimationController), підписка на потоки даних (StreamSubscription), налаштування таймерів та початкова ініціалізація полів. За даними Flutter docs (Flutter.dev, 2026), в initState не можна викликати BuildContext.of() — дерево ще не повністю змонтовано.

didChangeDependencies

didChangeDependencies — викликається після initState і кожного разу, коли змінюються залежності InheritedWidget. Це відповідне місце для виклику MediaQuery.of(context) або підписки на Theme — значення, які можуть змінитися під час роботи застосунку. Якщо віджет використовує InheritedWidget, логіка ініціалізації повинна бути тут, а не в initState.

build і didUpdateWidget

build — основний метод, що повертає дерево віджетів. Викликається після initState, після didChangeDependencies і після кожного setState. didUpdateWidget викликається, коли батько перебудовується і передає StatefulWidget з новими параметрами. Тут можна порівняти старі та нові поля віджета і, при необхідності, оновити стан.

dispose

dispose — завершальний етап життєвого циклу. Тут звільняються всі ресурси: відписуються від потоків, видаляються контролери, скасовуються таймери. Невиклик dispose призводить до витоків пам'яті. Після dispose State вважається мертвим — виклик setState всередині нього викидає виняток.

Як працює StatefulWidget?

Механізм роботи StatefulWidget заснований на узгодженій роботі трьох сутностей: Widget (легкий опис), Element (проміжний шар) і State (сховище даних). Коли Flutter зустрічає StatefulWidget в описі, він створює StatefulElement, який викликає createState і зберігає посилання на об'єкт State. При перебудові батька Flutter порівнює новий віджет з поточним Element — якщо тип і Key збігаються, Element оновлюється, а State залишається незмінним.

Стан змінюється тільки через виклик setState, який повідомляє фреймворку про необхідність перебудови. Важливо розуміти: setState не змінює стан автоматично — він лише маркує віджет як «брудний». Розробник самостійно оновлює поля State у колбеку, переданому в setState. Після завершення колбека Flutter викликає build і оновлює UI.

За даними команди Dart/Flutter (Dart Language Specification, 2026), таке розділення гарантує, що всі зміни стану відбуваються синхронно до виклику build, виключаючи ситуацію, коли UI відображає частково оновлені дані. Це ключовий механізм консистентності інтерфейсу у Flutter.

Приклади коду на Dart

Розглянемо простий StatefulWidget — лічильник натискань на кнопку. Він демонструє базовий патерн: створення State, ініціалізація поля в initState, зміна через setState:

dart
class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

Приклад з асинхронним завантаженням даних і управлінням життєвим циклом. StatefulWidget завантажує дані з мережі і відображає стан завантаження:

dart
class UserProfilePage extends StatefulWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  State<UserProfilePage> createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  UserModel? _user;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUser();
  }

  Future<void> _loadUser() async {
    final user = await UserService.fetchUser(widget.userId);
    setState(() {
      _user = user;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const CircularProgressIndicator();
    return Text('Hello, ${_user!.name}');
  }
}

У другому прикладі важливо зазначити: initState запускає асинхронну операцію, але сам метод не є асинхронним. Асинхронність реалізується через async/await всередині окремого методу _loadUser, який оновлює стан через setState після завершення запиту. Такий підхід гарантує, що віджет коректно відобразить індикатор завантаження до отримання даних.

StatefulWidget vs StatelessWidget

Вибір між StatefulWidget і StatelessWidget — не лише питання наявності стану. StatefulWidget надає повноцінний життєвий цикл з методами initState, didChangeDependencies, didUpdateWidget і dispose, що необхідно для роботи з контролерами, анімаціями та потоками. StatelessWidget, у свою чергу, не має цих методів і завжди легший для фреймворку.

Рекомендація команди Flutter (Flutter docs, 2026) — мінімізувати кількість StatefulWidget у застосунку, піднімаючи стан вгору по дереву (State Hoisting) або використовуючи рішення для керування станом (Riverpod, Bloc, Provider). Кожен StatefulWidget створює об'єкт State, який живе до видалення елемента — чим більше таких віджетів, тим вище навантаження на пам'ять.

КритерійStatefulWidgetStatelessWidget
СтанЗміннийНезмінний
Життєвий цикл6 етапівТільки build
Об'єкт StateСтворюється окремоНе потрібен
setStateДоступнийНедоступний
ПідпискиinitState/disposeНе підтримуються
const конструкторОбмеженийПовністю підтримується
Споживання пам'ятіВищеНижче

Продуктивність та оптимізація

StatefulWidget потребує більше ресурсів, ніж StatelessWidget, через необхідність створювати та підтримувати об'єкт State. Однак правильне використання StatefulWidget не призводить до проблем продуктивності, якщо дотримуватися кількох правил. По-перше, уникайте глибокої вкладеності StatefulWidget — кожен рівень додає накладні витрати на обхід дерева. По-друге, розбивайте складні StatefulWidget на кілька простих, кожен з яких відповідає за свою частину стану.

За даними дослідження Flutter Performance (Flutter.dev, лютий 2026), найчастішою причиною просідання FPS є виклик setState у батьківському віджеті, який перебудовує всіх нащадків, включаючи StatelessWidget, що не змінили своє відображення. Рішення — виносити змінну частину UI в окремий StatefulWidget, щоб setState перебудовував лише мінімум необхідних віджетів.

Використання const всередині State — ще один важливий прийом. Якщо дочірні віджети оголошені як const, Flutter не буде перебудовувати їх при виклику setState у батьку. Це знижує навантаження на фреймворк і зменшує час рендерингу кадру.

Уникайте частих setState

Кожен виклик setState запускає повну перебудову віджета. Якщо стан змінюється з високою частотою (наприклад, анімація або потік даних), розгляньте використання AnimatedBuilder, ValueListenableBuilder або StreamBuilder замість ручного виклику setState. Ці віджети оптимізують перебудову, оновлюючи лише ту частину UI, яка дійсно змінилася.

Типові помилки

Перша типова помилка з StatefulWidget — виклик setState після dispose. Коли віджет видалено з дерева, State вважається мертвим, і будь-який виклик setState викидає виняток «setState called after dispose». Найчастіше це відбувається, коли асинхронна операція завершується після видалення віджета. Рішення — перевіряти прапорець mounted перед викликом setState або скасовувати асинхронні операції в dispose.

Друга помилка — виконання важких обчислень у методі build. Оскільки build викликається при кожному setState і при кожній перебудові батька, всі обчислення повинні бути максимально легкими. Якщо потрібно виконати ресурсоємну операцію — винесіть її в окремий ізолят (Isolate) або кешуйте результат у полі State.

Третя помилка — відсутність виклику super.initState() і super.dispose(). Перевизначаючи ці методи, розробник зобов'язаний викликати батьківську реалізацію. Якщо цього не зробити, фреймворк не зможе коректно керувати станом Element, що призведе до важковідстежуваних багів.

Рекомендації щодо уникнення помилок

  • Завжди перевіряй mounted перед setState в асинхронних колбеках
  • Не забувай викликати super.initState() і super.dispose()
  • Не роби HTTP-запити безпосередньо в build — використовуй initState
  • Відписуйся від усіх підписок в dispose
  • Використовуй мінімальну кількість StatefulWidget у проекті

Часті запитання

Чим StatefulWidget відрізняється від StatelessWidget?

StatefulWidget може змінювати свій стан через setState, має життєвий цикл (initState, dispose) і створює окремий об'єкт State. StatelessWidget не може змінювати стан і не має методів життєвого циклу — він просто відображає передані дані.

Скільки разів викликається createState?

createState викликається рівно один раз для кожного екземпляра StatefulElement. Навіть якщо батько перебудовується багаторазово, поки тип і Key віджета не змінюються, createState не викликається — використовується існуючий об'єкт State.

Що станеться, якщо не викликати dispose?

Ресурси не звільняться: контролери продовжать працювати у фоні, підписки на потоки залишаться активними, таймери не скасуються. Це призводить до витоків пам'яті і може викликати виклик setState після dispose, що викидає виняток.

Чи можна StatefulWidget зробити const?

Так, конструктор StatefulWidget може бути const. Однак це не дає такого ж виграшу, як для StatelessWidget — об'єкт State все одно буде створюватися при першому вбудовуванні. const впливає тільки на сам віджет (легку обгортку), не на State.

Навіщо потрібен метод didUpdateWidget?

didUpdateWidget викликається, коли батько передає StatefulWidget з новими параметрами. Це потрібно, щоб синхронізувати стан з новими даними — наприклад, якщо змінився userId в параметрах, потрібно завантажити профіль нового користувача.

Підсумки

  • StatefulWidget — віджет зі змінним станом, що використовує окремий об'єкт State для зберігання даних та управління життєвим циклом
  • Життєвий цикл складається з createState, initState, didChangeDependencies, build, didUpdateWidget і dispose, кожен з яких має своє призначення
  • setState — єдиний легітимний спосіб повідомлення фреймворку про зміну стану, після якого автоматично викликається build
  • mounted — прапорець, який необхідно перевіряти перед викликом setState в асинхронних операціях для уникнення винятку після dispose
  • Продуктивність — StatefulWidget потребує більше ресурсів, ніж StatelessWidget; рекомендується мінімізувати їх кількість через винесення стану у зовнішні шари
  • const дочірні віджети всередині State дозволяють скоротити обсяг перебудови при виклику setState, покращуючи продуктивність
  • Правильний вибір — використовуйте StatefulWidget тільки коли віджету необхідно керувати змінними даними або асинхронними операціями

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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