تقليل نطاق إعادة البناء إلى أدنى حد
تقليل نطاق إعادة البناء إلى أدنى حد
من أكثر تقنيات أداء Flutter فاعليةً هي تقليل نطاق إعادة البناء: ضمان أنه عند تغيُّر الحالة، يُعاد بناء أصغر شجرة فرعية ممكنة من شجرة الودجات فحسب. إعادات البناء غير الضرورية تُهدر دورات المعالج، وتُسبب التقطع (jank)، وتستنزف البطارية. يتناول هذا الدرس الاستراتيجيات التي يحتاج كل مطوِّر Flutter إلى إتقانها.
لماذا يهم نطاق إعادة البناء؟
كل استدعاء لـ setState() أو notifyListeners() أو إعادة بناء من مدير الحالة يطلق build() على الودجت المتأثر وكل ودجت يُعيده. إذا أعاد بناء Scaffold كبير يضم عشرات الأبناء فقط لأن عدادًا ما تغيّر، فستُشغَّل جميع دوال build() لتلك الودجات الأبناء — حتى وإن كانت مخرجاتها مطابقة تماماً لما كانت عليه.
- إهدار وقت المعالج في
build()لودجات لم تتغيّر مخرجاتها - زيادة الضغط على جامع القمامة (GC) بسبب إنشاء كائنات ودجات مؤقتة
- خطر إسقاط الإطارات عند إعادة بناء ودجات كثيرة في آنٍ واحد
build() على كل ودجت متأثر — ليست بلا تكلفة. تقليل عدد الودجات التي تدخل تلك المرحلة هو خط الدفاع الأول.التقنية 1 — استخراج الودجات
التقنية الأساسية الأولى هي استخراج الشجرات الفرعية المستقلة إلى فئات ودجات خاصة بها. الابن عديم الحالة أو الابن ذو الحالة الذي لا يعتمد على الجزء المتغيِّر من الحالة لن يُعاد بناؤه حين يُعاد بناء الأب، شريطة أن يتمكن Flutter من إيجاده في الشجرة عبر مفتاحه (key) أو موضعه.
قبل الاستخراج: دالة build() ضخمة — كل شيء يُعاد بناؤه
class ShopPage extends StatefulWidget {
const ShopPage({super.key});
@override
State<ShopPage> createState() => _ShopPageState();
}
class _ShopPageState extends State<ShopPage> {
int _cartCount = 0;
@override
Widget build(BuildContext context) {
// كل ابن يُعاد بناؤه عند كل تغيير في _cartCount
return Scaffold(
appBar: AppBar(
title: const Text('المتجر'), // يُعاد بناؤه دون حاجة
actions: [
Badge(
label: Text('$_cartCount'),
child: const Icon(Icons.shopping_cart),
),
],
),
body: Column(
children: [
// هذه القائمة المكلفة تُعاد بناؤها في كل نقرة — إهدار
const ProductGrid(), // لم يُستخرج بعد
ElevatedButton(
onPressed: () => setState(() => _cartCount++),
child: const Text('أضف إلى السلة'),
),
],
),
);
}
}
بعد الاستخراج: ProductGrid مستقل — لا يتأثر بتغييرات السلة
// تم الاستخراج إلى StatelessWidget خاص
class ProductGrid extends StatelessWidget {
const ProductGrid({super.key});
@override
Widget build(BuildContext context) {
// هذه الدالة لا تُستدعى عند تغيير _cartCount
return GridView.builder(
shrinkWrap: true,
itemCount: 20,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (_, index) => ProductCard(index: index),
);
}
}
// الآن build() في _ShopPageState تُعيد بناء Badge فقط
// ProductGrid يُعاد استخدامه كما هو من شجرة العناصر
التقنية 2 — وضع Consumer / Selector في الموضع المناسب
عند استخدام Provider، تغليف ودجت عالي المستوى بـ Consumer يُعيد بناء كل ما يُعيده عند كل تغيير. بدلاً من ذلك، ادفع Consumer (أو Selector) إلى أعمق مستوى ممكن — ويفضَّل أن يُغلِّف فقط الودجت الذي يحتاج البيانات فعلاً.
- Consumer<T> — يُعيد بناء شجرته الفرعية عند كل استدعاء لـ
ChangeNotifier.notifyListeners()، بصرف النظر عمّا تغيّر. - Selector<T, S> — يُعيد البناء فقط عند تغيُّر القيمة المحددة
S(باستخدام مقارنة==)، مما يجعله أكثر دقةً واستهدافًا. - context.select<T, S>() — نفس الفكرة بصورة مضمَّنة دون طبقة ودجت إضافية.
استخدام Consumer و Selector بشكل انتقائي
class CartPage extends StatelessWidget {
const CartPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('سلة المشتريات'),
actions: [
// Selector يُعيد البناء فقط عند تغيُّر itemCount
Selector<CartModel, int>(
selector: (_, cart) => cart.itemCount,
builder: (_, count, __) => Badge(
label: Text('$count'),
child: const Icon(Icons.shopping_cart),
),
),
],
),
body: Column(
children: [
// Consumer هنا يُعيد بناء القائمة عند أي تغيير في السلة
Consumer<CartModel>(
builder: (_, cart, __) => CartItemList(items: cart.items),
),
// هذا التذييل الثابت لا يُعاد بناؤه بسبب تغييرات السلة
const CheckoutFooter(),
],
),
);
}
}
Selector على Consumer متى كنت تعتمد على خاصية واحدة فقط من نموذج كبير. إذا كان CartModel يضم 10 حقول وتحتاج فقط itemCount، فإن Consumer يُعيد البناء عند تغيُّر أي حقل بينما يُعيد Selector البناء فقط عند تغيُّر itemCount نفسه.التقنية 3 — تقسيم دوال build() الكبيرة
نمط مضاد شائع هو وجود دالة build() ضخمة وحيدة تمتد لمئات الأسطر. فضلاً عن مشكلات القراءة، يزيد هذا من نطاق إعادة البناء لأن كل شيء في الدالة يُنفَّذ كوحدة واحدة. تقسيمها إلى دوال أصغر يحسِّن القراءة لكنه لا يمنع إعادة البناء — فقط الاستخراج إلى فئات ودجات مستقلة (لا دوال مساعدة) يحقق ذلك، لأن فئات الودجات وحدها لها عُقد خاصة في شجرة العناصر.
- الدوال المساعدة (مثل
Widget _buildHeader()) مضمَّنة — ليست عُقد عناصر مستقلة وتُعاد بناؤها مع الأب. - فئات الودجات المستخرجة (مثل
class _Header extends StatelessWidget) هي عُقد مستقلة يمكن لـ Flutter تجاوزها حين تكون مدخلاتها دون تغيير.
التقنية 4 — منشئات const وودجات const
تعليم الودجات بـ const يُخبر Flutter بأنها لن تتغيَّر أبداً. يستطيع الإطار تجاوز فحص المقارنة بالكامل للشجرات الفرعية الثابتة، متخطيًا أعمال البناء والمطابقة. دائماً أعلن الودجات الطرفية كـ const حين تكون خصائصها ثوابت وقت الترجمة.
استخدام const لمنع المطابقة غير الضرورية
// كل إعادة بناء للأب تتجاوز مطابقة هذه الودجات:
return Column(
children: [
const SizedBox(height: 24),
const _StaticHeader(), // مستخرج + const = تكلفة صفرية
const Divider(),
DynamicCounter(count: _count), // هذا فقط يُعاد بناؤه
const _StaticFooter(), // يتجاوزه الإطار
],
);
الخلاصة
تقليل نطاق إعادة البناء يعني الدقة الجراحية: حدِّد ما الذي تغيَّر، ثم أعد بناء أصغر شجرة فرعية ممكنة تعرض تلك البيانات. الاستراتيجية ذات الأربعة مستويات هي: (1) استخراج الشجرات الفرعية المستقلة إلى فئات ودجات، (2) دفع Consumer/Selector إلى أعمق مستوى ممكن، (3) استبدال الدوال المساعدة بفئات ودجات في العُقد الحرجة أداءً، و(4) تعليم كل ما لا يتغيَّر بـ const. بتطبيق هذه التقنيات معاً، يمكن القضاء على الغالبية العظمى من إعادات البناء غير الضرورية في أي تطبيق Flutter نموذجي.
const.