setState() — الجوهر، آلية العمل والتطبيق

المؤلف: IT Sectr نُشر: 2026-07-01 وقت القراءة: 9 دق

setState() هو الأسلوب الرئيسي لـ State في Flutter، الذي يخطر الإطار بتغييرات البيانات ويطلق إعادة بناء الواجهة. وفقًا للوثائق الرسمية لـ Flutter (Flutter.dev، 2026)، فإن setState هو الآلية الرئيسية للتفاعلية في StatefulWidget: بدون استدعائه، لن تعلم واجهة المستخدم بالتغييرات في حقول State وستبقى في حالتها السابقة. يقبل الأسلوب VoidCallback، داخله يقوم المطور بتعديل الحقول القابلة للتغيير، وبعد ذلك يستدعي Flutter تلقائيًا build لإعادة بناء الويدجت.

الخلاصة

  • setState() — أسلوب State يضع علامة على الويدجت بأنه متسخ (dirty) ويخطط لإعادة بناء واجهة المستخدم في الإطار التالي
  • الاستدعاء — setState يقبل VoidCallback، داخله يجب أن تكون جميع تغييرات حقول State التي تؤثر على الواجهة
  • اللاتزامنية — setTimeout أو Future داخل setState لا يضمنان التزامن؛ الطفرات بعد await يجب أن تكون داخل setState آخر
  • الأداء — كل استدعاء لـ setState يعيد بناء الويدجت بأكمله؛ للتقليل استخدم ويدجتات فرعية const
  • mounted — قبل استدعاء setState في الاستدعاءات اللاتزامنية، تحقق دائمًا من mounted، وإلا — استثناء

ما هو setState()؟

setState() هو أسلوب مدمج في كلاس State في Flutter، مصمم لإخطار الإطار بأن الحالة الداخلية للويدجت قد تغيرت ويجب إعادة بناء واجهة المستخدم. بدون استدعاء setState، لا يعلم Flutter بالتغييرات — حتى إذا تم تعديل حقول State، ستبقى الواجهة دون تغيير حتى إعادة البناء الإجبارية التالية بواسطة الوالد.

توقيع الأسلوب: void setState(VoidCallback fn). يتم تنفيذ الاستدعاء بشكل متزامن داخل setState، وفقط بعد اكتماله يتم وضع علامة متسخ على State. هذا يضمن تطبيق جميع التغييرات بشكل ذري قبل إعادة البناء. وفقًا لمواصفات لغة Dart (Dart Team، 2026)، تمنع ذرية setState حالات السباق حيث يمكن أن يرى build حالة محدثة جزئيًا.

setState لا يقبل وسائط، لا يعيد قيمة، ولا يمكن تجاوزه. إنه أسلوب نهائي (مختوم) من كلاس State. لا يمكن للمطور تغيير سلوكه — فقط استخدامه كما هو مقصود. محاولة استدعاء setState خارج State (مثلاً، من كلاس آخر) مستحيلة لأن الأسلوب مُعلن في كلاس State.

setState لا يغير الحالة — أنت تغيرها

مفهوم خاطئ شائع هو الاعتقاد بأن setState يغير الحالة بنفسه. هذا غير صحيح. setState فقط يستدعي الاستدعاء الممرر (الذي يقوم المطور فيه بتعديل الحقول) ثم يشير للإطار بالحاجة إلى build. الاستدعاء إلزامي — تمرير null أو استدعاء فارغ سيسبب خطأ.

كيف يعمل setState()؟

يمكن تقسيم آلية عمل setState() إلى أربع مراحل. الأولى — استدعاء الأسلوب مع استدعاء. الثانية — تنفيذ متزامن للاستدعاء، داخله يتم تعديل حقول State. الثالثة — يتم وضع علامة متسخ على State في حقل خاص _dirty. الرابعة — في نهاية المهمة الصغرى الحالية، يتجاوز Flutter جميع العناصر المتسخة ويستدعي build بترتيب ظهورها في الشجرة.

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

وفقًا لفريق محرك Flutter (Google، 2025)، تعتمد آلية العلم المتسخ على تمرير BuildOwner._dirtyElements. كل StatefulElement متسخ يضاف إلى القائمة ويتم معالجته في مرحلة تحديث الإطار. إذا تمت إزالة ويدجت من الشجرة قبل المعالجة، يتم استبعاده تلقائيًا من قائمة العناصر المتسخة.

ضمانات setState

  • يتم تنفيذ الاستدعاء بشكل متزامن قبل وضع علامة متسخ
  • يتم استدعاء build ليس أكثر من مرة لكل إطار (حتى مع استدعاءات setState متعددة)
  • يحدث تحديث واجهة المستخدم في الإطار التالي (عادة ~16ms عند 60 FPS)
  • بعد dispose، استدعاء setState ممنوع — يرمي استثناء
  • أثناء build، استدعاء setState ممنوع — حلقة لا نهائية

أمثلة كود في Dart

مثال أساسي لـ setState() مع زيادة عداد. يوضح الاستخدام الصحيح: تعديل حقل داخل الاستدعاء:

dart
class _CounterState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++; // تغيير الحقل داخل الاستدعاء
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _increment,
      child: Text('$_count'),
    );
  }
}

مثال مع حقل نصي ووحدة تحكم — setState() لإدارة رؤية كلمة المرور:

dart
class _PasswordFieldState extends State<PasswordField> {
  bool _obscured = true;
  final _controller = TextEditingController();

  void _toggleVisibility() {
    setState(() {
      _obscured = !_obscured;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      obscureText: _obscured,
      decoration: InputDecoration(
        suffixIcon: IconButton(
          icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
          onPressed: _toggleVisibility,
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

في هذا المثال، setState() يغير فقط الحقل المنطقي _obscured، مما يؤدي إلى إعادة بناء TextField بأيقونة جديدة ووضع عرض. لا يتم إعادة إنشاء وحدة تحكم النص — يتم تهيئتها مرة واحدة في initState وتحرر في dispose.

طفرات متعددة في setState واحد

إذا كنت بحاجة إلى تغيير عدة حقول، يجب تنفيذ جميع التغييرات داخل setState واحد. هذا يضمن أن build سيرى حالة متسقة:

dart
setState(() {
  _isLoading = false;
  _items = newItems;
  _error = null;
});

ثلاثة حقول تتغير في استدعاء واحد — سيتم تنفيذ build مرة واحدة وسيرى جميع التغييرات في وقت واحد. إذا كان كل استدعاء setState منفصلًا، سيظل build ينفذ مرة واحدة فقط بفضل المعالجة الدفعية للعناصر المتسخة.

اللاتزامنية و setState

أحد أهم الفروق الدقيقة لـ setState() هو سلوكه مع العمليات اللاتزامنية. يتم تنفيذ استدعاء setState بشكل متزامن، ولكن إذا تم استدعاء await داخله، فسيتم تنفيذ الكود بعد await بعد أن يكون setState قد أكمل عمله بالفعل. هذا يعني أن تغييرات الحقول بعد await لن يتم التقاطها بواسطة setState الحالي.

النهج الصحيح: يتم تنفيذ العملية اللاتزامنية خارج setState، ويتم استدعاء setState بعد اكتمالها. كل الكود بين استلام النتيجة واستدعاء setState يعمل في سياق متزامن بعد await:

dart
// صحيح: await خارج setState
Future<void> _loadData() async {
  final result = await ApiService.fetchData();
  setState(() {
    _data = result;
    _isLoading = false;
  });
}

// خطأ: await داخل setState — لا ضمان للتحديث
void _loadDataWrong() {
  setState(() async {
    _data = await ApiService.fetchData(); // setState يعود قبل اكتمال await
    _isLoading = false; // هذا الكود لا يتم التقاطه بواسطة setState
  });
}

وفقًا لوثائق Flutter (Dart async patterns، 2026)، فإن تمرير استدعاء لاتزامني إلى setState هو نمط مضاد لأن setState يتوقع VoidCallback (دالة متزامنة)، بينما الدالة اللاتزامنية تعيد Future الذي يتم تجاهله. التغييرات بعد أول await في مثل هذا الاستدعاء لن يتم معالجتها بشكل صحيح بواسطة الإطار.

التحقق من mounted في السيناريوهات اللاتزامنية

قبل استدعاء setState() بعد عملية لاتزامنية، تحقق دائمًا من mounted:

dart
if (mounted) {
  setState(() => _data = data);
}

إذا تمت إزالة الويدجت من الشجرة أثناء العملية اللاتزامنية، سيصبح mounted false، ولن يتم استدعاء setState. هذا يمنع الاستثناءات وتسرب الموارد.

الأداء والتحسين

setState() هو آلية مريحة ولكنها قد تكون مكلفة إذا استخدمت بدون تفكير. كل استدعاء لـ setState يعيد بناء الويدجت بأكمله وجميع نسله (إذا لم يكونوا const). في الأشجار العميقة أو مع الاستدعاءات المتكررة، قد يؤدي هذا إلى انخفاض FPS.

استراتيجيات التحسين الرئيسية: تقليل منطقة إعادة البناء (استخراج أجزاء واجهة المستخدم القابلة للتغيير إلى StatefulWidgets منفصلة)، استخدام const للأبناء غير القابلين للتغيير، وتجنب استدعاء setState في ويدجتات الوالدين إذا تغيرت تفصيلة UX صغيرة فقط. إذا تم تحديث الحالة بتردد عالٍ (رسوم متحركة، تدفق بيانات)، فكر في AnimatedBuilder أو ValueListenableBuilder.

وفقًا لأفضل ممارسات أداء Flutter (Flutter.dev، فبراير 2026)، يُظهر تحليل أداء التطبيقات الحقيقية أن ما يصل إلى 40% من جميع استدعاءات setState يمكن استبدالها بويدجتات فرعية const أو بناة تفاعلية (StreamBuilder، FutureBuilder). هذا يقلل متوسط وقت بناء الإطار بنسبة 15–25%.

متى يكون setState زائدًا عن الحاجة

السيناريوالبديلالميزة
الرسوم المتحركةAnimatedBuilderيعيد بناء فقط الويدجت المتحرك
تدفق البياناتStreamBuilderيتفاعل مع كل عنصر في التدفق
النتيجة المستقبليةFutureBuilderيدير حالات التحميل/الخطأ
القيمة المحليةValueListenableBuilderيتفاعل مع تغييرات قيمة واحدة

بدائل setState

على الرغم من تعدد استخدامات setState()، في المشاريع الكبيرة يتم استخدامه بشكل أساسي للحالة المحلية. للحالة العالمية أو المشتركة، يتم استخدام حلول متخصصة، كل منها يحل محل أو يغلف setState.

يستخدم Provider ChangeNotifier + notifyListeners كبديل لـ setState، ولكن مع القدرة على اشتراك ويدجتات متعددة. يستخدم Bloc Streams — يتم تغيير الحالة بإضافة أحداث إلى StreamController. يجمع Riverpod بين النهج، مما يوفر إدارة محلية (StateProvider) ولا تزامنية (AsyncNotifier) دون ربط بـ StatefulWidget. جميع النهج الثلاثة تلغي الحاجة إلى استدعاء setState يدويًا — تحديثات واجهة المستخدم تحدث تلقائيًا عند تغيير البيانات.

وفقًا لاستطلاع مجتمع Flutter 2025 (Flutter Foundation، ديسمبر 2025)، 74% من المطورين يستخدمون أداة إدارة حالة واحدة على الأقل بالإضافة إلى setState. في الوقت نفسه، 92% يستمرون في استخدام setState للبيانات المحلية لحقول النص، مربعات الاختيار أو العدادات البسيطة — يعتبر هذا أفضل ممارسة.

متى تبقى على setState

  • الحالة تستخدم بواسطة ويدجت واحد فقط
  • قيمة منطقية أو رقمية بسيطة (التركيز، الرؤية، العداد)
  • النمذجة الأولية والتجارب السريعة
  • وحدات التحكم (TextEditingController، PageController) لا تزال تتطلب StatefulWidget

الأخطاء الشائعة

أول وأخطر خطأ هو استدعاء setState بعد dispose. عملية لاتزامنية بدأت في initState، المستخدم غادر الشاشة، تمت إزالة الويدجت، والاستدعاء اللاتزامني يستدعي setState — يتعطل التطبيق باستثناء. الحل — تحقق دائمًا من mounted قبل الاستدعاء.

الخطأ الثاني هو استدعاء setState داخل build. يؤدي هذا إلى حلقة لا نهائية: build → setState → متسخ → build → setState → ... Flutter لا يمنع مثل هذا الاستدعاء (ستحصل على StackOverflowError). يمكن استدعاء setState فقط استجابة لحدث (ضغطة زر، إكمال Future، بيانات من تدفق).

الخطأ الثالث هو تعديل حقول State دون استدعاء setState. يكتب المطور _count++ ويتوقع أن تتحدّث واجهة المستخدم. لا يستطيع Flutter تتبع تغييرات الحقول تلقائيًا — يحتاج إلى إشارة صريحة عبر setState. هذا فرق جوهري عن الأطر التفاعلية مثل Vue.js، حيث تغييرات البيانات تطلق التحديثات تلقائيًا.

الخطأ الرابع هو استدعاء setState باستدعاء لاتزامني (lambda async). كما هو موصوف في قسم اللاتزامنية، التغييرات بعد await لن يتم التقاطها، مما يؤدي إلى أخطاء يصعب إعادة إنتاجها. استخدم استدعاء متزامن واستدع setState بعد await.

قائمة التحقق لـ setState الآمن

  • تحقق دائمًا من mounted في الاستدعاءات اللاتزامنية
  • لا تستدع setState داخل build
  • لا تمرر lambda async إلى setState
  • لا تعدل حقول State خارج setState
  • إذا كنت تغير عدة حقول — افعل ذلك في setState واحد

الأسئلة الشائعة

ماذا يفعل setState() في Flutter؟

setState() يخطر Flutter بأن البيانات الداخلية لـ StatefulWidget قد تغيرت ويجب إعادة بناء واجهة المستخدم. يقبل الأسلوب استدعاء، ينفذه بشكل متزامن، يضع علامة متسخ على الويدجت، ويخطط لاستدعاء build في الإطار التالي.

ماذا يحدث إذا لم أستدع setState بعد تغيير حقل؟

واجهة المستخدم لن تتحدّث. Flutter لا يتتبع تغييرات الحقول تلقائيًا. قيمة الحقل تتغير في الذاكرة، لكن الويدجت يبقى في حالته السابقة حتى إعادة البناء الإجبارية التالية بواسطة الوالد.

هل يمكن استدعاء setState داخل build؟

لا. يؤدي هذا إلى حلقة لا نهائية: build يستدعي setState، الذي يضع علامة متسخ على الويدجت ويستدعي build مرة أخرى. Flutter لا يمنع هذا الموقف — سيتعطل التطبيق بـ StackOverflowError.

كم مرة سيتم تنفيذ build مع استدعاءين متتاليين لـ setState؟

سيتم تنفيذ build مرة واحدة. Flutter يجمع جميع العناصر المتسخة ويعيد بنائها دفعة في نهاية الإطار. setState الثاني قبل المعالجة ببساطة يضيف العنصر إلى نفس قائمة العناصر المتسخة — لا يحدث إعادة بناء مكررة.

ما هو mounted ولماذا هو مهم لـ setState؟

mounted هو علم منطقي يشير إلى أن الويدجت لا يزال في الشجرة. إذا تم استدعاء setState بعد عملية لاتزامنية دون التحقق من mounted وكانت الويدجت قد أزيلت بالفعل — يتعطل التطبيق باستثناء "setState called after dispose".

الملخص

  • setState() — أسلوب State يخطر Flutter بتغييرات البيانات ويطلق إعادة بناء واجهة المستخدم في الإطار التالي
  • آلية العمل — تنفيذ متزامن للاستدعاء، وضع علامة متسخ على State، إعادة بناء دفعة لجميع العناصر المتسخة في نهاية الإطار
  • اللاتزامنية — الاستدعاءات اللاتزامنية في setState لا تعمل؛ await يجب أن يكون خارجًا، و setState بعد الحصول على النتيجة
  • mounted — تحقق إلزامي قبل setState في العمليات اللاتزامنية لمنع الاستثناءات
  • التحسين — قلل منطقة إعادة البناء من خلال ويدجتات فرعية const وانقل الرسوم المتحركة إلى AnimatedBuilder
  • البدائل — للحالة العالمية استخدم Riverpod أو Bloc أو Provider؛ أبق setState للبيانات المحلية
  • القاعدة — لا تستدع setState داخل build، لا تمرر lambdas async، تحقق دائمًا من mounted

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا