Provider & Riverpod
Provider: InheritedWidget ที่ ergonomic
หัวข้อที่มีชื่อว่า “Provider: InheritedWidget ที่ ergonomic”การเขียน InheritedWidget ด้วยมือคือ boilerplate Provider ห่อ pattern นั้นด้วย API ที่สะอาด และจับคู่กับ ChangeNotifier — class ง่าย ๆ ที่ถือ state และเรียก notifyListeners() เมื่อ state เปลี่ยน
// 1. model ที่ notify listener เมื่อมันเปลี่ยนclass CartModel extends ChangeNotifier { final List<String> _items = []; List<String> get items => List.unmodifiable(_items);
void add(String item) { _items.add(item); notifyListeners(); // บอก widget ที่ listen อยู่ให้ rebuild }}
// 2. provide มันไว้เหนือ widget ที่ต้องใช้ChangeNotifierProvider( create: (_) => CartModel(), child: const MyApp(),);
// 3. อ่านมัน watch() rebuild เมื่อเปลี่ยน; read() ไม่final cart = context.watch<CartModel>(); // rebuild เมื่อ cart เปลี่ยนcontext.read<CartModel>().add('Book'); // action ครั้งเดียว ไม่ rebuildความต่างระหว่าง watch กับ read คือหัวใจทั้งหมด context.watch<T>() subscribe — widget rebuild เมื่อ model notify context.read<T>() หยิบค่าครั้งเดียวโดยไม่ subscribe — ใช้ใน callback (เช่น onPressed) ที่คุณอยาก ทำ ไม่ใช่ ฟัง สลับสองอันนี้คือ bug ที่พบบ่อยที่สุดของ Provider (ไม่ rebuild ที่ควร หรือ rebuild มากเกิน)
Riverpod: provider ที่ไม่ผูกกับ tree
หัวข้อที่มีชื่อว่า “Riverpod: provider ที่ไม่ผูกกับ tree”Riverpod คือการคิดใหม่โดยผู้เขียนคนเดิมที่แก้จุดหยาบของ Provider หมัดเด็ดของตัวเอง: provider เป็น top-level object ไม่ผูกกับ widget tree ซึ่งทำให้ compile-safe (อ่าน provider ที่ไม่มีอยู่ไม่ได้) และ test กับ combine ได้ง่าย
// provider ประกาศที่ top level — ไม่ได้อยู่ใน widget treefinal counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> { @override int build() => 0; // initial state คืนออกมาจาก build()
void increment() => state++; // การ reassign `state` จะ notify listener}
// ใน ConsumerWidget, `ref` อ่าน provider:class CounterView extends ConsumerWidget { const CounterView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(counterProvider); // rebuild เมื่อเปลี่ยน return TextButton( onPressed: () => ref.read(counterProvider.notifier).increment(), child: Text('$count'), ); }}Notifier<T> คืน initial state ออกมาจาก build() (ไม่ใช่ constructor super(initial)) และการแก้ state จะ notify listener สังเกตว่ามีความต่าง watch/read แบบเดียวกันผ่าน ref ความต่างจาก Provider เป็นเชิงโครงสร้าง: ไม่มี BuildContext lookup, ไม่มี “provider not found” ตอน runtime, และ provider compose กันได้ (provider ตัวหนึ่ง ref.watch อีกตัวได้)
สำหรับ async state ตัวเทียบเท่าคือ AsyncNotifier / AsyncNotifierProvider ที่ build() คืน Future นอกจากนี้ Riverpod ยังมีสไตล์ code-generation ผ่าน @riverpod ที่ generate provider ให้จาก class หรือ function ที่ annotate ไว้
API เดิมอย่าง
StateNotifier/StateNotifierProviderและStateProviderตอนนี้เป็น legacy (deprecated ใน Riverpod 3) — คุณยังเจอใน code เดิมได้ แต่ code ใหม่ควรใช้Notifier/NotifierProvider(หรือ@riverpodcodegen)
เลือกระหว่างสองตัว
หัวข้อที่มีชื่อว่า “เลือกระหว่างสองตัว”flowchart TB prov["Provider InheritedWidget + ChangeNotifier ผูกกับ tree, lookup ตอน runtime"] --> use1["ดีสำหรับ app เล็ก, ทีมที่ใช้อยู่แล้ว"] rp["Riverpod top-level provider compile-safe, test ได้, compose ได้"] --> use2["นิยมสำหรับ app ใหม่, state ซับซ้อนหรือต้อง test"]
ทั้งคู่ยอดเยี่ยม Provider ง่ายกว่าและมีอยู่ทุกที่; Riverpod ทนทานกว่าสำหรับ app ใหญ่ที่ต้อง test และเป็นทิศทางที่ ecosystem กำลังมุ่งไป ตัวไหนก็เป็น default ที่ดีกว่า InheritedWidget ที่เขียนเองสำหรับ shared state จริง