Что такое InheritedWidget в Flutter?

Ответ

InheritedWidget — это специальный виджет в Flutter, предназначенный для эффективной передачи данных вниз по дереву виджетов. Он позволяет дочерним виджетам получать доступ к данным без необходимости явно прокидывать их через конструкторы каждого промежуточного виджета (проброс пропсов).

Ключевые особенности:

  • Эффективность: При изменении данных в InheritedWidget Flutter перестраивает только те виджеты, которые от этих данных зависят (вызвали context.dependOnInheritedWidgetOfExactType), а не всё поддерево.
  • Доступность: Любой виджет в поддереве ниже InheritedWidget может получить к нему доступ.
  • Использование: Исторически использовался для тем, локализации, аутентификации или любого другого «глобального» для поддерева состояния.

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

// 1. Создаем InheritedWidget
class AppTheme extends InheritedWidget {
  final Color primaryColor;
  final Brightness brightness;

  const AppTheme({
    required this.primaryColor,
    required this.brightness,
    required Widget child,
    Key? key,
  }) : super(key: key, child: child);

  // 2. Статический метод для удобного доступа
  static AppTheme of(BuildContext context) {
    final AppTheme? result = context.dependOnInheritedWidgetOfExactType<AppTheme>();
    assert(result != null, 'No AppTheme found in context');
    return result!;
  }

  // 3. Метод, определяющий, нужно ли уведомлять зависимые виджеты об обновлении
  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    return primaryColor != oldWidget.primaryColor || brightness != oldWidget.brightness;
  }
}

// 4. Использование в дочернем виджете
class ThemedButton extends StatelessWidget {
  const ThemedButton({super.key});

  @override
  Widget build(BuildContext context) {
    // Получаем данные из ближайшего AppTheme в дереве
    final theme = AppTheme.of(context);
    return ElevatedButton(
      style: ElevatedButton.styleFrom(primary: theme.primaryColor),
      onPressed: () {},
      child: const Text('Кнопка'),
    );
  }
}

Важно: InheritedWidget — это низкоуровневый примитив. В современных приложениях для управления состоянием чаще используются пакеты-надстройки, такие как Provider (который сам построен на InheritedWidget), Riverpod или Bloc, так как они предоставляют более удобный API и лучшие возможности для тестирования.

Ответ 18+ 🔞

Э, слушай, вот есть в Flutter'е такая штука — InheritedWidget. Представь себе, что ты пытаешься передать бутылку воды через всю толпу на концерте, а вокруг тебя — овердохуища народу. Так вот, InheritedWidget — это как будто ты ставишь эту бутылку на сцену и кричишь: «Кому надо — берите!». Любой в зале может до неё дотянуться, не передавая из рук в руки через десять человек.

В чём прикол:

  • Не жрёт ресурсы: Когда данные в этом виджете меняются, Flutter не начинает долбить и пересобирать всё подряд. Он умный — обновляет только тех, кто реально завис от этих данных и сказал «я на них подписан». Остальным — да похуй, они и так работают.
  • Доступ — раз плюнуть: Любой виджет-потомок может в любой момент закричать «эй, дай сюда тему!» и получить её. Никаких пробросов через десять конструкторов, где в каждом промежуточном виджете ты пишешь final Color someColor и чувствуешь себя идиотом.
  • Классика жанра: Раньше это был главный способ тащить темы, настройки языка или данные юзера куда угодно. Честно, иногда и сейчас удобно, если проект не огромный.

Смотри, как это выглядит на практике:

// 1. Делаем своего наследничка
class AppTheme extends InheritedWidget {
  final Color primaryColor;
  final Brightness brightness;

  const AppTheme({
    required this.primaryColor,
    required this.brightness,
    required Widget child,
    Key? key,
  }) : super(key: key, child: child);

  // 2. Делаем статический метод, чтобы было удобно орать "Дай тему!"
  static AppTheme of(BuildContext context) {
    // Вот эта строка — магия. Она говорит: "Ищи выше по дереву AppTheme и подпишись на него".
    final AppTheme? result = context.dependOnInheritedWidgetOfExactType<AppTheme>();
    // На всякий случай орём, если не нашли (но обычно находим).
    assert(result != null, 'No AppTheme found in context');
    return result!;
  }

  // 3. Самое важное! Тут решаем, пора ли будить всех подписчиков.
  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    // Если цвет или яркость поменялись — да, пора орать "Обновитесь!".
    return primaryColor != oldWidget.primaryColor || brightness != oldWidget.brightness;
  }
}

// 4. Используем где угодно внизу по дереву
class ThemedButton extends StatelessWidget {
  const ThemedButton({super.key});

  @override
  Widget build(BuildContext context) {
    // Вжух! И тема у нас в кармане. Никаких пробросов.
    final theme = AppTheme.of(context);
    return ElevatedButton(
      style: ElevatedButton.styleFrom(primary: theme.primaryColor),
      onPressed: () {},
      child: const Text('Кнопка'),
    );
  }
}

Но есть нюанс, ёпта! InheritedWidget — это как голый двигатель. Мощно, но возиться с ним — терпения ноль ебать. Поэтому сейчас все умные люди берут готовые обёртки: Provider (который внутри — тот же самый InheritedWidget, но с конфеткой), Riverpod или Bloc. Они дают меньше мороки и меньше шансов выстрелить себе в ногу. Но знать, как работает этот костыль в основе — полезно, а то будешь как мартышлюшка, которая кнопки тыкает, не понимая, что внутри.