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

Приведи пример использования InheritedWidget

2.0 Middle🔥 141 комментариев
#Flutter виджеты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

InheritedWidget: использование и практические примеры

InheritedWidget — это один из ключевых механизмов Flutter для передачи данных вниз по дереву виджетов без необходимости передавать их через конструктор каждого промежуточного виджета (避免prop drilling).

Что такое InheritedWidget

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

Важно понимать: InheritedWidget сам по себе не управляет состоянием, он только распространяет его вниз по дереву.

Практический пример: тема приложения

Здесь я создам InheritedWidget для управления темой (светлая/тёмная):

// Модель для хранения данных темы
class ThemeData {
  final Color primaryColor;
  final Color backgroundColor;
  final Brightness brightness;

  ThemeData({
    required this.primaryColor,
    required this.backgroundColor,
    required this.brightness,
  });
}

// InheritedWidget для распространения темы
class ThemeProvider extends InheritedWidget {
  final ThemeData theme;
  final VoidCallback onThemeChanged;

  const ThemeProvider({
    required this.theme,
    required this.onThemeChanged,
    required super.child,
    super.key,
  });

  // Удобный способ доступа к провайдеру из любого места в дереве
  static ThemeProvider of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
    assert(
      result != null,
      'No ThemeProvider found in context',
    );
    return result!;
  }

  // Метод, определяющий, нужно ли перестраивать потомков
  @override
  bool updateShouldNotify(ThemeProvider oldWidget) {
    return theme != oldWidget.theme;
  }
}

// Виджет для переключения темы
class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late ThemeData _theme = ThemeData(
    primaryColor: Colors.blue,
    backgroundColor: Colors.white,
    brightness: Brightness.light,
  );

  void _toggleTheme() {
    setState(() {
      _theme = ThemeData(
        primaryColor: _theme.brightness == Brightness.light
            ? Colors.deepPurple
            : Colors.blue,
        backgroundColor: _theme.brightness == Brightness.light
            ? Colors.grey[900]!
            : Colors.white,
        brightness: _theme.brightness == Brightness.light
            ? Brightness.dark
            : Brightness.light,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return ThemeProvider(
      theme: _theme,
      onThemeChanged: _toggleTheme,
      child: MaterialApp(
        title: 'InheritedWidget Example',
        theme: ThemeData(
          primaryColor: _theme.primaryColor,
          brightness: _theme.brightness,
        ),
        home: HomeScreen(),
      ),
    );
  }
}

Использование в виджетах-потомках

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Получаем доступ к провайдеру
    final themeProvider = ThemeProvider.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('InheritedWidget Example'),
        backgroundColor: themeProvider.theme.primaryColor,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current theme: ${themeProvider.theme.brightness}',
              style: TextStyle(
                fontSize: 18,
                color: themeProvider.theme.primaryColor,
              ),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: themeProvider.onThemeChanged,
              style: ElevatedButton.styleFrom(
                backgroundColor: themeProvider.theme.primaryColor,
              ),
              child: const Text('Toggle Theme'),
            ),
            const SizedBox(height: 24),
            // Этот виджет будет перестроен при изменении темы
            CardWidget(),
          ],
        ),
      ),
    );
  }
}

class CardWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = ThemeProvider.of(context).theme;

    return Container(
      width: 200,
      height: 100,
      decoration: BoxDecoration(
        color: theme.backgroundColor,
        border: Border.all(color: theme.primaryColor, width: 2),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Center(
        child: Text(
          'This card responds to theme changes',
          textAlign: TextAlign.center,
          style: TextStyle(color: theme.primaryColor),
        ),
      ),
    );
  }
}

Пример с пользовательскими данными (язык)

// InheritedWidget для управления языком приложения
class LocaleProvider extends InheritedWidget {
  final String locale; // 'en', 'ru', 'es'
  final Function(String) setLocale;

  const LocaleProvider({
    required this.locale,
    required this.setLocale,
    required super.child,
    super.key,
  });

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

  @override
  bool updateShouldNotify(LocaleProvider oldWidget) {
    return locale != oldWidget.locale;
  }
}

// В виджете используем:
class LocalizedText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final locale = LocaleProvider.of(context).locale;
    final text = getTranslation(locale);

    return Text(text);
  }

  String getTranslation(String locale) {
    final translations = {
      'en': 'Hello',
      'ru': 'Привет',
      'es': 'Hola',
    };
    return translations[locale] ?? 'Hello';
  }
}

Когда использовать InheritedWidget

Оптимально для:

  • Глобальные данные приложения (тема, язык, юзер)
  • Данные, которые часто читаются, но редко меняются
  • Данные, нужные глубоко вложенным виджетам

Не оптимально для:

  • Часто меняющегося состояния (используйте Riverpod, BLoC, GetX)
  • Сложной логики управления состоянием
  • Когда нужны side effects (исползуйте провайдеры с logic)

Отличие от Provider пакета

Код выше показывает raw InheritedWidget. На практике в современном Flutter обычно используют пакет provider или riverpod, которые оборачивают InheritedWidget и делают его более удобным и мощным:

// С пакетом provider это выглядит проще
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ThemeNotifier(),
      child: Consumer<ThemeNotifier>(
        builder: (context, theme, _) => MaterialApp(
          theme: theme.themeData,
          home: HomeScreen(),
        ),
      ),
    );
  }
}

Ключевые моменты

  1. dependOnInheritedWidgetOfExactType() — подписывает виджет на изменения
  2. updateShouldNotify() — определяет, нужно ли перестраивать потомков
  3. Нет нужды передавать данные через конструкторы — избегаем prop drilling
  4. Производительность — перестраиваются только нужные виджеты

InheritedWidget — фундамент для всех modern state management решений в Flutter (Provider, Riverpod, GetX).

Приведи пример использования InheritedWidget | PrepBro