تحسين الأداء

التحكم في إعادة بناء الودجات باستخدام المفاتيح

15 دقيقة الدرس 3 من 12

التحكم في إعادة بناء الودجات باستخدام المفاتيح

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

ملاحظة: لا يغيّر Key طريقة عرض الودجت. هو مجرد رمز هوية يستخدمه Flutter أثناء مرحلة المقارنة لتحديد ما إذا كان سيُعيد استخدام العناصر أو يُزيلها أو ينشئها من جديد.

كيف يطابق Flutter الودجات بالعناصر

في كل استدعاء لـ build()، يجوب Flutter الشجرتين القديمة والجديدة بالتوازي. في كل موضع يتحقق من:

  • إذا تطابق runtimeType وkey للودجت الجديد مع العنصر الحالي، يستدعي Flutter update() — ويُحفظ العنصر وحالته.
  • إن لم يتطابقا، يُوقف Flutter العنصر القديم (مع احتمال فقدان الحالة) وينشئ عنصراً جديداً.
  • عند وجود مفتاح، يمكن لـ Flutter البحث بين الأشقاء عن تطابق بدلاً من الاعتماد على الموضع وحده.

أنواع المفاتيح الأربعة

ValueKey

يستخدم قيمة واحدة — عادةً String أو int أو enum — كهوية. يتساوى مفتاحان من نوع ValueKey حين تتساوى قيمتاهما. استخدمه حين يملك كل عنصر في القائمة معرّفاً ثابتاً وفريداً كمعرف قاعدة البيانات أو الـ slug.

// قائمة مهام قابلة لإعادة الترتيب — يحافظ كل بلاط على حالة خانة الاختيار
// لأن المفتاح يتبع العنصر لا الموضع.
ListView(
  children: todos.map((todo) {
    return CheckboxListTile(
      key: ValueKey<int>(todo.id),
      value: todo.isDone,
      onChanged: (v) => setState(() => todo.isDone = v!),
      title: Text(todo.title),
    );
  }).toList(),
)

ObjectKey

يستخدم هوية الكائن الكاملة (عبر identical()) بدلاً من تساوي قيمه. استخدمه حين قد تمتلك كائنان نفس قيم الحقول لكنهما مثيلان مختلفان فعلاً — مثل كائنَي Contact بنفس الاسم لكن بمرجعين مختلفين.

// قد يتشارك جهتا اتصال نفس الاسم؛ نستخدم هوية الكائن للتمييز بينهما.
Column(
  children: contacts.map((contact) {
    return ContactCard(
      key: ObjectKey(contact),
      contact: contact,
    );
  }).toList(),
)

UniqueKey

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

تحذير: لا تنشئ UniqueKey() داخل طريقة build() إلا إذا أردت عمداً أن يفقد الودجت كل حالته في كل إعادة بناء. خزّن المفتاح كحقل في فئة State إذا أردت تفعيل إعادة ضبط لمرة واحدة.

GlobalKey

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

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  // مخزّن كحقل — لا يُعاد إنشاؤه في كل build().
  final _formKey = GlobalKey<FormState>();

  void _submit() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      // المتابعة بتسجيل الدخول
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            validator: (v) => v!.isEmpty ? 'مطلوب' : null,
          ),
          ElevatedButton(
            onPressed: _submit,
            child: const Text('تسجيل الدخول'),
          ),
        ],
      ),
    );
  }
}

الخطأ الكلاسيكي للمفاتيح: عناصر القائمة ذات الحالة

أكثر حالات الفشل تعليمياً هي قائمة قابلة لإعادة الترتيب من عناصر StatefulWidget بلا مفاتيح. حين يتبادل العنصران الأول والثاني موضعيهما، يرى Flutter نفس أنواع الودجات في كل موضع، فيستدعي update() على العناصر الموجودة، ويحدّث التهيئة — لكن كائنات الحالة تبقى في مكانها. النتيجة: الحالة الخاطئة مرتبطة بالعنصر الخاطئ. إضافة ValueKey (أو ObjectKey) مرتبطة بهوية العنصر تُصلح هذا فوراً لأن Flutter يستطيع مطابقة العناصر بالمفتاح عبر تغيير الموضع.

اختيار المفتاح المناسب

  • عناصر القوائم والشبكات ذات المعرفات الثابتةValueKey(item.id)
  • العناصر المُعرَّفة بمرجع الكائنObjectKey(item)
  • فرض إعادة ضبط كاملة لمرة واحدة (مخزّن في State، يُغيَّر إلى UniqueKey جديد عند الحاجة) → UniqueKey()
  • الوصول العابر للشجرة إلى State أو BuildContextGlobalKey (استخدمه بحذر)
  • ودجات عديمة الحالة في قائمة ثابتة → لا حاجة لمفتاح
نصيحة: كقاعدة عامة — إذا كان ودجتك يحمل حالة (رسوم متحركة، متحكمات نص، موضع تمرير، قيم خانات اختيار) وقد يظهر في مجموعة قابلة لإعادة الترتيب أو التصفية، فزوّده دائماً بـ ValueKey أو ObjectKey. تكلفة الأداء لا تُذكر؛ أما فائدة الصحة فجوهرية.

ملخص

تمنح المفاتيح Flutter تحكماً صريحاً في هوية العناصر أثناء التطابق. يطابق ValueKey بقيمة منطقية، ويطابق ObjectKey بهوية المرجع، ويُجبر UniqueKey دائماً على إعادة الإنشاء، ويكشف GlobalKey عن مكونات الودجت الداخلية عبر الشجرة. استخدام المفتاح الصحيح في المكان الصحيح يمنع أخطاء إسناد الحالة الخاطئة الدقيقة ويمنحك تحكماً دقيقاً في متى يحافظ Flutter على الشجرة الفرعية للودجات أو يتخلص منها.