InheritedWidget — е специален уиджет във Flutter, който предава данни надолу по дървото от уиджети без изрично предаване чрез конструктори. Дъщерните уиджети получават достъп до данните чрез BuildContext и автоматично се абонират за актуализации. Когато данните в InheritedWidget се променят, всички зависими уиджети се преизграждат. Според Flutter API Reference, 2025, InheritedWidget стои в основата на Theme, MediaQuery, Localizations и повечето библиотеки за управление на състоянието.
Основни точки
InheritedWidget — е уиджет, който прави данните си достъпни за всички потомци в Widget Tree. За разлика от обикновен уиджет, който предава данни само чрез конструктор на дъщерни елементи, InheritedWidget позволява на всеки уиджет в поддървото да получи достъп до данните без верига от параметри. Това решава проблема "prop drilling" — предаване на данни чрез множество междинни уиджети, които сами не използват тези данни.
Flutter включва няколко вградени InheritedWidget: Theme (цветова схема и стилове), MediaQuery (размер на екрана, ориентация, плътност на пикселите), Localizations (локализирани низове), Directionality (посока на текста), DefaultTextStyle (стил на текста по подразбиране). Тези уиджети се задават от коренови уиджети като MaterialApp и са достъпни в цялото приложение.
InheritedWidget няма собствено състояние — съхранява данни, предадени чрез конструктора. Когато родителят на InheritedWidget се преизгради с нови данни, методът updateShouldNotify се извиква за сравняване на стари и нови данни. Ако методът върне true, всички зависими уиджети се маркират за преизграждане. Това е прост, но ефективен механизъм за реактивна актуализация.
Механизмът на предаване на данни чрез InheritedWidget се основава на Element Tree. Когато уиджет извика dependOnInheritedWidgetOfExactType, съответният елемент регистрира зависимост от InheritedElement. При промяна на InheritedWidget, InheritedElement уведомява всички зависими елементи, които се преизграждат в следващия кадър.
Методът dependOnInheritedWidgetOfExactType не само намира InheritedWidget в дървото — той абонира текущия елемент за известия. Ако използвате findAncestorWidgetOfExactType вместо dependOn, уиджетът ще получи данни, но няма да се преизгради при тяхната промяна. Това е важна разлика: dependOn е абонамент, findAncestor е еднократно търсене.
Когато уиджет поиска InheritedWidget, Flutter се изкачва по Element Tree от текущия елемент до корена, проверявайки всеки InheritedElement за съвпадение на типа. Първият намерен InheritedElement се връща. Това означава, че най-близкият InheritedWidget в дървото има приоритет — можете да презапишете данни на определено ниво, като поставите InheritedWidget по-близо до потомците.
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, и имплементация на статичен метод of за достъп от потомци. Данните се предават чрез конструктора, а методът updateShouldNotify определя кога зависимите уиджети трябва да се преизградят.
Класът трябва да наследи InheritedWidget и да приема данни чрез конструктора със задължителния параметър child. Данните могат да бъдат от всякакъв тип: примитиви, обекти, функции. Основното правило — данните трябва да бъдат неизменяеми (immutable), за да могат надеждно да се сравняват старите и новите стойности.
Статичният метод of приема BuildContext и връща данните на InheritedWidget. Вътрешно се извиква dependOnInheritedWidgetOfExactType, който търси най-близкия InheritedWidget от указания тип в дървото. Ако InheritedWidget не бъде намерен, методът или хвърля изключение, или връща стойност по подразбиране в зависимост от имплементацията.
За достъп до данните, уиджетът извиква MyWidget.of(context) вътре в метода build. Flutter автоматично абонира уиджета за актуализации. Ако данните се променят, уиджетът ще се преизгради в следващия кадър. Това позволява създаването на чист и декларативен код без излишни параметри.
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 — е ключовият метод на InheritedWidget, който определя дали зависимите уиджети трябва да бъдат уведомени за промяна на данни. Ако методът върне false, зависимите уиджети не се преизграждат, дори ако самият InheritedWidget е получил нова инстанция със същите данни. Това е критично за производителността.
Сравнявайте само полетата, които действително са се променили и влияят на показването. Ако InheritedWidget съдържа 10 полета, но само едно от тях влияе на UI, проверявайте само това поле. За колекции използвайте дълбоко сравнение или неизменяеми структури от данни. Не използвайте == за List или Map, тъй като те се сравняват по референция.
Най-честата грешка — връщане на true без сравнение. Това води до преизграждане на всички зависими уиджети при всяка актуализация на родителя, дори ако данните не са се променили. Втората грешка — връщане на false, когато данните са се променили, което води до остарял UI. Третата — сложно сравнение, което се изпълнява всеки кадър и забавя работата.
InheritedWidget и обратните извиквания (предаване на функции чрез конструктора) решават различни задачи. InheritedWidget е подходящ за данни, които са необходими на много уиджети на различни нива в дървото. Обратните извиквания са удобни за еднопосочно предаване на събития от родител към конкретен потомък или обратно. Изборът зависи от архитектурата на приложението и честотата на промените.
Използвайте InheritedWidget, когато данните са необходими на много уиджети на различни нива на влагане: тема на приложението, потребителски предпочитания, информация за устройството, данни за текущата сесия. InheritedWidget е особено ефективен за "глобални" данни, които се променят рядко, но са необходими в различни части на UI.
Обратните извиквания (callback функции) са подходящи за предаване на събития от дъщерен уиджет към родителски: натискане на бутон, избор на елемент от списък, изпращане на формуляр. Обратните извиквания изрично показват какви действия може да извърши потомъкът и не създават скрити зависимости. За предаване на данни надолу по дървото на малък брой нива също е по-просто да използвате параметри на конструктора.
| Критерий | InheritedWidget | Обратни извиквания |
|---|---|---|
| Посока | Отгоре надолу (родител → потомци) | Отдолу нагоре (потомък → родител) или директно |
| Обхват | Цялото поддърво | Конкретен уиджет |
| Преизграждане | Автоматично при промяна на данни | Изисква ръчен setState |
| Сложност | Средна (нужен е клас InheritedWidget) | Ниска (проста функция) |
Provider и Riverpod — популярни библиотеки за управление на състоянието във Flutter, изградени върху InheritedWidget. Те разширяват неговите възможности: добавят поддръжка на ChangeNotifier, автоматично премахване при демонтиране, мързелива инициализация и опростен синтаксис чрез генерици.
Provider използва InheritedWidget за предаване на обект от всякакъв тип надолу по дървото. ChangeNotifierProvider проследява промени чрез ChangeNotifier и извиква updateShouldNotify при извикване на notifyListeners. Това освобождава разработчика от ръчно създаване на InheritedWidget и имплементация на updateShouldNotify.
Директният InheritedWidget дава повече контрол и не изисква външни зависимости. Provider предоставя готова инфраструктура: Consumer, Selector, MultiProvider, ProxyProvider. Изборът зависи от сложността на приложението. За прости проекти директният InheritedWidget е достатъчен, за големи проекти Provider или Riverpod намаляват шаблонния код.
// Директен 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 прави данните достъпни за всички потомци чрез BuildContext, докато обикновеният уиджет предава данни само чрез конструктора. InheritedWidget също така абонира потомците за актуализации на данни.
Зависимите уиджети се преизграждат само когато updateShouldNotify върне true. Ако методът е имплементиран правилно, преизграждането настъпва само при реална промяна на данните, а не при всяко преизграждане на родителя.
Да, можете да използвате произволен брой InheritedWidget в едно дърво. Всеки предоставя данни от определен тип, а уиджетите могат да получават данни от няколко InheritedWidget едновременно.
dependOn абонира уиджета за актуализации — при промяна на данните уиджетът ще се преизгради. findAncestor извършва еднократно търсене без абонамент и уиджетът няма да научи за промените в данните.
За просто състояние (тема, настройки) InheritedWidget е достатъчен. За сложно състояние с бизнес логика използвайте Provider, Riverpod или BLoC — те са изградени върху InheritedWidget и добавят необходимата инфраструктура.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също