- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。