← Назад к вопросам
Расскажи про методы жизненного цикла виджета
2.0 Middle🔥 171 комментариев
#Flutter виджеты#State Management
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл виджета в Flutter
Введение
Каждый виджет в Flutter имеет определённый жизненный цикл — последовательность событий от создания до удаления. Понимание этого цикла критически важно для правильной работы с памятью, инициализацией, и обновлением состояния.
StatelessWidget жизненный цикл
StatelessWidget самый простой:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget();
// 1. constructor вызывается один раз
@override
Widget build(BuildContext context) {
// 2. build() вызывается при создании
// 3. build() вызывается при перестройке родителя
// 4. build() может вызваться много раз!
return Text("Hello");
}
// Когда виджет удаляется — больше ничего не вызывается
}
Жизненный цикл:
- Constructor → 2. build() → (переотрисовка при изменении родителя) → удаление
Ключевой момент: StatelessWidget не имеет состояния, поэтому нет методов инициализации и очистки.
StatefulWidget жизненный цикл
StatefulWidget намного сложнее. Жизненный цикл контролирует State объект:
class MyStatefulWidget extends StatefulWidget {
final String title;
const MyStatefulWidget({required this.title});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late String _data;
late StreamSubscription _subscription;
// 1. CONSTRUCTOR вызывается один раз
// (Dart создает объект State)
@override
void initState() {
// 2. INITSTATE вызывается один раз при создании
// - инициализация переменных
// - подписка на streams
// - setup listener'ов
// - запрос данных
super.initState();
_data = "initial";
_subscription = someStream.listen((value) {
setState(() {
_data = value;
});
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Вызывается при изменении состояния приложения
// (resumed, paused, inactive, detached)
if (state == AppLifecycleState.paused) {
print("App paused");
}
}
@override
void didUpdateWidget(MyStatefulWidget oldWidget) {
// 3. DIDUPDATEWIDGET вызывается когда parent перестроил виджет
// с новыми параметрами
super.didUpdateWidget(oldWidget);
if (oldWidget.title != widget.title) {
_data = "updated from ${widget.title}";
}
}
@override
Widget build(BuildContext context) {
// 4. BUILD вызывается:
// - после initState
// - после setState()
// - после didUpdateWidget
// - при любом перестроении родителя
// ВАЖНО: build может вызваться сотни раз!
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Text(_data),
);
}
@override
void reassemble() {
// 5. REASSEMBLE вызывается во время hot reload
// (разработка, горячая перезагрузка)
super.reassemble();
print("Hot reload detected!");
}
@override
void deactivate() {
// 6. DEACTIVATE вызывается когда виджет удален из дерева
// но State объект не удален (может быть переиспользован)
// - отписка от listener'ов? НЕЛЬЗЯ
// - очистка ресурсов? НЕЛЬЗЯ
super.deactivate();
}
@override
void dispose() {
// 7. DISPOSE вызывается один раз перед полным удалением
// ОБЯЗАТЕЛЬНО очищаем ресурсы здесь!
// - закрыть stream'ы
// - отписаться от listener'ов
// - отменить timer'ы
// - закрыть файлы
_subscription.cancel();
super.dispose();
}
}
Диаграмма жизненного цикла StatefulWidget
┌─────────────────────────────────────────────┐
│ Widget created (Constructor) │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ initState() (один раз!) │
│ - инициализация │
│ - подписки │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ didChangeAppLifecycleState() (опционально)│
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ didUpdateWidget() (если parent обновился) │
└────────────────┬────────────────────────────┘
│
▼
┌────────────────┐
│ build() ⬅️ │ (может вызваться много раз!)
└────────┬───────┘
│
(setState() → rebuild)┌┐
│ └┘
┌────────────────┴────────────────────────────┐
│ reassemble() (только во время hot reload) │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ deactivate() (редко нужен) │
│ - виджет удален из дерева │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ dispose() (один раз!) │
│ - закрытие stream'ов │
│ - отписка от listener'ов │
│ - отмена timer'ов │
│ - освобождение памяти │
└─────────────────────────────────────────────┘
│
▼
┌────────────────────┐
│ Widget удален │
└────────────────────┘
Практический пример: правильная инициализация
class DataFetcher extends StatefulWidget {
final String url;
const DataFetcher({required this.url});
@override
State<DataFetcher> createState() => _DataFetcherState();
}
class _DataFetcherState extends State<DataFetcher> {
late Future<List<String>> _dataFuture;
final _controller = TextEditingController();
@override
void initState() {
super.initState();
// ПРАВИЛЬНО: инициализируем будущее ONE TIME
_dataFuture = _fetchData(widget.url);
}
@override
void didUpdateWidget(DataFetcher oldWidget) {
super.didUpdateWidget(oldWidget);
// Если URL изменился — переполучаем данные
if (oldWidget.url != widget.url) {
_dataFuture = _fetchData(widget.url);
}
}
Future<List<String>> _fetchData(String url) async {
final response = await http.get(Uri.parse(url));
return (json.decode(response.body) as List)
.cast<String>();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: _dataFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return ListView(
children: snapshot.data!.map((item) => Text(item)).toList(),
);
},
);
}
@override
void dispose() {
_controller.dispose(); // ВАЖНО!
super.dispose();
}
}
Частые ошибки
❌ Ошибка 1: инициализация в build()
@override
Widget build(BuildContext context) {
_data = fetchData(); // ❌ НЕПРАВИЛЬНО! Вызовется сотни раз!
return Text(_data);
}
✅ Правильно:
@override
void initState() {
super.initState();
_data = fetchData(); // ✅ Один раз
}
❌ Ошибка 2: не закрывать ресурсы
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) => Text("data");
@override
void dispose() {
// ❌ ЗАБЫЛИ закрыть timer!
super.dispose();
// Memory leak!
}
✅ Правильно:
@override
void dispose() {
_timer?.cancel(); // ✅ Закрыли!
super.dispose();
}
Сравнение инициализации
| Метод | Количество вызовов | Когда использовать |
|---|---|---|
| constructor | 1 | присвоение константных параметров |
| initState | 1 | инициализация, подписки, загрузка |
| didUpdateWidget | 0+ | реакция на изменение параметров |
| build | много | построение UI |
| dispose | 1 | очистка ресурсов |
Итоги
Правильное понимание жизненного цикла — это основа эффективного Flutter кода. Золотые правила:
- Инициализируй в initState(), не в build()
- Реагируй на изменения в didUpdateWidget()
- Всегда очищай в dispose() (streams, timers, listeners)
- build() может вызваться много раз — делай его дешевым
- Не забывай super.initState(), super.dispose() и т.д.
Это различие между хорошим Flutter приложением и приложением, которое утекает память и падает.