- 原文地址:Reactive app state in Flutter
- 原文做者:Maksim Ryzhikov
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Starrier
flutter.iohtml
我使用 Flutter 已經有幾個星期了,因此我能感覺到它爲開發所帶來的便利,感謝 Flutter 和 Dart 團隊。但起初我嘗試攻擊 Flutter 中演示案例時,遇到了一些問題:前端
那麼咱們從第一個問題「如何傳遞應用程序狀態」開始。我用 Flutter 的標準 「Counter」 示例應用程序來演示個人解決方案。建立一個這樣的應用程序很是簡單:咱們只須要在終端輸入 「flutter create myapp」(「myapp」 —— 是個人示例應用程序的名字)。react
而後咱們打開 「main.dart」 文件,並使 「MyHomePage」 小部件爲 「stateless」:android
import 'package:flutter/material.dart';
var _counter = 0;
...
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
_incrementCounter() {}
}
複製代碼
咱們只需將 「build」 方法 「MyHomePageState」 移至 「MyHomePage」 小部件,而後在文件的頂部新建空方法 _incrementCounter
和變量 _counter
。如今咱們能夠從新加載咱們的應用程序,而後發現屏幕上沒有任何變化,除了 「+」 按鈕 —— 如今它尚未任何功能。這不要緊,由於咱們的小部件是無狀態的。ios
咱們考慮一下提供應用程序狀態的小部件應該是什麼樣子的:git
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Provider(
data: _counter,
child: new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
複製代碼
這裏咱們能夠看到包裝了咱們整個應用程序的新的小部件 「Provider」。它有兩個屬性:包含應用程序狀態的 「data」 和子代小部件的 「child」。此外,咱們應該有可能從樹下的任何小部件中獲取這些數據,但稍後咱們會考慮的。如今,咱們爲咱們的新小部件寫下簡單的實現。github
首先,咱們經過 「main.dart」 來新建一個 「Provider.dart」 dart 文件,而後用來實現咱們的 「Provider」 小部件。redux
如今咱們建立 「Provider」 做爲 「Stateless」 小部件:後端
import 'package:flutter/widgets.dart';
class Provider extends StatelessWidget {
const Provider({this.data, this.child});
final data;
final child;
@override
Widget build(BuildContext context) => child;
}
複製代碼
是的,直截了當。如今咱們將 "Provider" 導入 「main.dart」bash
import 'package:flutter/material.dart';
import 'package:myapp/Provider.dart';
複製代碼
重建咱們應用程序,以檢查全部的工做是否有錯。若是所有「運行」,那咱們就能夠進行下一步了。咱們如今有了一個用於應用程序狀態的容器,咱們能夠返回如何從這個容器中檢索數據的問題。幸運的是,Flutter 已經有了解決方案,並且它是 「InheritedWidget」。這些文檔已經說得很清楚了:
用於在樹中有效傳播信息的小部件的基類。
這正是咱們所須要的。咱們建立「繼承」小部件。
打開 「Provider.dart」,而後建立私有 「_InheritedProvider
」 小部件:
class _InheritedProvider extends InheritedWidget {
_InheritedProvider({this.data, this.child})
: super(child: child);
final data;
final child;
@override
bool updateShouldNotify(_InheritedProvider oldWidget) {
return data != oldWidget.data;
}
}
複製代碼
「InheritedWidget」 的全部子類都應該實現 「updateShouldNotify」 方法。此時,咱們只需檢查傳遞的 「data」 是否已更改。例如,當咱們將計數器從 「0」 改成 「1」 時,該方法應該返回 「true」。
咱們如今講「繼承」小部件,而後添加到小部件樹中:
class Provider extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return new _InheritedProvider(data: data, child: child);
}
...
}
複製代碼
好的,咱們如今有了小部件,它在小部件樹中傳播數據,但咱們應該建立一個公有方法,它容許 get 這個數據:
class Provider extends StatelessWidget {
...
static of(BuildContext context) {
_InheritedProvider p =
context.inheritFromWidgetOfExactType(_InheritedProvider);
return p.data;
}
...
}
複製代碼
「inheritFromWidgetOfExactType」 方法獲取 「_InheritedProvider」 類型實例的最近父部件。
咱們如今有了解決第一個問題的能力:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Provider(
data: 0,
...
}
...
}
...
class MyHomePage extends StatelessWidget {
...
Widget build(BuildContext context) {
var _counter = Provider.of(context);
...
}
複製代碼
咱們刪除了全局變量 「_counter」 並使用 「Provider.of」 在 「MyHomePage」 小部件中取得了 「counter」。如你所見,咱們沒有將它做爲參數傳遞給 「MyHomePage」,而是使用 「Provider.of」 來獲取應用程序的狀態,他能夠應用於樹下的任何小部件。 此外,「Provider.of」 還包括當前小部件的上下文和重建,並在更改 「_InheritedProvider
」 小部件時對其進行註冊。
如今是時候檢測咱們的應用程序是否起做用了:咱們從新加載它。爲了確保咱們的 "Provider" 正常工做,咱們能夠將 「data」 從 「0」 更改成 「MyApp」 小部件中的 「1」,而後咱們必須從新加載應用程序。然而,咱們的 「+」 按鈕仍然沒法工做。
在這裏,咱們面臨的第二個問題是「如何在改變應用程序的狀態後重建小部件」,如今咱們應該從新開始思考。
咱們的應用程序狀態只是一個數字,但當這個數字被更改時,檢測起來就沒有那麼容易了。若是咱們將「計數器」編號包裝成一個「可觀察的」對象,該對象將跟蹤更改並通知「監聽器」這些更改。
慶幸的是,Flutter 已經有了解決方案,這就是 「ValueNotifier」。像一般同樣,這裏有一個很好的文檔解釋:
當 value 被替代時,這個類會通知它的監聽器。
好的,讓咱們在 「mian.dart」 中建立應用程序的狀態類:
import 'package:flutter/material.dart';
import 'package:myapp/Provider.dart';
class AppState extends ValueNotifier {
AppState(value) : super(value);
}
var appState = new AppState(0);
複製代碼
而後將其傳遞給 "Provider"
Widget build(BuildContext context) {
return new Provider(
data: appState,
複製代碼
因爲 「data」 包含一個對象,因此咱們更改 「Provider.of(context)」 用法,那就這樣作:
Widget build(BuildContext context) {
var _counter = Provider.of(context).value;
複製代碼
重建咱們的應用程序,並確保沒有錯誤。
咱們如今已經實現了 「_incrementCounter
」:
floatingActionButton: new FloatingActionButton(
onPressed: () => _incrementCounter(context),
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
_incrementCounter(context) {
var appState = Provider.of(context);
appState.value += 1;
}
複製代碼
咱們從新加載了應用程序,並嘗試按下 「+」 按鈕。沒有改變什麼。但若是咱們容許「熱加載」,咱們將看到文本已經改變。這是由於咱們按下按鈕後改變了應用程序的狀態。但咱們在屏幕上看到了舊的狀態,由於咱們尚未從新構建小部件。當咱們容許小部件進行「熱加載」時,咱們就能夠看到屏幕上的實際狀態。
最後的挑戰是在咱們更改應用程序的狀態後重建小部件。但在此以前,咱們看看已有的東西:
首先,咱們回顧一下 「_InheritedProvider」 的 「updateShouldNotify」 方法:
@override
bool updateShouldNotify(_InheritedProvider oldWidget) {
return data != oldWidget.data;
}
複製代碼
如今 「data」 等於 「AppState」 的實例,這意味着咱們在 「_incrementCounter
」 方法中更改此實例的 「value」 時,它實際上並不會改變實例自己。所以,這個比較老是返回 「false」。咱們經過比較 「value」-s 來解決這個問題。但爲此,咱們應該將「值」曝出在小部件中,這容許咱們能夠不丟失重構之間的 「value」:
class _InheritedProvider extends InheritedWidget {
_InheritedProvider({this.data, this.child})
: _dataValue = data.value,
super(child: child);
final data;
final child;
final _dataValue;
@override
bool updateShouldNotify(_InheritedProvider oldWidget) {
return _dataValue != oldWidget._dataValue;
}
}
複製代碼
如今它能夠正確地工做了:當咱們改變狀態值時,小部件會從新構建消費者。但在從新構建消費者以前,咱們應該在改變應用程序的狀態後重建小部件自己。
咱們的代碼中只有一個能夠了解 「_InheritedProvider」 的小部件,就是 「Provider」 小部件。若是咱們想要跟蹤小部件中的某種狀態,咱們應該建立 「statefull」 小部件。好的,讓咱們將 「Provider」 小部件從 「stateless」 轉換爲 「statefull」:
class Provider extends StatefulWidget {
const Provider({this.child, this.data});
static of(BuildContext context) {
_InheritedProvider p =
context.inheritFromWidgetOfExactType(_InheritedProvider);
return p.data;
}
final ValueNotifier data;
final Widget child;
@override
State<StatefulWidget> createState() => new _ProviderState();
}
class _ProviderState extends State<Provider> {
@override
Widget build(BuildContext context) {
return new _InheritedProvider(
data: widget.data,
child: widget.child,
);
}
}
class _InheritedProvider extends InheritedWidget {
_InheritedProvider({this.data, this.child})
複製代碼
咱們如今能夠 「subscribe」 應用程序的狀態改變,並在它被更改後調用 「setState
」:
class _ProviderState extends State<Provider> {
@override
initState() {
super.initState();
widget.data.addListener(didValueChange);
}
didValueChange() => setState(() {});
複製代碼
不要忘記在小部件被銷燬後刪除垃圾:
class _ProviderState extends State<Provider> {
...
@override
dispose() {
widget.data.removeListener(didValueChange);
super.dispose();
}
...
}
複製代碼
咱們重建這個應用程序,並檢查它是如何工做的。如今,當咱們按下 「+」 按鈕時,咱們的應用程序狀態就會發生改變,小部件也會被重建。
咱們檢查一下咱們的問題:
源代碼在這裏 —— gist.github.com/c88f116d7d6…
結論。
本文的整體思想是演示如何在沒有額外包的狀況下,在 Flutter 中實現 「redux」 模式。Flutter 已經有了能夠實現 「redux」 模式的包,但有時它們不適合你的體系結構,知道如何用你本身的手從頭開始實現是好事 ✋。
感謝,
快樂的編碼,快樂的發展!
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。