# การจัดการ State ใน Flutter ปี 2026: Riverpod vs Bloc vs GetX — คู่มือเปรียบเทียบฉบับสมบูรณ์ > เปรียบเทียบเชิงปฏิบัติของโซลูชันการจัดการ state ใน Flutter ปี 2026 ครอบคลุม Riverpod 3.0, Bloc 9.0 และ GetX พร้อมตัวอย่างโค้ดจริง การวิเคราะห์ประสิทธิภาพ และกลยุทธ์การ migration - Published: 2026-04-02 - Updated: 2026-04-02 - Author: Anthony Fillion-Maillet - Tags: flutter, state-management, riverpod, bloc, getx, dart - Reading time: 10 min --- การจัดการ state ใน Flutter กำหนดวิธีที่แอปพลิเคชันจัดการการไหลของข้อมูลระหว่าง widget ในปี 2026 โซลูชันสามตัวครองระบบนิเวศ ได้แก่ Riverpod 3.0 ที่มาพร้อมความปลอดภัยระดับ compile-time, Bloc 9.0 ที่มีความสามารถในการติดตาม event ระดับองค์กร และ GetX ที่การใช้งานกำลังลดลงแต่ยังคงมีอยู่ในหลายโปรเจกต์ การเลือกโซลูชันที่เหมาะสมส่งผลโดยตรงต่อความสามารถในการทดสอบ ความสามารถในการขยายระบบ และต้นทุนการบำรุงรักษาระยะยาว > **กรอบการตัดสินใจอย่างรวดเร็ว** > > Riverpod 3.0 เหมาะกับโปรเจกต์ส่วนใหญ่ด้วยความปลอดภัยระดับ compile-time และ boilerplate ที่น้อยที่สุด Bloc 9.0 ยังคงเป็นมาตรฐานสำหรับอุตสาหกรรมที่มีกฎระเบียบเข้มงวดซึ่งต้องการ audit trail แบบ event-driven GetX ควรพิจารณาเฉพาะสำหรับการดูแล codebase ที่มีอยู่โดยไม่มีงบประมาณสำหรับการ migration เท่านั้น ## Riverpod 3.0: ความปลอดภัย Compile-Time และ Auto-Retry Riverpod 3.0 นำเสนอการเปลี่ยนแปลงพื้นฐานในวิธีที่แอปพลิเคชัน Flutter ประกาศและใช้งาน state การสร้างโค้ดอัตโนมัติผ่าน annotation ตรวจจับข้อผิดพลาดของ dependency ในขั้นตอนคอมไพล์แทนที่จะเป็น runtime ซึ่งช่วยกำจัดบั๊กทั้งประเภทที่ก่อนหน้านี้ต้องอาศัยการทดสอบด้วยมือเพื่อค้นพบ กลไก auto-retry สำหรับ provider ที่ล้มเหลวจัดการข้อผิดพลาดของเครือข่ายชั่วคราวโดยไม่ต้องแทรกแซงด้วยมือ เมื่อการคำนวณของ provider ล้มเหลว Riverpod จะลองใหม่โดยอัตโนมัติด้วยเวลาหน่วงที่กำหนดค่าได้ ช่วยลดโค้ด boilerplate สำหรับการกู้คืนจากข้อผิดพลาด ```dart // counter_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter_provider.g.dart'; // Code generation ensures compile-time safety @riverpod class Counter extends _$Counter { @override int build() => 0; // Initial state void increment() => state = state + 1; void decrement() => state = state - 1; void reset() => state = 0; } ``` Annotation `@riverpod` สร้าง boilerplate ทั้งหมดของ provider โดยอัตโนมัติ ความไม่ตรงกันของ type, override ที่ขาดหาย และ dependency แบบวนรอบจะถูกตรวจพบในระหว่างการคอมไพล์ ```dart // user_repository_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user_repository_provider.g.dart'; @riverpod Future currentUser(Ref ref) async { final authService = ref.watch(authServiceProvider); final userId = authService.currentUserId; // Auto-retry on network failure (Riverpod 3.0) final response = await ref.watch( httpClientProvider, ).get('/api/users/$userId'); return User.fromJson(response.data); } ``` Riverpod 3.0 ยังหยุด listener ของ provider โดยอัตโนมัติเมื่อ widget ออกจากหน้าจอ ช่วยลดการคำนวณที่ไม่จำเป็นและเพิ่มประสิทธิภาพแบตเตอรี่บนอุปกรณ์มือถือ ## Bloc 9.0: สถาปัตยกรรม Event-Driven สำหรับแอปพลิเคชันองค์กร Bloc 9.0 บังคับใช้การแยกส่วนอย่างเข้มงวดระหว่าง event, state และ business logic ทุกการเปลี่ยนแปลง state จะถูกแมปกับ event เฉพาะ สร้าง audit trail ที่อุตสาหกรรมที่มีกฎระเบียบต้องการ การตรวจสอบความปลอดภัย mounted ในเวอร์ชัน 9.0 ป้องกันไม่ให้ callback ทำงานบน widget ที่ถูก dispose ไปแล้ว ```dart // authentication_event.dart sealed class AuthenticationEvent {} final class LoginRequested extends AuthenticationEvent { final String email; final String password; LoginRequested({required this.email, required this.password}); } final class LogoutRequested extends AuthenticationEvent {} final class SessionRestored extends AuthenticationEvent { final String token; SessionRestored({required this.token}); } ``` Sealed class ใน Dart 3 รับประกัน exhaustive pattern matching บน event คอมไพเลอร์บังคับให้ทุก event type ต้องมี handler ที่สอดคล้องกัน ```dart // authentication_bloc.dart import 'package:flutter_bloc/flutter_bloc.dart'; class AuthenticationBloc extends Bloc { final AuthRepository _authRepo; final TokenStorage _tokenStorage; AuthenticationBloc({ required AuthRepository authRepo, required TokenStorage tokenStorage, }) : _authRepo = authRepo, _tokenStorage = tokenStorage, super(AuthenticationInitial()) { on(_onLoginRequested); on(_onLogoutRequested); on(_onSessionRestored); } Future _onLoginRequested( LoginRequested event, Emitter emit, ) async { emit(AuthenticationLoading()); try { final token = await _authRepo.login( email: event.email, password: event.password, ); await _tokenStorage.save(token); emit(AuthenticationSuccess(token: token)); } catch (e) { emit(AuthenticationFailure(message: e.toString())); } } Future _onLogoutRequested( LogoutRequested event, Emitter emit, ) async { await _tokenStorage.clear(); emit(AuthenticationInitial()); } Future _onSessionRestored( SessionRestored event, Emitter emit, ) async { emit(AuthenticationSuccess(token: event.token)); } } ``` แต่ละ event handler สร้างการเปลี่ยนแปลง state ที่ชัดเจน Logging middleware สามารถบันทึกทุก event เพื่อวัตถุประสงค์ในการ debug หรือปฏิบัติตามกฎระเบียบ Interface `EmittableStateStreamableSource` ใน Bloc 9.0 ทำให้การทดสอบง่ายขึ้นด้วยการอนุญาตให้สร้าง mock implementation ที่เบา ## Event Transformer ของ Bloc: การจัดการ Input ความถี่สูง Bloc มี event transformer ในตัวที่แก้ปัญหา concurrency ทั่วไป การค้นหาขณะพิมพ์ การกดปุ่มซ้ำ ๆ อย่างรวดเร็ว และ stream ข้อมูลแบบ real-time ล้วนได้รับประโยชน์จากการประมวลผล event แบบ declarative ```dart // search_bloc.dart import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class SearchBloc extends Bloc { final SearchRepository _repository; SearchBloc({required SearchRepository repository}) : _repository = repository, super(SearchInitial()) { // restartable() cancels previous search on new input on( _onQueryChanged, transformer: restartable(), ); // droppable() ignores events while processing on( _onResultSelected, transformer: droppable(), ); } Future _onQueryChanged( SearchQueryChanged event, Emitter emit, ) async { if (event.query.length < 3) { emit(SearchInitial()); return; } emit(SearchLoading()); final results = await _repository.search(event.query); emit(SearchLoaded(results: results)); } Future _onResultSelected( SearchResultSelected event, Emitter emit, ) async { emit(SearchNavigating(result: event.result)); } } ``` Transformer `restartable()` ยกเลิกการค้นหาที่กำลังดำเนินอยู่เมื่อมี input ใหม่เข้ามา ป้องกันผลลัพธ์เก่าเขียนทับผลลัพธ์ใหม่ Transformer `droppable()` ละเว้นการกดซ้ำขณะที่การนำทางกำลังดำเนินอยู่ ## GetX: หนี้ทางเทคนิคและความเป็นจริงของการ Migration GetX ได้รับความนิยมจากความเร็วในการ prototyping และ boilerplate ที่น้อยที่สุด ในปี 2026 ไลบรารีนี้เผชิญกับวิกฤตการบำรุงรักษา ทั้งการอัปเดตที่ไม่สม่ำเสมอ การพึ่งพา maintainer เพียงคนเดียว และความไม่เข้ากันที่เพิ่มขึ้นกับเวอร์ชันล่าสุดของ Flutter SDK แอปพลิเคชัน production ที่ใช้ GetX พบปัญหา lifecycle ของ controller และการรั่วไหลของหน่วยความจำจาก singleton แบบ global ที่เป็น implicit ```dart // counter_controller.dart (GetX pattern) import 'package:get/get.dart'; // Global singleton - difficult to test and scope class CounterController extends GetxController { final count = 0.obs; // Reactive observable void increment() => count.value++; void decrement() => count.value--; // Lifecycle hooks - disposal timing is unpredictable @override void onClose() { // Cleanup may not execute reliably super.onClose(); } } // Usage in widget class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { // Get.put creates a global singleton final controller = Get.put(CounterController()); return Obx(() => Text('${controller.count}')); } } ``` การเรียก `Get.put()` ลงทะเบียน controller เป็น singleton แบบ global ใน flow การนำทางที่ซับซ้อน controller จะคงอยู่เกินขอบเขตที่ตั้งใจไว้ ส่งผลให้สิ้นเปลืองหน่วยความจำ ตัวแปร reactive `.obs` ข้ามระบบการแจ้งเตือน state มาตรฐานของ Flutter ทำให้การรวมเข้ากับ package อื่นไม่น่าเชื่อถือ ## การ Migration จาก GetX ไปยัง Riverpod: ทีละขั้นตอน สำหรับทีมที่ดูแล codebase GetX การ migration ไปยัง Riverpod สามารถดำเนินการได้ทีละขั้น ไลบรารีทั้งสองอยู่ร่วมกันในโปรเจกต์เดียวกันได้ ช่วยให้แปลงทีละหน้าจอโดยไม่ต้องเขียนใหม่ทั้งหมด ```dart // Step 1: Replace GetX controller with Riverpod notifier // Before (GetX) class ProductController extends GetxController { final products = [].obs; final isLoading = false.obs; Future loadProducts() async { isLoading.value = true; products.value = await ProductApi.fetchAll(); isLoading.value = false; } } // After (Riverpod 3.0) @riverpod class ProductList extends _$ProductList { @override Future> build() async { // Auto-retry on failure, auto-pause when off-screen return ProductApi.fetchAll(); } Future refresh() async { ref.invalidateSelf(); } } ``` ```dart // Step 2: Replace widget bindings // Before (GetX) class ProductPage extends StatelessWidget { @override Widget build(BuildContext context) { final ctrl = Get.put(ProductController()); return Obx(() { if (ctrl.isLoading.value) return CircularProgressIndicator(); return ListView.builder( itemCount: ctrl.products.length, itemBuilder: (_, i) => ProductTile(ctrl.products[i]), ); }); } } // After (Riverpod 3.0) class ProductPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productListProvider); return productsAsync.when( loading: () => const CircularProgressIndicator(), error: (err, stack) => ErrorDisplay(error: err), data: (products) => ListView.builder( itemCount: products.length, itemBuilder: (_, i) => ProductTile(products[i]), ), ); } } ``` เวอร์ชัน Riverpod จัดการ state ของ loading, error และ data อย่างชัดเจนผ่าน `AsyncValue.when()` ไม่มี singleton แบบ global ไม่ต้องจัดการ lifecycle ด้วยมือ และ dispose อัตโนมัติเมื่อ widget ถูก unmount ## การเปรียบเทียบประสิทธิภาพ: ความมีประสิทธิผลของ Rebuild ความมีประสิทธิผลของ rebuild ส่งผลโดยตรงต่อ frame rate แต่ละโซลูชันจัดการ rebuild ของ widget แตกต่างกัน และความแตกต่างสามารถวัดได้ในรายการที่มีรายการหลายร้อยรายการ | ตัวชี้วัด | Riverpod 3.0 | Bloc 9.0 | GetX | |--------|-------------|----------|------| | Rebuild แบบเลือก | `select()` filter | `BlocSelector` | `.obs` per field | | ความปลอดภัย compile-time | เต็มรูปแบบ (code gen) | บางส่วน (sealed classes) | ไม่มี | | Auto-dispose | มีในตัว | ต้องทำเอง ผ่าน `close()` | ไม่น่าเชื่อถือ | | หยุดชั่วคราวเมื่อ off-screen | อัตโนมัติ (3.0) | ต้องทำเอง | ไม่รองรับ | | การติดตาม event | Provider observer | Log event ครบถ้วน | ไม่มี | | การแยกการทดสอบ | `ProviderContainer.test()` | `EmittableStateStreamableSource` | ต้องใช้ `Get.testMode` | | ผลกระทบขนาด bundle | ~45KB | ~38KB | ~120KB (รวม routing, DI, HTTP) | เมธอด `select()` ของ Riverpod และ `BlocSelector` ของ Bloc ต่างก็เปิดใช้งาน rebuild อย่างแม่นยำ โดยอัปเดตเฉพาะ subtree ของ widget ที่ขึ้นอยู่กับข้อมูลที่เปลี่ยนแปลง `.obs` ของ GetX ทำได้ละเอียดในระดับเดียวกันต่อ field แต่ขาดการตรวจสอบ compile-time ต่อกราฟของ dependency > **ขนาด Bundle ของ GetX** > > GetX รวม routing, dependency injection, HTTP client และ state management ไว้ในแพ็คเกจเดียว แอปพลิเคชันที่ใช้เฉพาะ state management ยังคงต้อง import ไลบรารีทั้งหมดขนาด 120KB Riverpod และ Bloc เป็นแพ็คเกจที่เน้นเฉพาะด้าน โดยแต่ละตัวทำหน้าที่เดียวอย่างดี ## กลยุทธ์การทดสอบสำหรับแต่ละโซลูชัน ความสามารถในการทดสอบมักเป็นตัวกำหนดว่าโซลูชันใดจะขยายตัวได้ตามการเติบโตของทีม แต่ละไลบรารีมีแนวทางการทดสอบที่แตกต่างกัน ```dart // Riverpod test - isolated container import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod/riverpod.dart'; void main() { test('Counter increments', () { final container = ProviderContainer.test(); // Override dependencies for isolation final counter = container.read(counterProvider.notifier); expect(container.read(counterProvider), 0); counter.increment(); expect(container.read(counterProvider), 1); }); } ``` ```dart // Bloc test - event-driven verification import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { blocTest( 'emits [loading, success] on valid login', build: () => AuthenticationBloc( authRepo: MockAuthRepo(), tokenStorage: MockTokenStorage(), ), act: (bloc) => bloc.add( LoginRequested(email: 'dev@test.com', password: 'secure123'), ), expect: () => [ isA(), isA(), ], ); } ``` `ProviderContainer.test()` ของ Riverpod สร้างกราฟ dependency ที่แยกออกจากกันสำหรับแต่ละ test Helper `blocTest` ของ Bloc ตรวจสอบลำดับการเปลี่ยนแปลง state อย่างแม่นยำ สอดคล้องกับสถาปัตยกรรม event-driven การทดสอบ GetX ต้องตั้งค่า `Get.testMode = true` และจัดการ lifecycle ของ controller ด้วยมือ ซึ่งมักทำให้ test ไม่เสถียรในสภาพแวดล้อม CI > **การเตรียมตัวสัมภาษณ์งาน** > > การจัดการ state ใน Flutter เป็นหนึ่งในหัวข้อที่ถูกถามบ่อยที่สุดในการสัมภาษณ์นักพัฒนา mobile การเข้าใจข้อดีข้อเสียระหว่าง Riverpod, Bloc และ GetX แสดงถึงความเป็นผู้ใหญ่ทางสถาปัตยกรรม ควรฝึกอธิบายว่าแต่ละโซลูชันเหมาะสมเมื่อใดและไม่เหมาะสมเมื่อใด ## เมทริกซ์การตัดสินใจ: การเลือกโซลูชันที่เหมาะสม ข้อจำกัดของโปรเจกต์เป็นตัวกำหนดตัวเลือกที่ดีที่สุด ขนาดทีม ข้อกำหนดด้านกฎระเบียบ และ codebase ที่มีอยู่ล้วนเป็นปัจจัยในการตัดสินใจ **Riverpod 3.0** เหมาะสมเมื่อทีมให้ความสำคัญกับความปลอดภัยระดับ compile-time โปรเจกต์ต้องการ fetching ข้อมูลแบบ async พร้อมการกู้คืนข้อผิดพลาดอัตโนมัติ หรือ codebase เริ่มต้นจากศูนย์ เส้นโค้งการเรียนรู้อยู่ในระดับปานกลาง นักพัฒนาที่คุ้นเคยกับ Provider สามารถเปลี่ยนผ่านได้อย่างเป็นธรรมชาติ **Bloc 9.0** เหมาะสมเมื่อโปรเจกต์ดำเนินงานในอุตสาหกรรมที่มีกฎระเบียบเข้มงวด (fintech, สาธารณสุข) ทีมต้องการการติดตาม event อย่างครบถ้วนเพื่อการตรวจสอบ หรือแอปพลิเคชันจัดการ workflow แบบ concurrent ที่ซับซ้อนอย่างการประมวลผลการชำระเงิน ต้นทุน boilerplate คุ้มค่ากับความสามารถในการบำรุงรักษาในระดับใหญ่ **GetX** เหมาะสมเฉพาะเมื่อดูแล codebase GetX ที่มีอยู่ซึ่งต้นทุนการ migration เกินกว่างบประมาณที่มี การเริ่มโปรเจกต์ใหม่ด้วย GetX ในปี 2026 หมายถึงการสร้างหนี้ทางเทคนิคตั้งแต่วันแรก [เอกสารอย่างเป็นทางการของ Flutter](https://docs.flutter.dev/data-and-backend/state-mgmt/options) ไม่ได้ระบุ GetX ในรายการโซลูชันที่แนะนำ สำหรับการฝึกฝนเชิงลึกเกี่ยวกับรูปแบบการจัดการ state ใน Flutter โมดูล[พื้นฐานการจัดการ state ใน Flutter](/technologies/flutter/interview-questions/state-management-basics) ครอบคลุมแนวคิดพื้นฐานที่ถูกทดสอบในการสัมภาษณ์ โมดูล[รูปแบบ provider](/technologies/flutter/interview-questions/provider-pattern) สำรวจกลยุทธ์ dependency injection ที่ใช้ได้กับทั้งสามโซลูชัน ## สรุป - Riverpod 3.0 มอบความปลอดภัยระดับ compile-time ผ่าน code generation มี retry อัตโนมัติสำหรับ provider ที่ล้มเหลว และรองรับ pause/resume ที่ช่วยลดการใช้แบตเตอรี่บนอุปกรณ์มือถือ - Bloc 9.0 บังคับใช้การเปลี่ยนแปลง state แบบ event-driven พร้อมความสามารถในการตรวจสอบอย่างครบถ้วน ทำให้เป็นมาตรฐานสำหรับแอปพลิเคชันองค์กรในอุตสาหกรรมที่มีกฎระเบียบ - GetX เผชิญกับวิกฤตการบำรุงรักษาในปี 2026 ด้วยการอัปเดตที่ไม่สม่ำเสมอและความไม่เข้ากันของ SDK ที่เพิ่มขึ้น โปรเจกต์ GetX ที่มีอยู่ควรวางแผน migration แบบค่อยเป็นค่อยไปไปยัง Riverpod - การ migration จาก GetX ไปยัง Riverpod ดำเนินการทีละหน้าจอโดยไม่ต้องเขียนใหม่ทั้งหมด เนื่องจากไลบรารีทั้งสองอยู่ร่วมกันในโปรเจกต์เดียวกันได้ - การแยกการทดสอบแตกต่างกันอย่างมาก Riverpod ใช้ `ProviderContainer.test()`, Bloc ใช้ `blocTest` พร้อมการตรวจสอบลำดับ event และ GetX ต้องการการกำหนดค่า test mode แบบ global ที่เปราะบาง - ขนาด bundle มีความสำคัญบนมือถือ Riverpod (~45KB) และ Bloc (~38KB) เป็นแพ็คเกจที่เน้นเฉพาะด้าน ในขณะที่ GetX (~120KB) พกพาฟีเจอร์ที่ไม่ได้ใช้มาด้วย --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx