Расскажи про жизненный цикл InheritedWidget.

«Расскажи про жизненный цикл InheritedWidget.» — вопрос из категории Жизненный цикл, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

InheritedWidget — это особый виджет в Flutter, предназначенный для эффективной передачи данных вниз по дереву виджетов. Он является фундаментом для таких механизмов, как Theme, MediaQuery и State Management решений вроде Provider.

Его «жизненный цикл» проще, чем у StatefulWidget, и сводится к ключевым моментам вставки, обновления и удаления из дерева.

1. Создание и Вставка в дерево

InheritedWidget создается как любой другой виджет и помещается в дерево, обычно ближе к корню, чтобы быть доступным для многих потомков.

class MyAppState extends InheritedWidget {
  final int counter; // Данные, которые мы хотим распространить.

  const MyAppState({
    Key? key,
    required this.counter,
    required Widget child,
  }) : super(key: key, child: child);

  // ...
}

2. Получение данных потомками (Наиболее важная часть)

Потомки получают доступ к данным через статический метод (часто называемый of), который использует context.dependOnInheritedWidgetOfExactType<T>().

  static MyAppState of(BuildContext context) {
    // Этот метод устанавливает зависимость.
    final result = context.dependOnInheritedWidgetOfExactType<MyAppState>();
    assert(result != null, 'No MyAppState found in context');
    return result!;
  }

Ключевой момент: Вызов dependOnInheritedWidgetOfExactType:

  • Находит ближайший InheritedWidget типа MyAppState выше по дереву.
  • Регистрирует зависимость текущего BuildContext (виджета-потомка) от этого InheritedWidget. Если InheritedWidget обновится, все зависящие от него виджеты будут перестроены.

3. Обновление данных

InheritedWidget неизменяем (immutable). Чтобы обновить данные, необходимо создать новый экземпляр InheritedWidget с новыми данными и заменить старый в дереве (например, через setState в родительском StatefulWidget).

Перед перестроением зависимых виджетов Flutter вызывает метод updateShouldNotify.

  @override
  bool updateShouldNotify(MyAppState oldWidget) {
    // Сравниваем старые и новые данные.
    // Возвращаем true, только если данные изменились и зависимые виджеты должны перестроиться.
    return counter != oldWidget.counter;
  }

Это критично для производительности. Если counter не изменился, Flutter не будет тратить ресурсы на перерисовку всех подписанных виджетов.

4. Удаление из дерева

Когда InheritedWidget удаляется из дерева (например, при навигации), все зависимости автоматически очищаются.

Полный пример использования:

class MyAppState extends InheritedWidget {
  final int counter;
  const MyAppState({Key? key, required this.counter, required Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(MyAppState oldWidget) => counter != oldWidget.counter;

  static MyAppState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyAppState>()!;
  }
}

// Родительский виджет, управляющий состоянием.
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return MyAppState(
      counter: _counter,
      child: MaterialApp(
        home: Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Потомок, который зависит от MyAppState и перестроится при изменении counter.
                Builder(
                  builder: (context) {
                    final appState = MyAppState.of(context);
                    return Text('Counter: ${appState.counter}');
                  },
                ),
                ElevatedButton(
                  onPressed: () => setState(() => _counter++),
                  child: Text('Increment'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Итог: InheritedWidget — это эффективный механизм «подписки» на данные. Его жизненный цикл управляется Flutter'ом на основе зависимостей, установленных через dependOnInheritedWidgetOfExactType, и оптимизируется методом updateShouldNotify.