تخلّص من عمليات إعادة البناء عديمة الفائدة بفضل const والمفاتيح والتوفيق (reconciliation)، ثم قِس باستخدام DevTools.
افتح هذا الدرس في Kodokonتتعامل Flutter مع ثلاث أشجار: الودجات (تكوينات غير قابلة للتغيير، قابلة للتخلص منها)، وعناصر Element (نُسَخ حيّة تحمل الحالة) وكائنات العرض (التخطيط، الرسم). إن إعادة بناء ودجت أمر زهيد؛ أما المكلف فهو التخطيط والرسم المتتالي. وأثناء إعادة البناء، يطبّق Element.updateChild ثلاث قواعد بالترتيب: إذا كان الودجت الجديد مطابقًا (النسخة نفسها) للقديم، تُختصَر الشجرة الفرعية بأكملها؛ وإذا كان Widget.canUpdate محقّقًا (النوع runtimeType نفسه، والمفتاح key نفسه)، يُحتفَظ بالعنصر ويُعاد تكوينه فحسب؛ وإلا، يُدمَّر العنصر ثم يُعاد إنشاؤه، بما في ذلك الحالة. تستغل الكلمة المفتاحية const القاعدة الأولى: فالتعبير الثابت يُقنَّن (canonicalize) بواسطة المُصرِّف، وتعود النسخة نفسها في كل بناء، ويكون identical محقّقًا - فيُتخطّى الفرع بأكمله.
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');
}
}بالنسبة لقوائم الأبناء، يقرن التوفيق (reconciliation) أولًا حسب الموضع. أعِد ترتيب عناصر ذات حالة (stateful) دون مفاتيح، وسيحتفظ كل عنصر بحالة شاغل موضعه السابق: مربّعات اختيار مؤشّرة في المكان الخطأ، وحقول نصية متبادَلة. ويغيّر Key قاعدة الاقتران: إذ يعثر الخوارزم على العنصر الذي يحمل المفتاح نفسه، حتى عند نقله داخل القائمة. استخدم ValueKey مع مُعرِّف عملي (business) ثابت. ويذهب GlobalKey إلى أبعد من ذلك - فهو يتيح لك إعادة تأصيل شجرة فرعية بأكملها (reparent) دون فقدان حالتها - لكن له تكلفة حقيقية (سجلّ عام، وقيد تفرّد عبر الشجرة كلها): احتفظ به للحاجات الحقيقية مثل Form أو إعادة التأصيل.
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)),
),
],
);
}
}قِس قبل أن تُحسِّن. في DevTools، يتتبّع عرض الأداء (Performance) زمن كل إطار للخيطين الحرِجين: UI (كود Dart الخاص بك: البناء، التخطيط) وraster (تنقيط الطبقات). والميزانية نحو 16 مِلّي ثانية عند 60 هرتز. والتقطّع (jank) على جانب raster لا يُصلَح بتحسين عمليات البناء لديك: فهو ناتج عن تكلفة الرسم (الظلال، saveLayer، الصور المفرطة الحجم). فعِّل Track widget builds لترى مَن يعيد البناء، وdebugRepaintRainbowEnabled لتصوّر المناطق المُعاد رسمها - إذ يتغير لون الحدّ في كل عملية إعادة رسم. ويعزل RepaintBoundary شجرة فرعية في طبقتها الخاصة: فمؤشّر دوّار يعيد الرسم باستمرار لم يعد يُلوّث جيرانه.
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(),
),
),
),
);
}
}