This article is from Medium written by Jorge Coca, Thank you Jorge for allowing me translate your awesome article into Chinese
本文來源於Medium,由Jorge Coca撰寫,並准許我翻譯成中文
狀態管理在Flutter中是一個很熱的話題。可選的方案有很多,這可能很好,但卻很容易陷入其中,在專案中選擇最適合方案時感到迷失。我也是,不過我已經找到了適合我的方案,讓我來分享給你。
為了找到適合需求的方案,頭一件事就是確認需求,然後設定目標和期望。對我而言,我定義瞭如下:
- 允許穩定的開發速度,而不犧牲程式碼質量
- 分離展示邏輯和業務邏輯
- 容易理解;難以破壞
- 可預期並且可以廣泛部署
在給定了這些限制後,我們來看看我們可選的方案:
- 使用StatefulWidgets的setState()
- ScopedModel
- BLoC(Business Logic Component)
- Redux
理解區域性狀態和全域性狀態的不同
在深入分析不同方案前,有一件事可能會幫助我們更好的理解怎樣選擇——什麼是區域性狀態,什麼是全域性狀態
為了這件事,我們來考慮一件事:想象一個簡單的登陸表單,使用者可以輸入使用者名稱和密碼,如果登陸成功,就可以從後臺獲得到id。在這個例子中,登入表單的任何驗證型別,都可以考慮為區域性狀態,因為這些規則僅適用於這個元件,而App的其他部分不需要知道這個型別。但是從後臺獲取的id,就需要考慮成全域性狀態,因為它影響整個app的作用域(未登入和已登陸),而且可能別的元件會依賴它。
簡而言之:告訴我結果
如果你不想等太久,或者對深入探索不感興趣,下面這個表可以快速告訴你我的發現:
我的建議是:使用BLoC進行區域性狀態管理,使用Redux進行全域性狀態管理。特別是對持續迭代的複雜應用而言。
為什麼不用setState()?
快速製作原型的時候,在widget中使用setState()非常爽,而且你會立即得到反饋。但是這並不能幫助我們實現目標:展示邏輯和業務邏輯在同一個類裡,打破了乾淨和高質量程式碼的原則。以後應用的規模擴張的時候,程式碼的維護將會是一個挑戰。因此除了快速原型設計,我不建議使用setState()。
ScopedModel,正確方向上的一步
ScopedModel是Brian Egan維護的第三方包。它讓我們建立Model物件,然後在我們需要的時候呼叫notifyListeners();例如,在我們的模型(model)屬性改變的時候:
class CounterModel extends Model {
int _counter = 0;
int get counter = _counter;
void increment() {
_counter++;
notifyListeners();
}
}
複製程式碼
在我們的widget中,我們可以使用ScopedModelDescendant來響應模型的變化:
class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModel<CounterModel>(
model: new CounterModel(),
child: new Column(children: [
new ScopedModelDescendant<CounterModel>(
builder: (context, child, model) => new Text('${model.counter}'),
),
new Text("Another widget that doesn't depend on the CounterModel")
])
);
}
}
複製程式碼
和setState()相反,如果我們使用ScopedModel,就可以分離展示邏輯和業務邏輯,這樣就很好了。但是也會有一些侷限:
- 如果你的模型變得複雜,挑選呼叫notifyListeners()的時機以防止沒必要的更新,將會是一個挑戰。
- Model的API並沒有準確描述UI應用程式的非同步性。
綜上,除非狀態很容易控制,否則我不建議你使用ScopedModel;如果你的應用足夠複雜,我不相信這是支援複雜性和迭代的正確答案。
BLoC,一個強大的方案
BLoC是一個被Google創造並使用的設計模式;他會幫助我們完成這幾件事:
- 分離展示邏輯和業務邏輯
- 擁抱UI應用的非同步性
- 可以在不同的dart應用中複用,不論是Flutter應用還是Angular dart應用
BLoC背後的想法很簡單:
- BLoC開放
Sink<I>
API來描述我們元件的非同步輸入 - BLoC開放
Stream<T>
API來描述我們元件的非同步輸出 - 最終,我們使用
StreamBuilder
widget來管理資料流,我們不需要再維護對資料流的訂閱和widget的重繪
因為它被Google重度使用和推薦,他們有個非常好的例子:github.com/filiph/stat…
我非常推薦你在應用中使用BLoC,特別是管理區域性狀態。
即使是全域性狀態管理,我相信它也會有很好的解決;但是你將會在這個領域面臨一些挑戰,例如怎麼在不同UI元件中插入BLoC更合適,我想這是Redux的閃光點。
Redux和BLoC,對我來說完美的搭配
在文章的開頭,其中一個目標就是找到一個可以廣泛應用並且可預期的方案。好吧,就是Redux。
Redux是一個設計模式,融合了一些工具,來幫助我們管理全域性狀態。它建立在3個基本原則上:
- 單一事實來源:你的整個應用的狀態,都存在在單一的store中,並且以一個樹狀物件存在
- 狀態是隻讀的:改變狀態的唯一方法就是發射action,一個描述發生了什麼的物件
- 改變被純函式處理:為了說明action怎麼改變狀態,你想要寫一個純函式的reducer(譯者注:純函式就是這個函式內部不儲存狀態,在相同的引數的前提下,無論在何時呼叫,返回值嚴格一致)
Redux是網頁開發著廣泛使用的設計模式(譯者注:比如用在React.js中),所以在移動端也有廣泛的基礎,會讓我們互相收益。
Brian Egan維護redux和flutter_redux,而且創造了一個非常棒的ToDo 應用, 整合了很多不同的架構模式,包括Redux。
鑑於Redux的所有特性,我完全建議使用它來管理全域性狀態,但是如果你想擴張你的應用,請確保在Redux架構裡面不包含區域性狀態。
最後的思考
這裡並沒有正確與錯誤答案。為了選一個工具,或者應用一個設計模式,理解你自己的需求非常重要。對我和我的需求來說,Redux和BLoC搭配能幫助我快速安全地擴張我的應用。同時因為工具的可用性被社群很好地理解,更多的開發者開始使用它。話說回來,每個人都有不同的需求和看問題的角度,重要的是始終有好奇心,去學習和思考,什麼才是最好的方案
你可以訪問原作者的GitHub
也歡迎關注我的更多文章,以及我的GitHub