Программирование с ИИ
ENRU
11.4

Состояние

stateless, stateful и что выбирать

Что это

Виджеты бывают двух видов: те, что просто рисуют переданные данные, и те, что хранят что-то своё и перерисовываются при изменении.

Идея та же, что пропсы и состояние в React (статья 10.3), и правила принятия решений те же. Отличается только синтаксис — и он во Flutter многословнее.

Как выглядит в коде

Stateless — своих данных нет

class CityCard extends StatelessWidget {
  final String name;
  final double? temp;
  final void Function(String) onRemove;

  const CityCard({
    super.key,
    required this.name,
    this.temp,
    required this.onRemove,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Text(name),
          Text(temp == null ? 'нет данных' : '$temp°'),
          TextButton(
            onPressed: () => onRemove(name),
            child: const Text('Удалить'),
          ),
        ],
      ),
    );
  }
}

Всё приходит снаружи через конструктор — это прямой аналог пропсов. final у полей не случаен: менять их нельзя, как и пропсы в React.

super.key — тот самый key, что в React (статья 10.2), и нужен он для того же: чтобы при изменении списка Flutter понимал, какой виджет какой.

Stateful — есть своё

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

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

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

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: _increment,
      child: Text('нажато: $_count'),
    );
  }
}

Два класса вместо одного — особенность Flutter, к которой надо привыкнуть: сам виджет и его состояние живут отдельно.

setState — единственный способ сообщить об изменении. Присваивание _count++ без обёртки setState изменит значение, но экран не перерисуется. Это ровно та же ошибка, что присваивание вместо setX в React.

Подчёркивание в начале имени в Dart означает «только для этого файла». Поэтому состояние обычно _CounterState, а поля _count.

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

@override
void initState() {
  super.initState();
  _loadData();        // выполняется один раз при появлении
}

@override
void dispose() {
  _controller.dispose();   // уборка: контроллеры, подписки, таймеры
  super.dispose();
}
МетодКогдаАналог в React
initStateодин раз при появленииuseEffect(..., [])
buildпри каждой перерисовкетело компонента
disposeпри удалениивозврат функции из useEffect

dispose обязателен для всего, что надо закрывать: контроллеры полей ввода, прокрутки, анимации, подписки, таймеры. Забытый dispose — утечка, и анализатор о ней не всегда предупредит.

Когда своего состояния становится много

Передавать данные через конструкторы на пять уровней вниз неудобно, и для этого есть библиотеки управления состоянием: provider, riverpod, bloc и другие.

Выбор между ними — архитектурное решение, а не вопрос вкуса: оно определяет, как будет устроен весь проект. Принимать его стоит самому, а не оставлять ИИ (статья 1.1), и обязательно называть выбранное в запросах — иначе получишь код под ту библиотеку, которой в обучающих данных было больше.

Как сказать ИИ

Запрос
В проекте используется provider, версия 6. Состояние
хранится в ChangeNotifier. Новых библиотек управления
состоянием не добавляй.
Запрос
Этот виджет stateful, но своего состояния у него нет —
всё приходит через конструктор. Переделай в stateless.
Запрос
Проверь: у всех ли контроллеров в этом файле есть dispose.
Список того, что не закрывается.

На что смотреть

Stateful без состояния. Лишний класс и лишний код. Признак: в State нет полей, только build.

Присваивание без setState. Значение меняется, экран нет.

setState с тяжёлой работой внутри. Внутрь кладут только изменение полей; запросы и вычисления — до него.

Нет dispose. Контроллеры и подписки живут дольше виджета.

Запрос к сети в build. build вызывается часто; запрос должен быть в initState или в слое данных. Прямой аналог ошибки из статьи 10.4.

Библиотека состояния выбрана за тебя. В ответе появился bloc, хотя в проекте provider, — проект получит две системы сразу.

Шпаргалка

  • Stateless — всё снаружи через конструктор, аналог пропсов.

  • Stateful — два класса: виджет и его состояние.

  • Менять поля только внутри setState, иначе экран не перерисуется.

  • initState — один раз, build — часто, dispose — уборка.

  • Контроллеры и подписки обязаны закрываться в dispose.

  • Библиотеку управления состоянием выбирай сам и называй в запросе.