Shared State ด้วย Provider
React แก้ปัญหา shared state ด้วย Context API หรือ Redux — คุณสร้าง context, ครอบ tree ด้วย provider และ consume จาก descendant ใดก็ได้ Provider package ของ Flutter ทำงานแบบเดียวกัน: คุณกำหนด class ChangeNotifier (state ของคุณ), ครอบ tree ด้วย ChangeNotifierProvider และอ่านจาก descendant ใดก็ได้ผ่าน context.watch หรือ context.read ถ้าคุณเคยใช้ React Context กับ useReducer มาก่อน Provider จะรู้สึกคุ้นเคยทันที
การเปรียบเทียบแบบ side-by-side
หัวข้อที่มีชื่อว่า “การเปรียบเทียบแบบ side-by-side”// React: Context API + useReducerconst CounterContext = createContext(null);
function counterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; default: return state; }}
export function CounterProvider({ children }) { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <CounterContext.Provider value={{ state, dispatch }}> {children} </CounterContext.Provider> );}
// Consuming componentfunction CounterDisplay() { const { state } = useContext(CounterContext); return <p>Count: {state.count}</p>;}
function IncrementButton() { const { dispatch } = useContext(CounterContext); return ( <button onClick={() => dispatch({ type: 'increment' })}> Increment </button> );}// Flutter: Provider + ChangeNotifierclass CounterModel extends ChangeNotifier { int _count = 0; int get count => _count;
void increment() { _count++; notifyListeners(); // เทียบเท่ากับ dispatch / setState }}
// ครอบ tree — เหมือนกับ Context.Providervoid main() { runApp( ChangeNotifierProvider( create: (_) => CounterModel(), child: const MyApp(), ), );}
// Consuming widgetsclass CounterDisplay extends StatelessWidget { @override Widget build(BuildContext context) { // context.watch rebuild widget นี้เมื่อ notifyListeners() ถูกเรียก final count = context.watch<CounterModel>().count; return Text('Count: ${count}'); }}
class IncrementButton extends StatelessWidget { @override Widget build(BuildContext context) { // context.read ไม่ subscribe — ปลอดภัยสำหรับ callbacks return ElevatedButton( onPressed: () => context.read<CounterModel>().increment(), child: const Text('Increment'), ); }}ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
),
);
}
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: CounterPage(),
),
),
);
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CounterDisplay(),
SizedBox(height: 24),
IncrementButton(),
],
);
}
}
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Count',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 8),
Text(
'${counter.count}',
style: const TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
),
],
),
),
);
}
}
class IncrementButton extends StatelessWidget {
const IncrementButton({super.key});
@override
Widget build(BuildContext context) {
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => context.read<CounterModel>().increment(),
icon: const Icon(Icons.add),
label: const Text('Increment', style: TextStyle(fontSize: 16)),
);
}
}Riverpod และ Bloc เป็น alternatives ยอดนิยมที่มี features มากกว่า — Provider เป็นจุดเริ่มต้นที่แนะนำ