Element Tree هو طبقة وسيطة في Flutter تربط Widget Tree التصريحي مع RenderObject Tree الأمرّي. على عكس الـ Widgets التي يُعاد إنشاؤها في كل إعادة بناء، تبقى العناصر بين التحديثات وتدير الحالة والمفاتيح ودورة الحياة. وفقًا Flutter API Reference, 2025، فإن فهم Element Tree ضروري للعمل الفعّال مع المفاتيح وتحسين الأداء وتصحيح السلوك غير المتوقع للـ Widgets.
الخلاصة
Element Tree هو تسلسل هرمي وسيط في Flutter يُنشأ بناءً على Widget Tree ويدير إدراج الـ Widgets في التطبيق. كل نسخة عنصر تقابل Widget واحدًا في الشجرة وتخزّن مرجعًا إليه. الفرق الرئيسي بين العنصر والـ Widget هو أن العنصر يحتفظ بموقعه في الشجرة بين عمليات إعادة البناء، بينما يمكن إعادة إنشاء الـ Widget في كل استدعاء build.
بدون Element Tree، لم يكن Flutter قادرًا على تحديث الواجهة بكفاءة. إذا كانت كل إعادة بناء تُعيد إنشاء RenderObject Tree، سيكون الأداء منخفضًا بشكل غير مقبول. Element Tree يعمل كمثبّت: يحتفظ بمراجع RenderObject و State بين التحديثات، مما يسمح لـ Flutter بتطبيق الحد الأدنى من التغييرات فقط على شجرة العرض.
يستخدم Flutter ثلاثة أنواع رئيسية من العناصر: StatelessElement لـ StatelessWidget، و StatefulElement لـ StatefulWidget، و LeafRenderObjectElement و SingleChildRenderObjectElement و MultiChildRenderObjectElement لـ RenderObjectWidget. كل نوع متخصص لفئة الـ Widget الخاصة به ويحدد كيفية تفاعل العنصر مع RenderObject.
البنية ثلاثية الطبقات في Flutter تتكون من Widget Tree (الإعدادات)، و Element Tree (الإدارة)، و RenderObject Tree (العرض). Element Tree هو حلقة الوصل: يقرأ الإعدادات من Widget ويُمرر الأوامر إلى RenderObject. بدون Element Tree، لا يمكن للإطار مزامنة الوصف التصريحي مع العرض الفعلي بكفاءة.
عندما يُدرج عنصر في الشجرة، يتحقق من نوع الـ Widget. إذا كان الـ Widget هو RenderObjectWidget، يُنشئ العنصر RenderObject المقابل ويُضيفه إلى RenderObject Tree. إذا كان الـ Widget هو LeafRenderObjectWidget، يُنشئ العنصر RenderObject طرفيًا. بالنسبة لـ StatelessWidget و StatefulWidget، يدير العنصر ببساطة إدراج العناصر الفرعية.
abstract class Element {
Widget widget;
Element? parent;
List<Element>? children;
void mount(Element? parent, dynamic newSlot);
void update(Widget newWidget);
void unmount();
}
هذا الكود المبسّط يُظهر البنية الأساسية لـ Element. كل عنصر يُخزّن مرجعًا للـ Widget الحالي والعنصر الأب والعناصر الفرعية. تُدير طرق mount و update و unmount دورة حياة العنصر و RenderObject المرتبط به.
كل عنصر في Flutter يمر بسلسلة من مراحل دورة الحياة: الإنشاء، التركيب، التحديث، والإزالة. فهم هذه المراحل ضروري لتصحيح السلوك غير المتوقع، خاصة عند العمل مع الرسوم المتحركة والعمليات غير المتزامنة وإدارة الحالة.
يُتاح العنصر عن طريق استدعاء طريقة createElement الخاصة بالـ Widget. بالنسبة لـ StatelessWidget، يُنشأ StatelessElement؛ وبالنسبة لـ StatefulWidget، يُنشأ StatefulElement (الذي يُنشئ أيضًا كائن State). بالنسبة لـ RenderObjectWidget، يُنشأ RenderObjectElement المناسب. يحدث إنشاء العنصر عند أول ظهور للـ Widget في Widget Tree.
أثناء التركيب، يُضاف العنصر إلى Element Tree ويحصل على عنصر أب. بالنسبة لـ RenderObjectElement، يُنشئ التركيب أيضًا RenderObject ويُدرجه في RenderObject Tree. إذا كان الـ Widget هو StatefulWidget، تُستدعى طريقة initState لكائن State في هذه المرحلة.
عندما يُعاد بناء الـ Widget بإعدادات جديدة، يستقبل العنصر الـ Widget الجديد عبر طريقة update. يُقارن العنصر نوع الـ Widget القديم والجديد: إذا تطابقت الأنواع، يُحدّث العنصر إعداداته؛ وإلا، يُفك العنصر ويُتاح عنصر جديد. يُسمى هذا "تغيير الـ Widget" وهو سبب فقدان الحالة عند تغيير الأنواع.
عند إزالة الـ Widget من Widget Tree، تُستدعى طريقة unmount الخاصة بالعنصر. يُحذف العنصر من Element Tree، ويُحذف RenderObject من RenderObject Tree، وبالنسبة لـ StatefulWidget تُستدعى طريقة dispose لكائن State. بعد الفك، يمكن إعادة استخدام العنصر إذا ظهر الـ Widget مرة أخرى في نفس الموقع.
المفاتيح (Key) هي آلية تعريف للعناصر تسمح لـ Flutter بمطابقة الـ Widgets من Widget Tree القديم والجديد ليس حسب الموقع، بل حسب معرف فريد. المفاتيح بالغة الأهمية عند العمل مع القوائم الديناميكية حيث يمكن أن يتغير ترتيب العناصر: إضافة أو إزالة أو إعادة ترتيب العناصر.
بدون مفتاح، يقوم Flutter بمطابقة العناصر حسب موقعها في الشجرة: يُستبدل العنصر في الموضع 0 من الشجرة القديمة بالـ Widget في الموضع 0 من الشجرة الجديدة. إذا تغير الترتيب، تختلط العناصر وقد تُفقد الحالة أو تُربط ببيانات غير صحيحة. يجبر المفتاح Flutter على البحث عن عنصر حسب المعرف بدلاً من الموقع.
ValueKey يستخدم قيمة بسيطة (سلسلة نصية، رقم) لتحديد عنصر. ObjectKey يستخدم مرجع كائن — مناسب عندما لا يكون للعنصر معرف نصي مستقر. UniqueKey يُنشئ معرفًا فريدًا عند كل إنشاء — يُستخدم عندما يجب أن تكون كل نسخة من الـ Widget فريدة.
Column(
children: items.map((item) => TodoItem(
key: ValueKey(item.id),
title: item.title,
isDone: item.isDone,
)).toList(),
)
في هذا المثال، ValueKey مع item.id يضمن أن كل TodoItem يحتفظ بحالته (مثل تركيز حقل الإدخال) عند تغيير ترتيب العناصر في القائمة. بدون مفتاح، كان العنصر في الموضع الأول سيحصل على حالة العنصر السابق في نفس الموضع.
الحالة (State) في Flutter لا تُخزّن في الـ Widgets، بل في العناصر. عندما يُعاد بناء StatefulWidget ويُنشئ نسخة جديدة من الـ Widget، يحتفظ StatefulElement المقابل بمرجع لكائن State القديم. يرتبط الـ Widget الجديد بالـ State الموجود، مما يسمح بالاحتفاظ بالبيانات بين عمليات إعادة البناء.
أثناء إعادة بناء الـ Widget، يُنشئ Flutter نسخة جديدة من StatefulWidget، لكن StatefulElement المقابل يبقى في Element Tree. يستدعي العنصر طريقة update على State، مُمررًا الـ Widget الجديد. وهكذا، يتم الحفاظ على كائن State وبياناته. يحدث فقدان الحالة فقط عند تغيير نوع الـ Widget، أو تغيير المفتاح، أو إزالة العنصر من الشجرة.
InheritedElement هو عنصر خاص يسمح للعناصر الفرعية باستقبال بيانات من InheritedWidget أب دون تمرير صريح عبر المنشئات. عندما يتغير InheritedWidget، يُعلم InheritedElement جميع العناصر المعتمدة، التي تُعاد بناؤها. هذه الآلية هي أساس Theme و MediaQuery و Provider.
Element Tree يستهلك ذاكرة ويؤثر على سرعة العرض الأول. كل عنصر يشغل قدرًا معينًا من الذاكرة: مرجع للـ Widget، ومرجع للأب، وقائمة بالعناصر الفرعية، وفتحة، وحقول إضافية لـ RenderObjectElement. تحسين Element Tree يُقلّل وقت بدء التشغيل ويُخفّض استهلاك الذاكرة.
يحاول Flutter إعادة استخدام العناصر أثناء إعادة البناء. إذا كان الـ Widget في الإعدادات الجديدة له نفس النوع والمفتاح، لا يُعاد إنشاء العنصر — بل يُحدّث. هذا أسرع بكثير من إنشاء عنصر جديد مع تركيبه لاحقًا. ومع ذلك، عندما يتغير النوع أو المفتاح، يُفك العنصر القديم ويُنشأ عنصر جديد من الصفر.
RepaintBoundary يُنشئ RenderRepaintBoundary منفصلًا في RenderObject Tree يعزل جزءًا من الشجرة. على مستوى Element Tree، لا يُنشئ RepaintBoundary عنصرًا خاصًا — بل يستخدم SingleChildRenderObjectElement. يظهر الفرق على مستوى RenderObject: عندما يتغير محتوى RepaintBoundary، يُعاد طلاء المنطقة المعزولة فقط.
| العملية | بدون RepaintBoundary | مع RepaintBoundary |
|---|---|---|
| إعادة الطلاء | الشاشة بأكملها | فقط المنطقة المعزولة |
| الوقت | ~16 مللي ثانية عند 60 إطارًا في الثانية | ~2-5 مللي ثانية |
| الذاكرة | أقلية | + بضعة كيلوبايتات لكل طبقة |
كما يُظهر الجدول، RepaintBoundary يُقلّل بشكل كبير وقت إعادة الطلاء عن طريق عزل المنطقة المتغيرة. على مستوى Element Tree، لا يتطلب ذلك أي إعدادات إضافية — يكفي تغليف الـ Widget المتغير في RepaintBoundary.
الأسئلة الشائعة
Widget Tree هو إعدادات يُعاد إنشاؤها في كل إعادة بناء. Element Tree هو بنية ثابتة تبقى بين التحديثات وتدير الحالة و RenderObject ودورة حياة الـ Widgets.
بدون Element Tree، كان سيتعين على Flutter إعادة إنشاء RenderObject Tree في كل تغيير حالة، مما كان سيُسبب تأخيرات كبيرة. يحتفظ Element Tree بـ RenderObject و State، مما يسمح بتطبيق الحد الأدنى من التغييرات فقط.
يُحذف العنصر عندما يختفي الـ Widget المقابل من Widget Tree، أو عندما يتغير نوع الـ Widget (مثل استبدال Column بـ Row) أو المفتاح. عند الفك، تُستدعى dispose في State.
المفاتيح تُغيّر خوارزمية المطابقة: بدلاً من البحث عن عنصر حسب الموقع، يبحث Flutter عن عنصر حسب قيمة المفتاح. هذا يسمح بالحفاظ على الحالة عند تغيير ترتيب أو عدد الـ Widgets.
نعم، عبر BuildContext، وهو تجريد للعنصر. تعمل طريقتا findAncestorWidgetOfExactType و dependOnInheritedWidgetOfExactType مع Element Tree، متجهتين للأعلى عبر شجرة العناصر.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا