تحسين الأداء

المُنشئات الثابتة والودجات في وقت التصريف

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

المُنشئات الثابتة والودجات في وقت التصريف

من أكثر تقنيات الأداء تأثيراً والأقل اهتماماً في Flutter هو الاستخدام الصحيح للكلمة المفتاحية const. عندما تُعلّم ودجتاً أو مُنشئه بـ const، يُنشئ Dart ذلك الكائن في وقت التصريف بدلاً من وقت التنفيذ. هذا يعني أن الكائن يُخصَّص مرة واحدة ويُحفظ في الذاكرة، ثم يُعاد استخدامه في كل مرة يُحتاج إليه — فيتخطى الإطار عملية الإنشاء كلياً عند إعادة البناء.

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

ملاحظة: const في Dart ليست نفس final. المتغير final يُسنَد مرة واحدة في وقت التنفيذ. القيمة const تُحدَّد كلياً في وقت التصريف وتُوحَّد: كائنان const متطابقان هما حرفياً نفس الكائن في الذاكرة.

كيف تعمل توحيد الثوابت (Canonicalization)

عند تصريف Dart لشيفرتك، يُقيِّم كل تعبير const ويخزن نتيجته في مجمع الثوابت. إذا ظهر نفس تعبير const أكثر من مرة، يتعرف Dart على أنهما متطابقان ويُعيد نفس مرجع الكائن لكليهما. يُسمى هذا التوحيد (canonicalization).

التوحيد في العمل

void main() {
  // كلا المتغيرين يشيران إلى نفس الكائن في الذاكرة
  const a = EdgeInsets.all(8.0);
  const b = EdgeInsets.all(8.0);

  print(identical(a, b)); // true — نفس مرجع الكائن

  // غير الثابت يُنشئ كائنين منفصلين
  final c = EdgeInsets.all(8.0);
  final d = EdgeInsets.all(8.0);

  print(identical(c, d)); // false — كائنات مختلفة
}

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

كتابة ودجت متوافق مع const

لكي يدعم ودجت الإنشاء بـ const، يجب أن يكون كل حقل يحتفظ به ثابتاً في وقت التصريف. يتطلب هذا شرطين:

  • يجب تعريف جميع الحقول بـ final
  • يجب أن تكون جميع القيم الممررة إلى المُنشئ ثوابت في وقت التصريف (قيم حرفية أو كائنات const أخرى أو تعابير const)

جعل ودجت متوافقاً مع const

// صحيح — جميع الحقول final والمُنشئ const
class StatusBadge extends StatelessWidget {
  final String label;
  final Color color;

  const StatusBadge({
    super.key,
    required this.label,
    required this.color,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: color,
        borderRadius: const BorderRadius.all(Radius.circular(12)),
      ),
      child: Text(
        label,
        style: const TextStyle(color: Colors.white, fontSize: 12),
      ),
    );
  }
}

// الاستخدام — const ممكنة لأن جميع الوسيطات قيم حرفية
const StatusBadge(label: 'نشط', color: Colors.green);
نصيحة: فعّل قاعدتَي التحليل prefer_const_constructors وprefer_const_literals_to_create_immutables في ملف analysis_options.yaml. سيُبرز المحلل كل فرصة مفوتة لـ const كتحذير.

أين لا يمكن تطبيق const

فهم قيود const لا يقل أهمية عن معرفة أماكن استخدامها. لا يمكن أن يكون ودجت أو تعبير const في الحالات التالية:

  • أي وسيط في المُنشئ هو قيمة وقت تنفيذ (متغير أو نتيجة استدعاء دالة أو خاصية من الحالة)
  • يصل الودجت إلى BuildContext (مثل استدعاء Theme.of(context) أو MediaQuery.of(context) داخل المُنشئ)
  • الصنف يحتوي على حقل قابل للتغيير (غير مُعلَّن بـ final)
  • جسم المُنشئ ينفذ أي حساب يعتمد على بيانات وقت التنفيذ

نقاط القرار: const مقابل غير const

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

  @override
  State<MyScreen> createState() => _MyScreenState();
}

class _MyScreenState extends State<MyScreen> {
  int _counter = 0;
  String _username = 'Alice';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // const — قيم حرفية خالصة، لا اعتماد على وقت التنفيذ
        const Text('مرحباً بك في التطبيق'),
        const SizedBox(height: 16),
        const Icon(Icons.home, size: 24),

        // ليس const — يعتمد على متغير الحالة في وقت التنفيذ
        Text('العد: $_counter'),

        // ليس const — يعتمد على متغير وقت التنفيذ
        Text('مرحباً، $_username'),

        // ليس const — Theme.of(context) استدعاء وقت تنفيذ
        Text(
          'نص مُصمَّم',
          style: TextStyle(
            color: Theme.of(context).colorScheme.primary,
          ),
        ),

        // ElevatedButton نفسه ليس const بسبب onPressed
        // لكن child يمكن أن يكون const
        ElevatedButton(
          onPressed: () => setState(() => _counter++),
          child: const Text('زيادة'), // child يمكن أن يكون const
        ),
      ],
    );
  }
}
تحذير: لا تُجبر const على ودجت يحتفظ ببيانات ديناميكية. سيرفض Dart تصريفه برسالة خطأ واضحة. الخطر الحقيقي هو العكس: نسيان const على ودجات يمكن أن تكون ثابتة، مما يتسبب في إعادات بناء غير ضرورية في الشجرة.

const في القوائم والخرائط والودجات المتداخلة

تتسرب الكلمة المفتاحية const إلى كل عنصر في مجموعة ثابتة أو شجرة ودجات ثابتة متداخلة. تحتاج كتابتها مرة واحدة فقط عند المستوى الخارجي الأعلى؛ يستنتجها Dart لجميع المواضع المتداخلة:

تسرب const في أشجار الودجات

// كتابة const مرة واحدة يجعل كل القيم الحرفية المتداخلة const
const Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('السطر الأول'),        // const ضمنياً
    SizedBox(height: 8),        // const ضمنياً
    Text('السطر الثاني'),       // const ضمنياً
    Icon(Icons.check),          // const ضمنياً
  ],
);

// الشكل الصريح المعادل (مطوَّل، غير موصى به):
const Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    const Text('السطر الأول'),
    const SizedBox(height: 8),
    const Text('السطر الثاني'),
    const Icon(Icons.check),
  ],
);

الأثر العملي على أداء إعادة البناء

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

النقطة الرئيسية: اجعل كل ودجت وتعبير فرعي const لا يعتمد على قيم وقت التنفيذ. هذا تحسين مجاني تُطبقه أداة التصريف: يُقلل من تخصيصات الكائنات، ويُسرِّع مقارنة الودجات، ويمنع مسَّ شجر فرعية بأكملها أثناء إعادات البناء. يُسهِّل محلل Dart إيجاد كل فرصة مفوتة.