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

Расскажи про методы жизненного цикла виджета

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");
  }
  // Когда виджет удаляется — больше ничего не вызывается
}

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

  1. 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();
}

Сравнение инициализации

МетодКоличество вызововКогда использовать
constructor1присвоение константных параметров
initState1инициализация, подписки, загрузка
didUpdateWidget0+реакция на изменение параметров
buildмногопостроение UI
dispose1очистка ресурсов

Итоги

Правильное понимание жизненного цикла — это основа эффективного Flutter кода. Золотые правила:

  1. Инициализируй в initState(), не в build()
  2. Реагируй на изменения в didUpdateWidget()
  3. Всегда очищай в dispose() (streams, timers, listeners)
  4. build() может вызваться много раз — делай его дешевым
  5. Не забывай super.initState(), super.dispose() и т.д.

Это различие между хорошим Flutter приложением и приложением, которое утекает память и падает.

Расскажи про методы жизненного цикла виджета | PrepBro