setState() — ماهیت، مکانیزم کار و کاربرد

نویسنده: IT Sectr منتشر شده: 2026-07-01 زمان مطالعه: 9 دقیقه

setState() — متد کلیدی State در Flutter است که فریمورک را از تغییر داده‌ها مطلع کرده و بازسازی رابط کاربری را راه‌اندازی می‌کند. طبق مستندات رسمی Flutter (Flutter.dev, 2026)، setState مکانیزم اصلی واکنش‌گرایی در StatefulWidget است: بدون فراخوانی آن، UI از تغییرات فیلدهای State مطلع نمی‌شود و در وضعیت قبلی باقی می‌ماند. این متد یک callback از نوع VoidCallback دریافت می‌کند که درون آن توسعه‌دهنده فیلدهای قابل تغییر را اصلاح می‌کند و پس از آن Flutter به طور خودکار build را برای بازسازی ویجت فراخوانی می‌کند.

نکات اصلی

  • setState() — متد State که ویجت را به عنوان dirty (کثیف) علامت‌گذاری کرده و بازسازی UI را در فریم بعدی برنامه‌ریزی می‌کند
  • Callback — setState یک VoidCallback دریافت می‌کند که درون آن باید تمام تغییرات فیلدهای State مؤثر بر رابط کاربری انجام شود
  • ناهمزمانی — setTimeout یا Future درون setState همزمانی را تضمین نمی‌کنند؛ جهش‌های بعد از await باید درون setState دیگری باشند
  • عملکرد — هر بار فراخوانی setState کل ویجت را بازسازی می‌کند؛ برای به حداقل رساندن از const برای ویجت‌های فرزند استفاده کنید
  • mounted — قبل از فراخوانی setState در callbackهای ناهمزمان حتماً mounted را بررسی کنید، در غیر این صورت — استثنا

setState() چیست؟

setState() — متد داخلی کلاس State در Flutter است که برای اطلاع‌رسانی به فریمورک درباره تغییر وضعیت داخلی ویجت و نیاز به بازسازی UI طراحی شده است. بدون فراخوانی setState، Flutter از تغییرات مطلع نمی‌شود — حتی اگر فیلدهای State اصلاح شده باشند، رابط کاربری تا下一次 بازسازی اجباری توسط والد بدون تغییر می‌ماند.

امضای متد: void setState(VoidCallback fn). callback به صورت همزمان درون setState اجرا می‌شود و فقط پس از اتمام آن، State به عنوان dirty علامت‌گذاری می‌شود. این تضمین می‌کند که تمام تغییرات قبل از بازسازی به صورت اتمی اعمال شوند. طبق Dart Language Specification (Dart Team, 2026)، اتمی بودن setState از شرایط مسابقه جلوگیری می‌کند که در آن build می‌توانست وضعیت نیمه‌به‌روز را ببیند.

setState آرگومان نمی‌گیرد، مقداری برنمی‌گرداند و نمی‌تواند بازنویسی شود. این یک متد نهایی (sealed) از کلاس State است. توسعه‌دهنده نمی‌تواند رفتار آن را تغییر دهد — فقط می‌تواند طبق هدف از آن استفاده کند. تلاش برای فراخوانی setState خارج از State (مثلاً از کلاس دیگر) غیرممکن است، زیرا متد در کلاس State اعلام شده است.

setState وضعیت را تغییر نمی‌دهد — شما تغییر می‌دهید

یک باور غلط رایج — این که setState خودش وضعیت را تغییر می‌دهد. این درست نیست. setState فقط callback ارسال شده را فراخوانی می‌کند (که در آن توسعه‌دهنده فیلدها را تغییر می‌دهد) و سپس به فریمورک درباره نیاز به build سیگنال می‌دهد. callback اجباری است — ارسال null یا callback خالی باعث خطا می‌شود.

setState() چگونه کار می‌کند؟

مکانیزم کار setState() را می‌توان به چهار مرحله تقسیم کرد. مرحله اول — فراخوانی متد با callback. مرحله دوم — اجرای همزمان callback که درون آن فیلدهای State تغییر می‌کنند. مرحله سوم — State در فیلد ویژه _dirty به عنوان dirty (کثیف) علامت‌گذاری می‌شود. مرحله چهارم — در پایان میکروتسک (microtask) جاری، Flutter تمام عناصر dirty را پیمایش کرده و build آن‌ها را به ترتیب ظهور در درخت فراخوانی می‌کند.

جزئیات مهم: setState بلافاصله build را فراخوانی نمی‌کند. Flutter از استراتژی به‌روزرسانی دسته‌ای استفاده می‌کند: تمام عناصر dirty جمع‌آوری شده و در یک فریم بازسازی می‌شوند. این بدان معناست که اگر setState چندین بار در یک بلوک همزمان فراخوانی شود، build فقط یک بار — پس از اتمام تمام تغییرات — اجرا می‌شود. چنین بهینه‌سازی‌ای از بازسازی‌های متعدد در یک فریم جلوگیری می‌کند.

طبق گفته Flutter Engine Team (Google, 2025)، مکانیزم پرچم‌های dirty بر اساس عبور از BuildOwner._dirtyElements است. هر StatefulElement dirty به لیست اضافه شده و در مرحله به‌روزرسانی فریم پردازش می‌شود. اگر ویجت قبل از پردازش از درخت حذف شده باشد، به طور خودکار از لیست عناصر dirty حذف می‌شود.

تضمین‌های setState

  • callback قبل از علامت‌گذاری dirty به صورت همزمان اجرا می‌شود
  • build حداکثر یک بار در هر فریم فراخوانی می‌شود (حتی با setStateهای متعدد)
  • به‌روزرسانی UI در فریم بعدی رخ می‌دهد (معمولاً ~16ms در 60 FPS)
  • پس از dispose فراخوانی ممنوع است — استثنا پرتاب می‌شود
  • در طول build فراخوانی setState ممنوع است — حلقه بی‌نهایت

نمونه کد در Dart

نمونه پایه setState() با افزایش شمارنده. استفاده صحیح را نشان می‌دهد: تغییر فیلد درون callback:

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

  void _increment() {
    setState(() {
      _count++; // جهش فیلد درون callback
    });
  }

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

نمونه با فیلد متنی و کنترلر — setState() برای مدیریت visibility رمز عبور:

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;
});

سه فیلد در یک callback تغییر می‌کنند — build یک بار اجرا شده و تمام تغییرات را همزمان می‌بیند. اگر هر فراخوانی یک setState جداگانه بود، build همچنان یک بار به دلیل پردازش دسته‌ای عناصر dirty اجرا می‌شد.

ناهمزمانی و setState

یکی از مهمترین نکات setState() — رفتار آن با عملیات ناهمزمان. callback 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)، ارسال async-callback به setState یک ضدالگو است، زیرا setState منتظر VoidCallback (تابع همزمان) است، در حالی که تابع async یک Future برمی‌گرداند که نادیده گرفته می‌شود. تغییرات پس از اولین await در چنین callbackی به درستی توسط فریمورک پردازش نخواهند شد.

بررسی mounted در سناریوهای ناهمزمان

قبل از فراخوانی setState() پس از عملیات ناهمزمان همیشه mounted را بررسی کنید:

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

اگر ویجت در حین اجرای عملیات ناهمزمان از درخت حذف شده باشد، mounted false می‌شود و setState فراخوانی نمی‌شود. این از استثنا و نشت منابع جلوگیری می‌کند.

عملکرد و بهینه‌سازی

setState() — مکانیزم راحت اما بالقوه پرهزینه، اگر بدون فکر استفاده شود. هر بار فراخوانی setState کل ویجت و تمام فرزندان آن (اگر const نباشند) را بازسازی می‌کند. در درخت‌های عمیق یا با فراخوانی‌های مکرر، این می‌تواند منجر به افت FPS شود.

استراتژی‌های اصلی بهینه‌سازی: به حداقل رساندن محدوده بازسازی (انتقال بخش‌های متغیر UI به StatefulWidgetهای جداگانه)، استفاده از const برای فرزندان غیرقابل تغییر و اجتناب از فراخوانی setState در ویجت‌های والد اگر فقط یک جزئیات کوچک UX تغییر کرده است. اگر وضعیت با فرکانس بالا به‌روز می‌شود (انیمیشن، جریان داده)، AnimatedBuilder یا ValueListenableBuilder را در نظر بگیرید.

طبق Flutter Performance Best Practices (Flutter.dev, فوریه 2026)، پروفایل‌گیری برنامه‌های واقعی نشان می‌دهد که تا 40٪ از تمام فراخوانی‌های setState را می‌توان با ویجت‌های const فرزند یا builderهای واکنش‌گرا (StreamBuilder, FutureBuilder) جایگزین کرد. این میانگین زمان ساخت فریم را 15–25٪ کاهش می‌دهد.

چه زمانی setState اضافی است

سناریوجایگزینمزیت
انیمیشنAnimatedBuilderفقط ویجت متحرک را بازسازی می‌کند
جریان دادهStreamBuilderبه هر عنصر جریان واکنش نشان می‌دهد
نتیجه آیندهFutureBuilderوضعیت‌های بارگذاری/خطا را مدیریت می‌کند
مقدار محلیValueListenableBuilderبه تغییر یک مقدار واکنش نشان می‌دهد

جایگزین‌های setState

با وجود تطبیق‌پذیری setState()، در پروژه‌های بزرگ عمدتاً برای وضعیت محلی استفاده می‌شود. برای وضعیت سراسری یا اشتراکی از راه‌حل‌های تخصصی استفاده می‌شود که هر کدام setState را جایگزین یا می‌پوشانند.

Provider از ChangeNotifier + notifyListeners به عنوان معادل setState استفاده می‌کند، اما با امکان اشتراک چند ویجت. Bloc از Streams استفاده می‌کند — وضعیت با افزودن رویدادها به StreamController تغییر می‌کند. Riverpod رویکردها را ترکیب می‌کند و مدیریت محلی (StateProvider) و ناهمزمان (AsyncNotifier) را بدون وابستگی به StatefulWidget ارائه می‌دهد. هر سه رویکرد نیاز به فراخوانی دستی setState را از بین می‌برند — به‌روزرسانی UI با تغییر داده‌ها به طور خودکار رخ می‌دهد.

طبق Flutter Community Survey 2025 (Flutter Foundation, دسامبر 2025)، 74٪ از توسعه‌دهندگان حداقل از یک ابزار مدیریت وضعیت علاوه بر setState استفاده می‌کنند. در عین حال، 92٪ همچنان از setState برای داده‌های محلی فیلد متنی، چک‌باکس یا شمارنده ساده استفاده می‌کنند — این بهترین روش (best practice) محسوب می‌شود.

چه زمانی setState را حفظ کنیم

  • وضعیت فقط توسط یک ویجت استفاده می‌شود
  • مقدار ساده بولی یا عددی (فوکوس، visibility، شمارنده)
  • نمونه‌سازی اولیه و آزمایش‌های سریع
  • کنترلرها (TextEditingController, PageController) به هر حال به StatefulWidget نیاز دارند

اشتباهات رایج

اولین و خطرناکترین اشتباه — فراخوانی setState پس از dispose. عملیات ناهمزمان در initState شروع شد، کاربر صفحه را ترک کرد، ویجت حذف شد و callback عملیات ناهمزمان setState را فراخوانی می‌کند — برنامه با استثنا crash می‌کند. راه‌حل — همیشه قبل از فراخوانی mounted را بررسی کنید.

دومین اشتباه — فراخوانی setState درون build. این منجر به حلقه بی‌نهایت می‌شود: build → setState → dirty → build → setState → ... Flutter چنین فراخوانی را مسدود نمی‌کند (شما StackOverflowError دریافت خواهید کرد). setState فقط در پاسخ به رویداد (فشار دکمه، اتمام Future، دریافت داده از جریان) قابل فراخوانی است.

سومین اشتباه — تغییر فیلدهای State بدون فراخوانی setState. توسعه‌دهنده _count++ می‌نویسد و انتظار دارد UI به‌روز شود. Flutter نمی‌تواند تغییرات فیلدها را به طور خودکار ردیابی کند — به سیگنال صریح از طریق setState نیاز دارد. این تفاوت اساسی با فریمورک‌های واکنش‌گرا مانند Vue.js است، where تغییر داده‌ها به طور خودکار به‌روزرسانی را راه‌اندازی می‌کند.

چهارمین اشتباه — فراخوانی setState با callback ناهمزمان (async-lambda). همانطور که در بخش ناهمزمانی توضیح داده شد، تغییرات بعد از await گرفته نمی‌شوند که منجر به باگ‌هایی می‌شود که بازتولید آنها دشوار است. از callback همزمان استفاده کنید و setState را بعد از await فراخوانی کنید.

یادداشت برای setState ایمن

  • همیشه mounted را در callbackهای ناهمزمان بررسی کنید
  • setState را درون build فراخوانی نکنید
  • async-lambda به setState ارسال نکنید
  • فیلدهای State را خارج از setState تغییر ندهید
  • اگر چند فیلد را تغییر می‌دهید — این کار را در یک setState انجام دهید

سوالات متداول

setState() در Flutter چه کاری انجام می‌دهد؟

setState() به Flutter اطلاع می‌دهد که داده‌های داخلی StatefulWidget تغییر کرده و UI نیاز به بازسازی دارد. متد یک callback دریافت می‌کند، آن را به صورت همزمان اجرا می‌کند، ویجت را به عنوان dirty علامت‌گذاری کرده و فراخوانی build را در فریم بعدی برنامه‌ریزی می‌کند.

اگر بعد از تغییر فیلد setState را فراخوانی نکنم چه می‌شود؟

UI به‌روز نمی‌شود. Flutter تغییرات فیلدها را به طور خودکار ردیابی نمی‌کند. مقدار فیلد در حافظه تغییر می‌کند، اما ویجت تا下一次 بازسازی اجباری توسط والد در وضعیت قبلی باقی می‌ماند.

آیا می‌توان setState را درون build فراخوانی کرد؟

نمی‌توان. این منجر به حلقه بی‌نهایت می‌شود: build setState را فراخوانی می‌کند، که ویجت را به عنوان dirty علامت‌گذاری کرده و دوباره build را فراخوانی می‌کند. Flutter چنین وضعیتی را مسدود نمی‌کند — برنامه با StackOverflowError crash می‌کند.

با دو setState متوالی، build چند بار اجرا می‌شود؟

Build یک بار اجرا می‌شود. Flutter تمام عناصر dirty را جمع‌آوری کرده و در پایان فریم به صورت دسته‌ای بازسازی می‌کند. setState دوم قبل از پردازش اولی، عنصر را به همان لیست عناصر dirty اضافه می‌کند — build تکراری رخ نخواهد داد.

mounted چیست و چرا برای setState مهم است؟

mounted — یک پرچم بولی که نشان می‌دهد ویجت هنوز در درخت وجود دارد. اگر پس از عملیات ناهمزمان setState را بدون بررسی mounted فراخوانی کنید و ویجت حذف شده باشد — برنامه با استثنای „setState called after dispose” crash می‌کند.

خلاصه

  • setState() — متد State که Flutter را از تغییر داده‌ها مطلع کرده و بازسازی UI را در فریم بعدی راه‌اندازی می‌کند
  • مکانیزم کار — اجرای همزمان callback، علامت‌گذاری State به عنوان dirty، بازسازی دسته‌ای تمام عناصر dirty در پایان فریم
  • ناهمزمانی — async-callback‌ها در setState کار نمی‌کنند؛ await باید خارج باشد و setState پس از دریافت نتیجه فراخوانی شود
  • mounted — بررسی اجباری قبل از setState در عملیات ناهمزمان برای جلوگیری از استثنا
  • بهینه‌سازی — محدوده بازسازی را از طریق const ویجت‌های فرزند به حداقل برسانید و انیمیشن‌ها را به AnimatedBuilder منتقل کنید
  • جایگزین‌ها — برای وضعیت سراسری از Riverpod, Bloc یا Provider استفاده کنید؛ setState را برای داده‌های محلی نگه دارید
  • قانون — setState را درون build فراخوانی نکنید، async-lambda ارسال نکنید، همیشه mounted را بررسی کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید