← Назад к вопросам

Всегда ли вызывается initState?

2.0 Middle🔥 171 комментариев
#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Всегда ли вызывается initState?

initState — это важный метод жизненного цикла StatefulWidget, который вызывается один раз после создания State объекта. Но есть важные нюансы, когда он вызывается, а когда нет.

Краткий ответ

Да, initState ВСЕГДА вызывается ровно один раз, если:

  1. Виджет является StatefulWidget
  2. У соответствующего State класса переопределён метод initState()
  3. Виджет успешно добавлен в дерево элементов (mounted)

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

// Порядок вызовов:

1. Конструктор MyStatefulWidget() — создание конфигурации
2. createState() — создание State объекта
3. initState() — инициализация State (ОДИН РАЗ!)
4. build() — отрисовка UI
5. didUpdateWidget() — если родитель перестроил виджет
6. build() — отрисовка заново
7. dispose() — очистка ресурсов перед удалением

Простой пример

class MyCounter extends StatefulWidget {
  @override
  State<MyCounter> createState() => _MyCounterState();
}

class _MyCounterState extends State<MyCounter> {
  int counter = 0;
  late StreamSubscription subscription;
  
  @override
  void initState() {
    super.initState();  // ОБЯЗАТЕЛЬНО первая строка!
    print('initState вызван');
    
    // Инициализация:
    // - Слушатели на потоки/сокеты
    // - Загрузка данных из API
    // - Инициализация контроллеров
    // - Запуск таймеров
    
    subscription = someStream.listen((_) {
      setState(() => counter++);
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Text('Counter: $counter');
  }
  
  @override
  void dispose() {
    print('dispose вызван');
    subscription.cancel();  // очистка ресурсов
    super.dispose();  // ОБЯЗАТЕЛЬНО последняя строка!
  }
}

Когда initState НЕ вызывается?

1. Если виджет StatelessWidget

class MyStateless extends StatelessWidget {  // StatelessWidget!
  @override
  void initState() {  // ❌ ОШИБКА! нет этого метода
    // это не работает
  }
  
  @override
  Widget build(BuildContext context) {
    return Text('Hello');
  }
}

2. Если State объект не добавлен в дерево (unmounted)

var state = _MyCounterState();  // создали State
// initState() НЕ вызывается! потому что это не в дереве элементов

// initState вызывается только когда:
// - Виджет добавлен в Widget Tree
// - Создан Element
// - Вызван createState()

3. Если переопределить createState неправильно

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() {
    // ❌ если здесь выбросить исключение
    throw Exception('Ошибка!');
    // то initState() не вызовется
  }
}

initState вызывается ОДИН РАЗ

Это критично понимать:

class MyWidget extends StatefulWidget {
  final String title;
  
  MyWidget({required this.title});
  
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    print('initState: title = ${widget.title}');
  }
  
  @override
  void didUpdateWidget(MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Вызывается если РОДИТЕЛЬ перестроил виджет с другим title
    print('didUpdateWidget: title = ${widget.title}');
    // НО initState не вызывается снова!
  }
  
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);
  }
}

// Сценарий:
var myWidget = MyWidget(title: 'First');
// Вывод: initState: title = First
// (build вызывается)

// Родитель перестраивает: MyWidget(title: 'Second')
// Вывод: didUpdateWidget: title = Second
// (build вызывается ещё раз)
// initState НЕ вызывается снова!

Когда нужен initState

initState используй для инициализации, которая нужна один раз:

class ProfileScreen extends StatefulWidget {
  final String userId;
  
  ProfileScreen({required this.userId});
  
  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  late Future<User> userFuture;
  late StreamSubscription subscription;
  
  @override
  void initState() {
    super.initState();
    
    // 1. Инициализация Future для загрузки данных
    userFuture = api.getUser(widget.userId);
    
    // 2. Слушание потока данных
    subscription = userService.userStream.listen((user) {
      setState(() { /* обновляем UI */ });
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: userFuture,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return UserCard(user: snapshot.data!);
        }
        return CircularProgressIndicator();
      },
    );
  }
  
  @override
  void dispose() {
    subscription.cancel();
    super.dispose();
  }
}

Важные правила initState

1. Всегда вызывай super.initState() в начале

@override
void initState() {
  super.initState();  // ✅ ВСЕГДА первым!
  // остальной код
}

2. Всегда вызывай super.dispose() в конце

@override
void dispose() {
  // очистка ресурсов
  super.dispose();  // ✅ ВСЕГДА последним!
}

3. Нельзя вызывать setState в initState

@override
void initState() {
  super.initState();
  
  setState(() {  // ❌ Не делай так!
    value = 100;
  });
  
  // Вместо этого присвой значение напрямую:
  value = 100;  // ✅ Правильно
}

didUpdateWidget vs initState

class MyCounter extends StatefulWidget {
  final int initialValue;
  
  MyCounter({required this.initialValue});
  
  @override
  State<MyCounter> createState() => _MyCounterState();
}

class _MyCounterState extends State<MyCounter> {
  late int counter;
  
  @override
  void initState() {
    super.initState();
    counter = widget.initialValue;  // инициализируем один раз
    print('initState: counter = $counter');
  }
  
  @override
  void didUpdateWidget(MyCounter oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Вызывается если initialValue изменился
    if (oldWidget.initialValue != widget.initialValue) {
      // Обновляем counter
      counter = widget.initialValue;
      print('didUpdateWidget: counter = $counter');
    }
  }
  
  @override
  Widget build(BuildContext context) => Text('$counter');
}

// Сценарий:
// MyCounter(initialValue: 10)
// Вывод: initState: counter = 10

// Родитель перестраивает: MyCounter(initialValue: 20)
// Вывод: didUpdateWidget: counter = 20

Пример с проверкой

class TestInitState extends StatefulWidget {
  @override
  State<TestInitState> createState() => _TestInitStateState();
}

class _TestInitStateState extends State<TestInitState> {
  int initCallCount = 0;
  
  @override
  void initState() {
    super.initState();
    initCallCount++;
    print('initState вызван (раз: $initCallCount)');
  }
  
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('initState вызван $initCallCount раз'),
          ElevatedButton(
            onPressed: () => setState(() {}),  // перестроение
            child: Text('Rebuild'),
          ),
        ],
      ),
    );
  }
}

// Результат:
// Первая отрисовка: "initState вызван 1 раз"
// После нажатия кнопки (setState): "initState вызван 1 раз" (не изменилось!)
// initState действительно вызывается только один раз

Краткая таблица вызовов

СобытиеinitStatedidUpdateWidgetbuilddispose
Первое создание
setState()
Новый конфиг от родителя
Удаление виджета

Вывод: initState ВСЕГДА вызывается ровно один раз при создании State для StatefulWidget. Это идеальное место для инициализации ресурсов, которые не нужно повторять. Помни про super.initState() в начале и всегда соответствующий dispose() для очистки ресурсов.

Всегда ли вызывается initState? | PrepBro