InheritedWidget — що це, передача даних у дереві та як працює

Автор: IT Sectr Опубліковано: 2026-07-02 Час читання: 9 хв

InheritedWidget — це спеціальний віджет у Flutter, який передає дані вниз по дереву віджетів без явної передачі через конструктори. Дочірні віджети отримують доступ до даних через BuildContext і автоматично підписуються на оновлення. Коли дані в InheritedWidget змінюються, всі залежні віджети перебудовуються. За даними Flutter API Reference, 2025, InheritedWidget лежить в основі Theme, MediaQuery, Localizations і більшості бібліотек керування станом.

Головне

  • InheritedWidget передає дані вниз по Widget Tree без явного проброшування через кожен віджет.
  • Автоматична підписка — віджети, що використовують dependOnInheritedWidgetOfExactType, перебудовуються при зміні даних.
  • Theme та MediaQuery — вбудовані приклади InheritedWidget, доступні в кожному Flutter-додатку.
  • Provider та Riverpod побудовані поверх InheritedWidget і розширюють його можливості для керування станом.
  • Правильна реалізація потребує перевизначення updateShouldNotify для запобігання зайвих перебудувань.

Що таке InheritedWidget у Flutter?

InheritedWidget — це віджет, який робить свої дані доступними для всіх нащадків у Widget Tree. На відміну від звичайного віджета, який передає дані лише через конструктори дочірнім елементам, InheritedWidget дозволяє будь-якому віджету в піддереві отримати доступ до даних без ланцюжка параметрів. Це вирішує проблему “prop drilling” — передачі даних через багато проміжних віджетів, які самі не використовують ці дані.

Вбудовані InheritedWidget

Flutter включає кілька вбудованих InheritedWidget: Theme (колірна схема та стилі), MediaQuery (розмір екрана, орієнтація, щільність пікселів), Localizations (локалізовані рядки), Directionality (напрям тексту), DefaultTextStyle (стиль тексту за замовчуванням). Ці віджети встановлюються кореневими віджетами на зразок MaterialApp і доступні в усьому додатку.

Життєвий цикл InheritedWidget

InheritedWidget не має власного стану — він зберігає дані, передані через конструктор. Коли батьківський InheritedWidget перебудовується з новими даними, викликається метод updateShouldNotify для порівняння старих і нових даних. Якщо метод повертає true, всі залежні віджети позначаються для перебудування. Це простий, але ефективний механізм реактивного оновлення.

Як працює передача даних через InheritedWidget

Механізм передачі даних через InheritedWidget заснований на Element Tree. Коли віджет викликає dependOnInheritedWidgetOfExactType, відповідний елемент реєструє залежність від InheritedElement. При зміні InheritedWidget, InheritedElement повідомляє всі залежні елементи, які перебудовуються в наступному кадрі.

Реєстрація залежності

Метод dependOnInheritedWidgetOfExactType не просто знаходить InheritedWidget у дереві — він підписує поточний елемент на сповіщення. Якби ви використовували findAncestorWidgetOfExactType замість dependOn, віджет отримав би дані, але не перебудовувався б при їх зміні. Це важлива відмінність: dependOn — це підписка, а findAncestor — одноразовий пошук.

Обхід дерева InheritedWidget

Коли віджет запитує InheritedWidget, Flutter піднімається по Element Tree від поточного елемента до кореня, перевіряючи кожен InheritedElement на збіг типу. Перший знайдений InheritedElement повертається. Це означає, що найближчий InheritedWidget у дереві має пріоритет — ви можете перевизначити дані на певному рівні, розмістивши InheritedWidget ближче до нащадків.

dart
class ThemeData {
  final Color primaryColor;
  final TextTheme textTheme;

  const ThemeData({required this.primaryColor, required this.textTheme});
}

class MyTheme extends InheritedWidget {
  final ThemeData data;

  const MyTheme({required this.data, required Widget child}) : super(child: child);

  static MyTheme of(BuildContext context) {
    final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
    assert(widget != null, "MyTheme not found in tree");
    return widget!;
  }

  @override
  bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}

У цьому прикладі MyTheme використовує статичний метод of для надання даних нащадкам. Метод dependOnInheritedWidgetOfExactType реєструє залежність, а updateShouldNotify порівнює старі та нові дані для визначення необхідності перебудування залежних віджетів.

Створення власного InheritedWidget

Створення власного InheritedWidget складається з двох кроків: визначення класу, що успадковує InheritedWidget, та реалізація статичного методу of для доступу з нащадків. Дані передаються через конструктор, а метод updateShouldNotify визначає, коли залежні віджети мають перебудовуватися.

Крок 1: Визначення класу InheritedWidget

Клас має успадковувати InheritedWidget і приймати дані через конструктор з обов’язковим параметром child. Дані можуть бути будь-якого типу: примітиви, об’єкти, функції. Головне правило — дані мають бути незмінними (immutable), щоб можна було надійно порівнювати старе та нове значення.

Крок 2: Статичний метод of

Статичний метод of приймає BuildContext і повертає дані InheritedWidget. Всередині викликається dependOnInheritedWidgetOfExactType, який шукає найближчий InheritedWidget зазначеного типу в дереві. Якщо InheritedWidget не знайдено, метод викидає виняток або повертає значення за замовчуванням залежно від реалізації.

Крок 3: Використання у віджетах

Для доступу до даних віджет викликає MyWidget.of(context) всередині методу build. Flutter автоматично підписує віджет на оновлення. Якщо дані зміняться, віджет перебудується в наступному кадрі. Це дозволяє створювати чистий та декларативний код без зайвих параметрів.

dart
class UserPreferences extends InheritedWidget {
  final String languageCode;
  final bool darkMode;

  const UserPreferences({
    required this.languageCode,
    required this.darkMode,
    required Widget child,
  }) : super(child: child);

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

  @override
  bool updateShouldNotify(UserPreferences oldWidget) =>
    oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}

У цьому прикладі UserPreferences зберігає налаштування користувача. Метод updateShouldNotify порівнює кожне поле окремо, що запобігає зайвим перебудуванням при зміні лише одного параметра. Використовуйте аналогічний підхід для власних InheritedWidget з кількома полями.

Метод updateShouldNotify та запобігання зайвим перебудуванням

updateShouldNotify — це ключовий метод InheritedWidget, який визначає, чи потрібно сповіщати залежні віджети про зміну даних. Якщо метод повертає false, залежні віджети не перебудовуються, навіть якщо сам InheritedWidget отримав новий екземпляр з тими самими даними. Це критично важливо для продуктивності.

Правильна реалізація updateShouldNotify

Порівнюйте лише ті поля, які дійсно змінилися та впливають на відображення. Якщо InheritedWidget містить 10 полів, але лише одне з них впливає на UI, перевіряйте лише це поле. Для колекцій використовуйте глибоке порівняння або імутабельні структури даних. Не використовуйте == для List або Map, оскільки вони порівнюються за посиланням.

  • Примітиви — використовуйте прямі порівняння: oldWidget.value != value.
  • Незмінні об’єкти — використовуйте перевизначений ==: oldWidget.data != data (якщо data перевизначає ==).
  • Колекції — використовуйте listEquals, mapEquals з package:flutter/foundation.dart.

Помилки при реалізації updateShouldNotify

Найчастіша помилка — повертати true без порівняння. Це призводить до перебудування всіх залежних віджетів при кожному оновленні батька, навіть якщо дані не змінилися. Друга помилка — повертати false, коли дані змінилися, що призводить до застарілого UI. Третя — складне порівняння, яке виконується кожен кадр і сповільнює роботу.

InheritedWidget vs колбеки: що обрати?

InheritedWidget і колбеки (передача функцій через конструктори) вирішують різні завдання. InheritedWidget підходить для даних, які потрібні багатьом віджетам на різних рівнях дерева. Колбеки зручні для однонаправленої передачі подій від батька до конкретного нащадка або навпаки. Вибір залежить від архітектури додатку та частоти змін.

Коли використовувати InheritedWidget

Використовуйте InheritedWidget, коли дані потрібні багатьом віджетам на різних рівнях вкладеності: тема додатку, налаштування користувача, інформація про пристрій, дані поточної сесії. InheritedWidget особливо ефективний для “глобальних” даних, які змінюються рідко, але потрібні в різних частинах UI.

Коли використовувати колбеки

Колбеки (функції зворотного виклику) підходять для передачі подій від дочірнього віджета до батьківського: натискання кнопки, вибір елемента списку, відправка форми. Колбеки явно вказують, які дії може виконати нащадок, і не створюють прихованих залежностей. Для передачі даних вниз по дереву на невелику кількість рівнів також простіше використовувати параметри конструктора.

КритерійInheritedWidgetКолбеки
НапрямЗверху вниз (батько → нащадки)Знизу вверх (нащадок → батько) або безпосередньо
ОбластьВсе піддеревоКонкретний віджет
ПеребудуванняАвтоматичне при зміні данихПотрібен setState вручну
СкладністьСередня (потрібен клас InheritedWidget)Низька (просто функція)

InheritedWidget та бібліотеки керування станом

Provider та Riverpod — популярні бібліотеки керування станом у Flutter, побудовані поверх InheritedWidget. Вони розширюють його можливості: додають підтримку ChangeNotifier, автоматичне видалення при демонтуванні, ліниву ініціалізацію та спрощений синтаксис через дженерики.

Provider на основі InheritedWidget

Provider використовує InheritedWidget для передачі об’єкта будь-якого типу вниз по дереву. ChangeNotifierProvider відстежує зміни через ChangeNotifier і викликає updateShouldNotify при виклику notifyListeners. Це позбавляє розробника від ручного створення InheritedWidget та реалізації updateShouldNotify.

Порівняння з прямим InheritedWidget

Прямий InheritedWidget дає більше контролю і не потребує зовнішніх залежностей. Provider надає готову інфраструктуру: Consumer, Selector, MultiProvider, ProxyProvider. Вибір залежить від складності додатку. Для простих проектів прямого InheritedWidget достатньо, для великих — Provider або Riverpod знижують шаблонний код.

dart
// Прямий InheritedWidget
class UserProvider extends InheritedWidget {
  final UserData userData;
  const UserProvider({required this.userData, required Widget child}) : super(child: child);
  static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
  @override
  bool updateShouldNotify(UserProvider old) => old.userData != userData;
}

// Еквівалент Provider
return ChangeNotifierProvider<UserData>(
  create: (_) => UserData(),
  child: MyApp(),
);

Обидва підходи в прикладі вирішують одне завдання — передачу UserData вниз по дереву. Provider скорочує кількість коду, але приховує механіку InheritedWidget. Прямий InheritedWidget дає повний контроль і розуміння того, що відбувається, що особливо важливо при вивченні Flutter та налагодженні складних проблем з перебудуванням.

Часто задавані питання

Чим InheritedWidget відрізняється від звичайного віджета?

InheritedWidget робить дані доступними для всіх нащадків через BuildContext, а звичайний віджет передає дані лише через конструктор. InheritedWidget також підписує нащадків на оновлення даних.

Як часто перебудовуються залежні віджети?

Залежні віджети перебудовуються лише тоді, коли updateShouldNotify повертає true. Якщо метод реалізовано правильно, перебудування відбувається лише при реальній зміні даних, а не при кожному перебудуванні батька.

Чи можна використовувати кілька InheritedWidget в одному дереві?

Так, можна використовувати будь-яку кількість InheritedWidget в одному дереві. Кожен надає дані певного типу, а віджети можуть отримувати дані з кількох InheritedWidget одночасно.

Чим відрізняється dependOnInheritedWidgetOfExactType від findAncestorWidgetOfExactType?

dependOn підписує віджет на оновлення — при зміні даних віджет перебудується. findAncestor виконує одноразовий пошук без підписки, і віджет не дізнається про зміни даних.

Чи підходить InheritedWidget для керування складним станом?

Для простого стану (тема, налаштування) InheritedWidget достатній. Для складного стану з бізнес-логікою використовуйте Provider, Riverpod або BLoC — вони побудовані на InheritedWidget і додають необхідну інфраструктуру.

Підсумки

  • InheritedWidget — спеціальний віджет Flutter для передачі даних вниз по дереву з автоматичною підпискою на оновлення.
  • Механізм роботи заснований на Element Tree: InheritedElement реєструє залежні елементи та повідомляє їх про зміни.
  • updateShouldNotify — ключовий метод для запобігання зайвим перебудуванням залежних віджетів.
  • Вбудовані InheritedWidget: Theme, MediaQuery, Localizations, Directionality, DefaultTextStyle.
  • Створення власного InheritedWidget включає успадкування класу, передачу даних через конструктор та статичний метод of.
  • Provider та Riverpod побудовані поверх InheritedWidget і додають ChangeNotifier, Consumer, Selector та спрощений синтаксис.
  • InheritedWidget вирішує проблему prop drilling і є фундаментом реактивного керування станом у Flutter.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також