Kodokon kodokon.com

الأداء: const، وعمليات إعادة البناء، والمفاتيح، وDevTools

تخلّص من عمليات إعادة البناء عديمة الفائدة بفضل const والمفاتيح والتوفيق (reconciliation)، ثم قِس باستخدام DevTools.

10 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

تتعامل Flutter مع ثلاث أشجار: الودجات (تكوينات غير قابلة للتغيير، قابلة للتخلص منها)، وعناصر Element (نُسَخ حيّة تحمل الحالة) وكائنات العرض (التخطيط، الرسم). إن إعادة بناء ودجت أمر زهيد؛ أما المكلف فهو التخطيط والرسم المتتالي. وأثناء إعادة البناء، يطبّق Element.updateChild ثلاث قواعد بالترتيب: إذا كان الودجت الجديد مطابقًا (النسخة نفسها) للقديم، تُختصَر الشجرة الفرعية بأكملها؛ وإذا كان Widget.canUpdate محقّقًا (النوع runtimeType نفسه، والمفتاح key نفسه)، يُحتفَظ بالعنصر ويُعاد تكوينه فحسب؛ وإلا، يُدمَّر العنصر ثم يُعاد إنشاؤه، بما في ذلك الحالة. تستغل الكلمة المفتاحية const القاعدة الأولى: فالتعبير الثابت يُقنَّن (canonicalize) بواسطة المُصرِّف، وتعود النسخة نفسها في كل بناء، ويكون identical محقّقًا - فيُتخطّى الفرع بأكمله.

DART
import 'package:flutter/material.dart';

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

  @override
  State<CounterScreen> createState() =>
      _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const _Header(),
        Text('Total: $_count'),
        FilledButton(
          onPressed: () => setState(() => _count++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

class _Header extends StatelessWidget {
  const _Header();

  @override
  Widget build(BuildContext context) {
    debugPrint('build _Header');
    return const Text('Counter');
  }
}
يظهر السجلّ مرة واحدة فقط: const يتخطّى الشجرة الفرعية.

بالنسبة لقوائم الأبناء، يقرن التوفيق (reconciliation) أولًا حسب الموضع. أعِد ترتيب عناصر ذات حالة (stateful) دون مفاتيح، وسيحتفظ كل عنصر بحالة شاغل موضعه السابق: مربّعات اختيار مؤشّرة في المكان الخطأ، وحقول نصية متبادَلة. ويغيّر Key قاعدة الاقتران: إذ يعثر الخوارزم على العنصر الذي يحمل المفتاح نفسه، حتى عند نقله داخل القائمة. استخدم ValueKey مع مُعرِّف عملي (business) ثابت. ويذهب GlobalKey إلى أبعد من ذلك - فهو يتيح لك إعادة تأصيل شجرة فرعية بأكملها (reparent) دون فقدان حالتها - لكن له تكلفة حقيقية (سجلّ عام، وقيد تفرّد عبر الشجرة كلها): احتفظ به للحاجات الحقيقية مثل Form أو إعادة التأصيل.

DART
import 'package:flutter/material.dart';

class TodoList extends StatelessWidget {
  const TodoList({super.key, required this.items});

  final List<String> items;

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        for (final item in items)
          Dismissible(
            key: ValueKey(item),
            onDismissed: (direction) {},
            child: ListTile(title: Text(item)),
          ),
      ],
    );
  }
}
دون مفتاح ثابت، يُطلق Dismissible خطأً بمجرد أن تحذف.

قِس قبل أن تُحسِّن. في DevTools، يتتبّع عرض الأداء (Performance) زمن كل إطار للخيطين الحرِجين: UI (كود Dart الخاص بك: البناء، التخطيط) وraster (تنقيط الطبقات). والميزانية نحو 16 مِلّي ثانية عند 60 هرتز. والتقطّع (jank) على جانب raster لا يُصلَح بتحسين عمليات البناء لديك: فهو ناتج عن تكلفة الرسم (الظلال، saveLayer، الصور المفرطة الحجم). فعِّل Track widget builds لترى مَن يعيد البناء، وdebugRepaintRainbowEnabled لتصوّر المناطق المُعاد رسمها - إذ يتغير لون الحدّ في كل عملية إعادة رسم. ويعزل RepaintBoundary شجرة فرعية في طبقتها الخاصة: فمؤشّر دوّار يعيد الرسم باستمرار لم يعد يُلوّث جيرانه.

DART
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  debugRepaintRainbowEnabled = true;
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      showPerformanceOverlay: true,
      home: Scaffold(
        body: Center(
          child: RepaintBoundary(
            child: CircularProgressIndicator(),
          ),
        ),
      ),
    );
  }
}
طبقة الإطارات + قوس قزح إعادة الرسم: ثنائي التشخيص.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. لماذا يُتجاهَل ودجت const عندما يعيد أبوه البناء؟
    • النسخة قانونية (canonical): فـ identical محقّق، ويختصر Element.updateChild الشجرة الفرعية بأكملها
    • يزيل المُصرِّف الودجت من الشجرة في وضع release
    • ودجات const ليس لها دالة build
  2. تعيد ترتيب قائمة من ودجات ذات حالة (stateful) دون مفاتيح. ماذا يحدث؟
    • تُقرَن العناصر حسب الموضع: فيرث كل عنصر حالة شاغل موضعه السابق
    • تُطلق Flutter استثناءً في وضع debug
    • تتبع الحالة تلقائيًا كل ودجت مُنقَّل
    • ترفض القائمة إعادة البناء
  3. ماذا يقيس الرسمان البيانيان في طبقة الأداء (performance overlay)؟
    • زمن كل إطار لخيط UI (كود Dart) وخيط raster (عرض الطبقات)
    • الذاكرة المخصَّصة وعدد الودجات
    • زمن البناء وزمن جمع المهملات (garbage collection)