前言
狀態管理一直是 Flutter 開發者最熱烈討論的話題之一。從最早的 setState 到現在百花齊放的各種方案,每隔一段時間社群就會出現「到底該用哪個?」的討論。身為一個獨立開發者,我在過去幾年的專案中實際使用過 Provider、Riverpod 和 Bloc,今天就來分享我的實際體驗和觀察。
Provider:入門首選,簡單直覺
Provider 是 Flutter 官方推薦的狀態管理方案之一,由 Remi Rousselet 開發。它的核心概念很簡單:透過 InheritedWidget 的封裝,讓你可以在 Widget Tree 中輕鬆地傳遞和監聽資料變化。
// 定義一個簡單的 Counter Provider
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 在頂層註冊
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
)
// 在子 Widget 中使用
final counter = context.watch<CounterModel>();
Text('${counter.count}')
優點:
- 學習曲線平緩,概念簡單易懂
- 官方文件完整,社群資源豐富
- 與 Flutter 的 Widget 系統緊密結合
缺點:
- 依賴
BuildContext,在非 Widget 層級存取不方便 - 型別安全性不夠嚴謹,容易遇到 runtime error
- 巢狀多個 Provider 時程式碼可讀性下降
我在早期的小型專案中大量使用 Provider,對於簡單的應用來說綽綽有餘。但當專案規模變大,我開始感受到它的侷限性。
Riverpod:Provider 的進化版
Riverpod 同樣出自 Remi Rousselet 之手,可以把它看作 Provider 的「重新設計版」。它解決了 Provider 的許多痛點,最大的突破是完全不依賴 BuildContext。
// 定義一個 Riverpod Provider
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() {
state++;
}
}
// 在 Widget 中使用(ConsumerWidget)
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
優點:
- 不依賴
BuildContext,可以在任何地方存取狀態 - 編譯期就能檢查型別錯誤,大幅減少 runtime error
- Code generation 讓語法更簡潔
- 內建支援非同步狀態(
AsyncValue) - Provider 之間的依賴關係管理非常優雅
缺點:
- 學習曲線比 Provider 稍高
- Code generation 需要額外設定
build_runner - 版本迭代快,從 v1 到 v2 的遷移需要花時間
我目前的新專案幾乎都採用 Riverpod。尤其是它處理非同步資料的方式,搭配 AsyncValue 的 when 方法,可以非常優雅地處理 loading、error 和 data 三種狀態:
@riverpod
Future<List<AppInfo>> fetchApps(FetchAppsRef ref) async {
final response = await http.get(Uri.parse('/api/apps'));
return parseApps(response.body);
}
// 使用時
final appsAsync = ref.watch(fetchAppsProvider);
return appsAsync.when(
data: (apps) => ListView.builder(...),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('錯誤:$err'),
);
Bloc:企業級首選,架構嚴謹
Bloc(Business Logic Component)由 Felix Angelov 開發,採用事件驅動的架構模式。它強制將業務邏輯與 UI 完全分離,適合需要嚴格架構規範的大型專案。
// 定義事件
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// 定義 Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}
// 在 Widget 中使用
BlocBuilder<CounterBloc, int>(
builder: (context, count) => Text('$count'),
)
優點:
- 架構清晰,業務邏輯與 UI 完全分離
- 事件驅動模式讓狀態變化可追蹤、可測試
- 適合多人團隊協作,降低程式碼衝突
- 豐富的生態系:
bloc_test、hydrated_bloc、replay_bloc
缺點:
- 樣板程式碼(boilerplate)較多
- 簡單功能也需要定義 Event、State、Bloc 三個部分
- 學習曲線最高,需要理解事件驅動的思維模式
Bloc 在我做外包專案或與團隊合作時特別好用。它的架構規範讓不同開發者寫出來的程式碼風格一致,也讓程式碼審查變得更容易。
實際比較:我怎麼選?
| 面向 | Provider | Riverpod | Bloc |
|---|---|---|---|
| 學習難度 | ⭐ 低 | ⭐⭐ 中 | ⭐⭐⭐ 高 |
| 樣板程式碼 | 少 | 中等 | 多 |
| 型別安全 | 普通 | 優秀 | 優秀 |
| 測試性 | 中等 | 優秀 | 優秀 |
| 適合專案規模 | 小型 | 中小型 | 中大型 |
| 非同步處理 | 需額外處理 | 內建支援 | 需搭配 Event |
我的推薦
- 個人小專案 / 學習階段:從 Provider 開始,理解狀態管理的基本概念
- 獨立開發者的正式專案:Riverpod 是目前最平衡的選擇,它的開發體驗和程式碼品質都很出色
- 團隊協作 / 企業專案:Bloc 的嚴格架構能確保程式碼一致性,長期維護更容易
不要忽略的其他選項
除了上述三個主流方案,社群中也有其他值得關注的選擇:
- GetX:功能全面但爭議較大,不太符合 Flutter 的設計哲學
- MobX:來自 React 生態的響應式方案,適合喜歡 observable 模式的開發者
- signals:受到前端框架啟發的新方案,值得觀察後續發展
結語
沒有「最好」的狀態管理方案,只有「最適合」的。我的建議是:先理解每個方案的核心概念和取捨,再根據專案的規模、團隊的經驗和實際需求做出選擇。最重要的是,不要花太多時間在選擇上而忘了真正重要的事——把產品做出來。
如果你跟我一樣是獨立開發者,我會建議直接從 Riverpod 開始。它在開發效率和程式碼品質之間取得了很好的平衡,也是目前 Flutter 社群中成長最快的方案之一。