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 ببيانات جديدة، يُستدعى method 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 إلزامي. يمكن أن تكون البيانات من أي نوع: أولية، كائنات، دوال. القاعدة الرئيسية هي أن البيانات يجب أن تكون غير قابلة للتغيير بحيث يمكن مقارنة القيم القديمة والجديدة بشكل موثوق.

الخطوة 2: طريقة ثابتة of

الطريقة الثابتة of تأخذ BuildContext وتعيد بيانات InheritedWidget. داخلياً، تستدعي dependOnInheritedWidgetOfExactType، التي تجد أقرب InheritedWidget من النوع المحدد في الشجرة. إذا لم يتم العثور على InheritedWidget، تطرح الطريقة استثناءً أو تعيد قيمة افتراضية حسب التنفيذ.

الخطوة 3: الاستخدام في العناصر

للوصول إلى البيانات، يستدعي العنصر MyWidget.of(context) داخل طريقة البناء. يشترك 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 حقول لكن واحداً فقط يؤثر على الواجهة، تحقق من ذلك الحقل فقط. للمجموعات، استخدم المقارنة العميقة أو هياكل البيانات غير القابلة للتغيير. لا تستخدم == مع List أو Map، حيث تتم المقارنة حسب المرجع.

  • الأنواع الأولية — استخدم المقارنات المباشرة: oldWidget.value != value.
  • الكائنات غير القابلة للتغيير — استخدم == المُعاد تعريفه: oldWidget.data != data (إذا كانت data تعيد تعريف ==).
  • المجموعات — استخدم listEquals، mapEquals من package:flutter/foundation.dart.

أخطاء في تنفيذ updateShouldNotify

الخطأ الأكثر شيوعاً هو إعادة true بدون مقارنة. هذا يتسبب في إعادة بناء جميع العناصر التابعة عند كل تحديث للوالد، حتى إذا لم تتغير البيانات. الخطأ الثاني هو إعادة false عندما تغيرت البيانات، مما يؤدي إلى واجهة قديمة. الثالث هو المقارنة المعقدة التي تُنفذ كل إطار وتبطئ الأداء.

InheritedWidget مقابل الاستدعاءات: أيهما تختار؟

InheritedWidget والاستدعاءات (تمرير الدوال عبر المنشئات) يحلان مشاكل مختلفة. InheritedWidget مناسب للبيانات التي تحتاجها العديد من العناصر في مستويات مختلفة من الشجرة. الاستدعاءات ملائمة لنقل الأحداث باتجاه واحد من الوالد إلى عنصر فرعي معين أو العكس. يعتمد الاختيار على بنية التطبيق وتكرار التحديثات.

متى تستخدم InheritedWidget

استخدم InheritedWidget عندما تكون البيانات مطلوبة من قبل العديد من العناصر في مستويات تداخل مختلفة: سمة التطبيق، إعدادات المستخدم، معلومات الجهاز، بيانات الجلسة الحالية. InheritedWidget فعال بشكل خاص للبيانات «العالمية» التي نادراً ما تتغير لكنها مطلوبة في أجزاء مختلفة من الواجهة.

متى تستخدم الاستدعاءات

الاستدعاءات (دوال رد النداء) مناسبة لنقل الأحداث من عنصر فرعي إلى الوالد: الضغط على زر، اختيار عنصر من قائمة، إرسال نموذج. تشير الاستدعاءات صراحة إلى الإجراءات التي يمكن أن ينفذها العنصر الفرعي، ولا تنشئ تبعيات خفية. لنقل البيانات لأسفل الشجرة عبر عدد صغير من المستويات، من الأسهل أيضاً استخدام معاملات المنشئ.

المعيار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.
  • إنشاء مخصص يشمل وراثة الفئة، نقل البيانات عبر المنشئ، وطريقة ثابتة of.
  • Provider وRiverpod مبنيان فوق InheritedWidget ويضيفان ChangeNotifier وConsumer وSelector وبناء جملة مبسط.
  • InheritedWidget يحل مشكلة prop drilling وهو أساس إدارة الحالة التفاعلية في Flutter.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا