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 را در درخت پیدا میکند — بلکه عنصر جاری را برای اعلانها مشترک میکند. اگر به جای dependOn از findAncestorWidgetOfExactType استفاده میکردید، ویجت دادهها را دریافت میکرد اما با تغییر آنها بازسازی نمیشد. این تفاوت مهمی است: 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 و callbackها (ارسال توابع از طریق سازنده) وظایف متفاوتی را حل میکنند. InheritedWidget برای دادههایی مناسب است که به ویجتهای زیادی در سطوح مختلف درخت نیاز دارند. Callbackها برای انتقال یکطرفه رویدادها از والد به فرزند خاص یا برعکس مناسب هستند. انتخاب به معماری برنامه و دفعات تغییرات بستگی دارد.
زمانی از InheritedWidget استفاده کنید که دادهها به ویجتهای زیادی در سطوح مختلف تو در تو نیاز باشند: تم برنامه، تنظیمات کاربر، اطلاعات دستگاه، دادههای جلسه جاری. InheritedWidget به ویژه برای دادههای "سراسری" که به ندرت تغییر میکنند اما در بخشهای مختلف UI مورد نیاز هستند، مؤثر است.
Callbackها (توابع بازگشتی) برای انتقال رویدادها از ویجت فرزند به والد مناسب هستند: کلیک دکمه، انتخاب آیتم لیست، ارسال فرم. Callbackها به صراحت نشان میدهند که فرزند چه اقداماتی میتواند انجام دهد و وابستگیهای پنهان ایجاد نمیکنند. برای انتقال داده به پایین درخت در تعداد کمی سطح نیز استفاده از پارامترهای سازنده سادهتر است.
| معیار | InheritedWidget | Callbackها |
|---|---|---|
| جهت | از بالا به پایین (والد ← فرزندان) | از پایین به بالا (فرزند ← والد) یا مستقیم |
| محدوده | کل زیردرخت | ویجت خاص |
| بازسازی | خودکار با تغییر دادهها | نیاز به setState دستی |
| پیچیدگی | متوسط (نیاز به کلاس InheritedWidget) | کم (یک تابع ساده) |
Provider و Riverpod — کتابخانههای محبوب مدیریت وضعیت در Flutter هستند که بر روی InheritedWidget ساخته شدهاند. آنها قابلیتهای آن را گسترش میدهند: پشتیبانی از ChangeNotifier، حذف خودکار هنگام جدا شدن، مقداردهی اولیه تنبل و نحو سادهشده از طریق جنریکها.
Provider از InheritedWidget برای انتقال یک شیء از هر نوع به پایین درخت استفاده میکند. ChangeNotifierProvider تغییرات را از طریق ChangeNotifier ردیابی میکند و با فراخوانی notifyListeners، updateShouldNotify را فراخوانی میکند. این کار توسعهدهنده را از ایجاد دستی 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید