Каков жизненный цикл виджета StatefulWidget?
Жизненный цикл StatefulWidget: createState → initState (инициализация) → didChangeDependencies → build (при каждой перерисовке) → didUpdateWidget (при смене параметров) → deactivate → dispose (освобождение ресурсов).
Жизненный цикл StatefulWidget
StatefulWidget состоит из двух классов: неизменяемого виджета и отдельного объекта State, который хранит изменяемое состояние и управляет жизненным циклом.
Методы жизненного цикла в порядке вызова
1. createState()
Фреймворк вызывает этот метод при первой вставке виджета в дерево. Создаёт объект State:
class ProfilePage extends StatefulWidget {
final String userId;
const ProfilePage({super.key, required this.userId});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
2. initState()
Вызывается один раз после создания State. Место для инициализации: контроллеров, подписок, асинхронных запросов:
class _ProfilePageState extends State<ProfilePage> {
late final TextEditingController _nameController;
StreamSubscription? _subscription;
@override
void initState() {
super.initState(); // обязательно первым
_nameController = TextEditingController();
_loadUser(); // запустить асинхронную загрузку
_subscription = userStream.listen(_onUserUpdate);
}
Future<void> _loadUser() async {
final user = await UserService.fetch(widget.userId);
if (mounted) { // проверяем перед setState
setState(() => _user = user);
}
}
}
3. didChangeDependencies()
Вызывается после initState() и при изменении InheritedWidget, от которого зависит State. Безопасное место для обращения к MediaQuery, Theme, провайдерам:
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Theme.of(context) безопасен здесь, но не в initState()
final locale = Localizations.localeOf(context);
_formatter = NumberFormat.currency(locale: locale.toString());
}
4. build()
Вызывается каждый раз, когда нужно отрисовать виджет — после setState(), смены зависимостей или перестройки родителя. Должен быть чистым и быстрым:
@override
Widget build(BuildContext context) {
if (_user == null) return const CircularProgressIndicator();
return Text(_user!.name);
}
5. didUpdateWidget()
Вызывается когда родитель перестраивает виджет с новыми параметрами. Старый виджет доступен через oldWidget:
@override
void didUpdateWidget(ProfilePage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.userId != widget.userId) {
// userId изменился — перезагружаем данные
_loadUser();
}
}
6. setState()
Не метод жизненного цикла, но ключевой метод: уведомляет фреймворк о необходимости перерисовки. Вызывает build() для этого State и его поддерева.
7. deactivate()
Вызывается при временном удалении State из дерева (например, при навигации). Редко используется напрямую.
8. dispose()
Вызывается при окончательном удалении из дерева. Место для освобождения ресурсов:
@override
void dispose() {
_nameController.dispose();
_subscription?.cancel();
_animationController.dispose();
super.dispose(); // обязательно последним
}
Полный пример с жизненным циклом
class _ProfilePageState extends State<ProfilePage> {
User? _user;
late final TextEditingController _nameController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_loadUser();
}
@override
void didUpdateWidget(ProfilePage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.userId != widget.userId) _loadUser();
}
Future<void> _loadUser() async {
final user = await UserService.fetch(widget.userId);
if (mounted) setState(() => _user = user);
}
@override
Widget build(BuildContext context) =>
_user == null ? const CircularProgressIndicator() : Text(_user!.name);
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
}
Подводные камни
- Вызов
setState()вdispose()— гарантированный крэш; всегда проверяйтеmountedпередsetState()после async-операций. super.initState()должен вызываться первым,super.dispose()— последним; нарушение порядка приводит к assertion errors.- Использование
contextвinitState()— допустимо только черезWidgetsBinding.instance.addPostFrameCallback, так как виджет ещё не встроен в дерево. - Забытый
dispose()дляAnimationController,TextEditingControllerилиStreamSubscription— утечка памяти. - Обращение к
widget.*вinitState()— корректно; обращение кTheme.of(context)вinitState()— нет (контекст ещё не готов). didChangeDependencies()может вызываться несколько раз — размещайте в нём только идемпотентные операции или защищайте флагом.
Common mistakes
- Сводить «Каков жизненный цикл виджета
StatefulWidget» к синтаксису и не объяснять element tree. - Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии flutter-11.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «Каков жизненный цикл виджета
StatefulWidget» и подтверждает ее корректным примером. - Умеет связать ответ с render tree, тестированием и отладкой на устройстве.
- Называет ограничения подхода flutter-11, включая производительность, память и сопровождение.