[譯]讓我來幫你理解和選擇Flutter狀態管理方案

Realank Liu發表於2018-09-27

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狀態管理方案

狀態管理在Flutter中是一個很熱的話題。可選的方案有很多,這可能很好,但卻很容易陷入其中,在專案中選擇最適合方案時感到迷失。我也是,不過我已經找到了適合我的方案,讓我來分享給你。

分享時刻

為了找到適合需求的方案,頭一件事就是確認需求,然後設定目標和期望。對我而言,我定義瞭如下:

  • 允許穩定的開發速度,而不犧牲程式碼質量
  • 分離展示邏輯和業務邏輯
  • 容易理解;難以破壞
  • 可預期並且可以廣泛部署

在給定了這些限制後,我們來看看我們可選的方案:

  • 使用StatefulWidgets的setState()
  • ScopedModel
  • BLoC(Business Logic Component)
  • Redux

理解區域性狀態和全域性狀態的不同

在深入分析不同方案前,有一件事可能會幫助我們更好的理解怎樣選擇——什麼是區域性狀態,什麼是全域性狀態

為了這件事,我們來考慮一件事:想象一個簡單的登陸表單,使用者可以輸入使用者名稱和密碼,如果登陸成功,就可以從後臺獲得到id。在這個例子中,登入表單的任何驗證型別,都可以考慮為區域性狀態,因為這些規則僅適用於這個元件,而App的其他部分不需要知道這個型別。但是從後臺獲取的id,就需要考慮成全域性狀態,因為它影響整個app的作用域(未登入和已登陸),而且可能別的元件會依賴它。

簡而言之:告訴我結果

如果你不想等太久,或者對深入探索不感興趣,下面這個表可以快速告訴你我的發現:

[譯]讓我來幫你理解和選擇Flutter狀態管理方案

我的建議是:使用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來描述我們元件的非同步輸出
  • 最終,我們使用StreamBuilderwidget來管理資料流,我們不需要再維護對資料流的訂閱和widget的重繪

因為它被Google重度使用和推薦,他們有個非常好的例子:github.com/filiph/stat…


我非常推薦你在應用中使用BLoC,特別是管理區域性狀態。

即使是全域性狀態管理,我相信它也會有很好的解決;但是你將會在這個領域面臨一些挑戰,例如怎麼在不同UI元件中插入BLoC更合適,我想這是Redux的閃光點。

Redux和BLoC,對我來說完美的搭配

在文章的開頭,其中一個目標就是找到一個可以廣泛應用並且可預期的方案。好吧,就是Redux。

Redux是一個設計模式,融合了一些工具,來幫助我們管理全域性狀態。它建立在3個基本原則上:

  • 單一事實來源:你的整個應用的狀態,都存在在單一的store中,並且以一個樹狀物件存在
  • 狀態是隻讀的:改變狀態的唯一方法就是發射action,一個描述發生了什麼的物件
  • 改變被純函式處理:為了說明action怎麼改變狀態,你想要寫一個純函式的reducer(譯者注:純函式就是這個函式內部不儲存狀態,在相同的引數的前提下,無論在何時呼叫,返回值嚴格一致)

[譯]讓我來幫你理解和選擇Flutter狀態管理方案

Redux是網頁開發著廣泛使用的設計模式(譯者注:比如用在React.js中),所以在移動端也有廣泛的基礎,會讓我們互相收益。

Brian Egan維護redux和flutter_redux,而且創造了一個非常棒的ToDo 應用, 整合了很多不同的架構模式,包括Redux。

鑑於Redux的所有特性,我完全建議使用它來管理全域性狀態,但是如果你想擴張你的應用,請確保在Redux架構裡面不包含區域性狀態。

最後的思考

這裡並沒有正確與錯誤答案。為了選一個工具,或者應用一個設計模式,理解你自己的需求非常重要。對我和我的需求來說,Redux和BLoC搭配能幫助我快速安全地擴張我的應用。同時因為工具的可用性被社群很好地理解,更多的開發者開始使用它。話說回來,每個人都有不同的需求和看問題的角度,重要的是始終有好奇心,去學習和思考,什麼才是最好的方案

你可以訪問原作者的GitHub

也歡迎關注我的更多文章,以及我的GitHub

相關文章