文章原文地址:Nealyang/PersonalBlog前端
可能做为一个前端,在学习 Flutter 的过程当中,总感受很是很是类似 React Native,甚至于,其中仍是有state的概念 setState
,因此在 Flutter 中,也固然会存在很是多的解决方案,好比 redux 、RxDart 还有 Scoped Model等解决方案。今天,咱们主要介绍下经常使用的两种 State 管理解决方案:redux、scoped model。react
Scoped Model 是 package 上 Dart 的一个第三方库scoped_model。Scoped Model 主要是经过数据model的概念来实现数据传递,表现上相似于 react 中 context 的概念。它提供了让子代widget轻松获取父级数据model的功能。git
从官网中的介绍能够了解到,它直接来自于Google正在开发的新系统Fuchsia核心 Widgets 中对 Model 类的简单提取,做为独立使用的独立 Flutter 插件发布。github
在直接上手以前,咱们先着重说一下 Scoped Model 中几个重要的概念web
固然,在 Scoped Model 的文档中,也介绍了一些 实现原理redux
demo地址bash
从gif上能够看到我们的需求很是的简单,就是在当前页面更新了count后,在第二个页面也可以传递过去。固然,new ResultPage(count:count)
就没意思啦~ 咱不讨论哈网络
lib/model/counter_model.dartapp
import 'package:scoped_model/scoped_model.dart';
class CounterModel extends Model{
int _counter = 0;
int get counter => _counter;
void increment(){
_counter++;
// 通知全部的 listener
notifyListeners();
}
}
复制代码
Model
get
方法,以便于后面取数据modelincrement
方法,去改变咱们的数据 model ,调用 package 中的 通知方法 notifyListeners
lib/main.dartless
import 'package:flutter/material.dart';
import './model/counter_model.dart';
import 'package:scoped_model/scoped_model.dart';
import './count_page.dart';
void main() {
runApp(MyApp(
model: CounterModel(),
));
}
class MyApp extends StatelessWidget {
final CounterModel model;
const MyApp({Key key,@required this.model}):super(key:key);
@override
Widget build(BuildContext context) {
return ScopedModel(
model: model,
child: MaterialApp(
title: 'Scoped Model Demo',
home:CountPage(),
),
);
}
}
复制代码
这是 app 的入口文件,划重点
MyApp
类 中,咱们传入一个定义好的数据 model ,方便后面传递给子类MaterialApp
用 ScopedModel
包裹一下,做用上面已经介绍了,方便子类能够拿到 ,相似于 redux
中 Provider
包裹一下model
传递给 ScopedModel
的 model 属性中lib/count_page.dart
class CountPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scoped Model'),
actions: <Widget>[
IconButton(
tooltip: 'to result',
icon: Icon(Icons.home),
onPressed: (){
Navigator.push(context,MaterialPageRoute(builder: (context)=>ResultPage()));
},
)
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('你都点击'),
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return Text(
'${model.counter.toString()} 次了',
style: TextStyle(
color: Colors.red,
fontSize: 33.0,
),
);
},
)
],
),
),
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context,child,model){
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'add',
child: Icon(Icons.add),
);
},
),
);
}
}
复制代码
常规布局和widget这里再也不重复介绍,咱们说下主角:Scoped Model
ScopedModelDescendant
ScopedModelDescendant
中的build方法须要返回一个widget,在这个widget中咱们可使用数据 model中的方法、数据等最后在 lib/result_page.dart
中就能够看到咱们数据 model 中的 count 值了,注意这里跳转页面,咱们并无经过参数传递的形式传递 Navigator.push(context,MaterialPageRoute(builder: (context)=>ResultPage()));
完整项目代码:flutter_scoped_model
相信做为一个前端对于 redux 必定不会陌生,而 Flutter 中也一样存在 state 的概念,其实说白了,UI 只是数据(state)的另外一种展示形式。study-redux是笔者以前学习redux时候的一些笔记和心得。这里为了防止有新人不太清楚redux,咱们再来介绍下redux的一些基本概念
state 咱们能够理解为前端UI的状态(数据)库,它存储着这个应用全部须要的数据。
既然这些state已经有了,那么咱们是如何实现管理这些state中的数据的呢,固然,这里就要说到action了。 什么是action?E:action:动做。 是的,就是这么简单。。。
只有当某一个动做发生的时候才可以触发这个state去改变,那么,触发state变化的缘由那么多,好比这里的咱们的点击事件,还有网络请求,页面进入,鼠标移入。。。因此action的出现,就是为了把这些操做所产生或者改变的数据从应用传到store中的有效载荷。 须要说明的是,action是state的惟一信号来源。
reducer决定了state的最终格式。 reducer是一个纯函数,也就是说,只要传入参数相同,返回计算获得的下一个 state 就必定相同。没有特殊状况、没有反作用,没有 API 请求、没有变量修改,单纯执行计算。reducer对传入的action进行判断,而后返回一个经过判断后的state,这就是reducer的所有职责。 从代码能够简单地看出:
import {INCREMENT_COUNTER,DECREMENT_COUNTER} from '../actions';
export default function counter(state = 0,action) {
switch (action.type){
case INCREMENT_COUNTER:
return state+1;
case DECREMENT_COUNTER:
return state-1;
default:
return state;
}
}
复制代码
对于一个比较大一点的应用来讲,咱们是须要将reducer拆分的,最后经过redux提供的combineReducers方法组合到一块儿。 好比:
const rootReducer = combineReducers({
counter
});
export default rootReducer;
复制代码
这里你要明白:每一个 reducer 只负责管理全局 state 中它负责的一部分。每一个 reducer 的 state 参数都不一样,分别对应它管理的那部分 state 数据。 combineReducers() 所作的只是生成一个函数,这个函数来调用你的一系列 reducer,每一个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, 而后这个生成的函数再将全部 reducer 的结果合并成一个大的对象。
store是对以前说到一个联系和管理。具备以下职责
再次强调一下 Redux 应用只有一个单一的 store。当须要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是建立多个 store。 store的建立经过redux的createStore方法建立,这个方法还须要传入reducer,很容易理解:毕竟我须要dispatch一个action来改变state嘛。 应用通常会有一个初始化的state,因此可选为第二个参数,这个参数一般是有服务端提供的,传说中的Universal渲染。后面会说。。。 第三个参数通常是须要使用的中间件,经过applyMiddleware传入。
说了这么多,action,store,action creator,reducer关系就是这么以下的简单明了:
一些工具集让你轻松地使用 redux 来轻松构建 Flutter widget,版本要求是 redux.dart 3.0.0+
StoreProvider ancestor,使用给定的 converter 函数将 Store 转换为 ViewModel ,并将ViewModel传递给 builder。 只要 Store 发出更改事件(action),Widget就会自动重建。 无需管理订阅!
Dart 2须要更严格的类型!
一、确认你正使用的是 redux 3.0.0+ 二、在你的组件树中,将 new StoreProvider(...)
改成 new StoreProvider<StateClass>(...)
三、若是须要从StoreProvider<AppState>
中直接获取 Store<AppState>
,则须要将 new StoreProvider.of(context)
改成 StoreProvider.of<StateClass>
.不须要直接访问 Store 中的字段,由于Dart2可使用静态函数推断出正确的类型
官方demo的代码先大概解释一下
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
//定义一个action: Increment
enum Actions { Increment }
// 定义一个 reducer,响应传进来的 action
int counterReducer(int state, dynamic action) {
if (action == Actions.Increment) {
return state + 1;
}
return state;
}
void main() {
// 在 基础 widget 中建立一个 store,用final关键字修饰 这比直接在build方法中建立要好不少
final store = new Store<int>(counterReducer, initialState: 0);
runApp(new FlutterReduxApp(
title: 'Flutter Redux Demo',
store: store,
));
}
class FlutterReduxApp extends StatelessWidget {
final Store<int> store;
final String title;
FlutterReduxApp({Key key, this.store, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// 用 StoreProvider 来包裹你的 MaterialApp 或者别的 widget ,这样可以确保下面全部的widget可以获取到store中的数据
return new StoreProvider<int>(
// 将 store 传递给 StoreProvider
// Widgets 将使用 store 变量来使用它
store: store,
child: new MaterialApp(
theme: new ThemeData.dark(),
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Text(
'You have pushed the button this many times:',
),
// 经过 StoreConnector 将 store 和 Text 链接起来,以便于 Text直接render
// store 中的值。相似于 react-redux 中的connect
//
// 将 Text widget 包裹在 StoreConnector 中,
// `StoreConnector`将会在最近的一个祖先元素中找到 StoreProvider
// 拿到对应的值,而后传递给build函数
//
// 每次点击按钮的时候,将会 dispatch 一个 action而且被reducer所接受。
// 等reducer处理得出最新结果后, widget将会自动重建
new StoreConnector<int, String>(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text(
count,
style: Theme.of(context).textTheme.display1,
);
},
)
],
),
),
// 一样使用 StoreConnector 来链接Store 和FloatingActionButton
// 在这个demo中,咱们使用store 去构建一个包含dispatch、Increment
// action的回调函数
//
// 将这个回调函数丢给 onPressed
floatingActionButton: new StoreConnector<int, VoidCallback>(
converter: (store) {
return () => store.dispatch(Actions.Increment);
},
builder: (context, callback) {
return new FloatingActionButton(
onPressed: callback,
tooltip: 'Increment',
child: new Icon(Icons.add),
);
},
),
),
),
);
}
}
复制代码
上面的例子比较简单,鉴于小册Flutter入门实战:从0到1仿写web版掘金App下面有哥们在登录那块评论了Flutter状态管理,
这里我简单使用redux模拟了一个登录的demo
lib/reducer/reducers.dart
首先咱们定义action须要的一些action type
enum Actions{
Login,
LoginSuccess,
LogoutSuccess
}
复制代码
而后定义相应的类来管理登录状态
class AuthState{
bool isLogin; //是否登陆
String account; //用户名
AuthState({this.isLogin:false,this.account});
@override
String toString() {
return "{account:$account,isLogin:$isLogin}";
}
}
复制代码
而后咱们须要定义一些action,定义个基类,而后定义登录成功的action
class Action{
final Actions type;
Action({this.type});
}
class LoginSuccessAction extends Action{
final String account;
LoginSuccessAction({
this.account
}):super( type:Actions.LoginSuccess );
}
复制代码
最后定义 AppState
以及咱们自定义的一个中间件。
// 应用程序状态
class AppState {
AuthState auth; //登陆
MainPageState main; //主页
AppState({this.main, this.auth});
@override
String toString() {
return "{auth:$auth,main:$main}";
}
}
AppState mainReducer(AppState state, dynamic action) {
if (Actions.LogoutSuccess == action) {
state.auth.isLogin = false;
state.auth.account = null;
}
if (action is LoginSuccessAction) {
state.auth.isLogin = true;
state.auth.account = action.account;
}
print("state changed:$state");
return state;
}
loggingMiddleware(Store<AppState> store, action, NextDispatcher next) {
print('${new DateTime.now()}: $action');
next(action);
}
复制代码
在稍微大一点的项目中,其实就是reducer 、 state 和 action 的组织会比较麻烦,固然,罗马也不是一日建成的, 庞大的state也是一点一点累计起来的。
下面就是在入口文件中使用 redux 的代码了,跟基础demo没有差别。
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'dart:async' as Async;
import './reducer/reducers.dart';
import './login_page.dart';
void main() {
Store<AppState> store = Store<AppState>(mainReducer,
initialState: AppState(
main: MainPageState(),
auth: AuthState(),
),
middleware: [loggingMiddleware]);
runApp(new MyApp(
store: store,
));
}
class MyApp extends StatelessWidget {
final Store<AppState> store;
MyApp({Key key, this.store}) : super(key: key);
@override
Widget build(BuildContext context) {
return new StoreProvider(store: store, child: new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new StoreConnector<AppState,AppState>(builder: (BuildContext context,AppState state){
print("isLogin:${state.auth.isLogin}");
return new MyHomePage(title: 'Flutter Demo Home Page',
counter:state.main.counter,
isLogin: state.auth.isLogin,
account:state.auth.account);
}, converter: (Store<AppState> store){
return store.state;
}) ,
routes: {
"login":(BuildContext context)=>new StoreConnector(builder: ( BuildContext context,Store<AppState> store ){
return new LoginPage(callLogin: (String account,String pwd) async{
print("正在登陆,帐号$account,密码:$pwd");
// 为了模拟实际登陆,这里等待一秒
await new Async.Future.delayed(new Duration(milliseconds: 1000));
if(pwd != "123456"){
throw ("登陆失败,密码必须是123456");
}
print("登陆成功!");
store.dispatch(new LoginSuccessAction(account: account));
},);
}, converter: (Store<AppState> store){
return store;
}),
},
));
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title, this.counter, this.isLogin, this.account})
: super(key: key);
final String title;
final int counter;
final bool isLogin;
final String account;
@override
Widget build(BuildContext context) {
print("build:$isLogin");
Widget loginPane;
if (isLogin) {
loginPane = new StoreConnector(
key: new ValueKey("login"),
builder: (BuildContext context, VoidCallback logout) {
return new RaisedButton(
onPressed: logout, child: new Text("您好:$account,点击退出"),);
}, converter: (Store<AppState> store) {
return () =>
store.dispatch(
Actions.LogoutSuccess
);
});
} else {
loginPane = new RaisedButton(onPressed: () {
Navigator.of(context).pushNamed("login");
}, child: new Text("登陆"),);
}
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
/// 有登陆,展现你好:xxx,没登陆,展现登陆按钮
loginPane
],
),
),
);
}
}
复制代码
完整项目代码:Nealyang/Flutter
更多学习 Flutter的小伙伴,欢迎入QQ群 Flutter Go :679476515
关于 Flutter 组件以及更多的学习,敬请关注咱们正在开发的: alibaba/flutter-go
flutter_architecture_samples flutter_redux flutter example scoped_model