今天的任务是将昨的代码用redux整理一下。
在此之前先说统一几个名词在本文中的叫法。本文源码见github
store : 仓库 dispatch : 分发 action : 动作 reducer : 分解器 connector : 连接器 provider : 供应器 converter : 转换器 builder : 构造器 依赖: flutter_redux: ^0.5.3 复制代码
1.初始项目的Redux化
大家应该都还记得初始项目吧,下面是它的梳理图,磨刀不误砍柴工。
我打算从它开始入手,向你简单介绍redux是什么?
1.1:分析行为及变化
很简单,行为是点击,变化是数字的自增长。
关于reducer,不想是什么纯不纯,在我看来它就是一个独立的逻辑单元,
不依靠外界存活,在逻辑上便可存在:给定一个输入就会返回一个预期的输出
enum Actions { increment//定义增加行为 } //使用counterReducer将行为从类中抽离分解,成为独立逻辑单元 int counterReducer(int input, dynamic action) { var output; switch(action){ case Actions.increment: output=input+1; break; } return output; } 复制代码
1.2:新建ReduxPage组件
redux核心之一便是Store,是一个仓库用来储存,供应,分发。
返回一个仓库提供器,它是一个Widget,需要store和child属性。
class ReduxPage extends StatelessWidget { final Store<int> store; ReduxPage({Key key, this.store,}) : super(key: key); return StoreProvider<int>( store: store, child: child, ); } 复制代码
1.3:现在的焦点在于孩子是如何构建的
这里为了看得清楚些,将
countText
与fab
两个与状态有关的组件抽离一下
var child = MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text("Flutter Redux Demo"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many time' ), countText//显示数字的Text ], ), ), floatingActionButton: fab,//点击的按钮 ), ); 复制代码
显示数字的Text:countText
如果你想要到仓库拿东西,你需要什么?钥匙呗。
StoreConnector
仓库连接器就是这把钥匙
converter转换器中回调出store对象,你就可以通过store去取值了,通过构造器生成组件返回出去
var countText= StoreConnector<int, String>( converter: (store) => store.state.toString(),//转换器,获取仓库,从仓库拿值 builder: (context, count) {//构造器,构建Widget return Text( count, style: Theme.of(context).textTheme.display1, ); }, ); 复制代码
处理动作的按钮
处理动作也是需要仓库,使用进行分发(dispatch)相应动作(action)
在构造器中,你就可以使用该动作逻辑了。
var fab= StoreConnector<int, VoidCallback>( converter: (store) { return () => store.dispatch(Actions.increment);//分发动作 }, builder: (context, callback) {//构造器,使用动作逻辑 return FloatingActionButton( onPressed: callback, tooltip: 'Increment', child: Icon(Icons.add), ); }, ); 复制代码
这里将动作方成为攻方,响应方成为受方,下面的图阐释了两方Widget如何构建
1.4:仓库对象的构建
可以说这核心便是仓库store了,看一下对象如何生成
void main() { final store = Store<int>(counterReducer, initialState: 0); runApp(ReduxPage( store: store, )); } 复制代码
2.redux优势
也许你会说:"感觉也不咋地啊,感觉好麻烦。"
2.1:增加一个功能时
比如我想要点一下加10该怎么办?使用redux你需要定义一个行为,及响应。
在行为分发时修改行为即可。也许你说我不用redux,改行就行了。如果逻辑非常多怎么办
之后又要改回来怎么办?抽象出一个行为来管理逻辑切换起来是非常方便的
而且想要修改直接在reducer中进行即可,就避免了污染封装的组件源码。
enum Actions{ increment, increment10 } int counterReducer(int input, dynamic action) { var output; switch(action){ case Actions.increment: output=input+1; break; case Actions.increment10: output=input+10; break; } return output; } var fab= StoreConnector<int, VoidCallback>( converter: (store) { return () => store.dispatch(Actions.increment10); }, 复制代码
2.2:全局的状态共享
另一个界面如何轻松享有上个界面的数据,这是个很大的问题。
当然可以通过构造传参,但这显然十分麻烦,不仅乱,而且还要接收个参数。
import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; class SecondPage extends StatelessWidget { SecondPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { var text = StoreConnector<int, String>(//直接从状态取值 converter: (store) => store.state.toString(), builder: (context, count) { return Text( count.toString(), style: Theme.of(context).textTheme.display1, ); }, ); return Scaffold( appBar: AppBar( title: Text("SecondPage"), ), body: Align( alignment: Alignment.topCenter, child: text, ), ); } } 复制代码
ReduxPage中为文字添加点击跳转到SecondPage
Builder _skipToSecondPage(StoreConnector<int, String> countText) { return Builder( builder: (context) => InkWell(child: countText, onTap: () { Navigator.of(context) .push(MaterialPageRoute(builder: (BuildContext context) { return SecondPage(); })); },), ); } 复制代码
3.对昨天TodoList的改造
还是一样的界面效果。
3.1:定义Todo描述类
class Todo { String sth; //待做事项 bool done;//是否已完成 Todo({this.sth, this.done}); //是否已做完 } 复制代码
3.2:定义状态类和动作及变化
昨天分析了有个有三个状态和四个动作
class TodoState { List<Todo> todos; //列表数据 String text; //当前输入文字 ShowType showType;//显示类型 TodoState({this.todos, this.text, this.showType}); //显示类型 } enum Acts { add, //添加到todo selectAll, //筛选所有 selectTodo, //筛选待完成 selectDone, //筛选已完成 } TodoState todoReducer(TodoState input, dynamic action) { switch (action) { case Acts.add: if (input.text != null && input.text != "") { input.todos.add(Todo(sth: input.text, done: false)); input.text = ""; } break; case Acts.selectAll: input.showType=ShowType.all; break; case Acts.selectTodo: input.showType=ShowType.todo; break; case Acts.selectDone: input.showType=ShowType.done; break; } return input; } final todoListStore = Store<TodoState>(todoReducer, initialState://初始状态 TodoState(todos: <Todo>[], text: "", showType: ShowType.all)); 复制代码
3.3:组件的封装
和上面一样,使用StoreConnector来从仓库拿资源,只不过这里资源是TodoState对象
动作的话,作为攻方,依旧是那回调来执行相应动作。
class TodoList extends StatefulWidget { final Store<TodoState> store; TodoList({ Key key, this.store, }) : super(key: key); @override _TodoListState createState() => _TodoListState(); } class _TodoListState extends State<TodoList> { @override Widget build(BuildContext context) { var textField= StoreConnector<TodoState, TodoState>( converter: (store) =>store.state,//转换器,获取仓库,从仓库拿值 builder: (context, state) {//构造器,构建Widget return TextField( controller: TextEditingController(text: state.text), keyboardType: TextInputType.text, textAlign: TextAlign.start, maxLines: 1, cursorColor: Colors.black, cursorWidth: 3, style: TextStyle( fontSize: 16, color: Colors.lightBlue, backgroundColor: Colors.white), decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: '添加一个待办项', hintStyle: TextStyle(color: Colors.black26, fontSize: 14), contentPadding: EdgeInsets.only(left: 14.0, bottom: 8.0, top: 8.0), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.white), borderRadius: BorderRadius.only( topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)), ), enabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: Colors.white), borderRadius: BorderRadius.only( topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)), ), ), onChanged: (str){ state.text=str; }, ); }, ); var btn = StoreConnector<TodoState, VoidCallback>( converter:(store) { return () => store.dispatch(Acts.add);//分发动作 }, builder: (context, callback) { return RaisedButton( child: Icon(Icons.add), padding: EdgeInsets.zero, onPressed: (){ callback(); FocusScope.of(context).requestFocus(FocusNode()); }); }, ); var inputBtn = Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( child: textField, width: 200, ), ClipRRect( borderRadius: BorderRadius.only( topRight: Radius.circular(10), bottomRight: Radius.circular(10)), child: Container( child: btn, width: 36, height: 36, ), ), ], ); var listInfo = [ ["全部", Acts.selectAll], ["已完成", Acts.selectDone], ["未完成", Acts.selectTodo], ]; var op = Row(//操作按钮 mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: listInfo.map((e) { return StoreConnector<TodoState, VoidCallback>( converter: (store) { return () => store.dispatch(e[1]); }, builder: (context, callback) { return RaisedButton( onPressed: callback, child: Text(e[0]), color: Colors.blue, ); }, ); }).toList(), ); var listView = StoreConnector<TodoState, TodoState>( converter: (store) => store.state, //转换器,获取仓库,从仓库拿值 builder: (context, state) { var result; //构造器,构建Widget switch(state.showType){ case ShowType.all: result= formList(state.todos); break; case ShowType.todo: result= formList(List.of( state.todos.where((e)=>!e.done))); break; case ShowType.done: result= formList(List.of( state.todos.where((e)=>e.done))); break; } return result; }); return StoreProvider<TodoState>( store: widget.store, child: Column( children: <Widget>[inputBtn, op, Expanded(child: listView)], ), ); } Widget formList(List<Todo> todos) { return ListView.builder( itemCount: todos.length, padding: EdgeInsets.all(8.0), itemExtent: 50.0, itemBuilder: (BuildContext context, int index) { var key = todos[index].sth; var value = todos[index].done; var text = Align( child: Text( key, style: TextStyle( decorationThickness: 3, decoration: value ? TextDecoration.lineThrough : TextDecoration.none, decorationColor: Colors.blue), ), alignment: Alignment.centerLeft, ); return Card( child: Row( children: <Widget>[ Checkbox( onChanged: (b) { todos[index].done = b; setState(() {}); }, value: todos[index].done, ), text ], ), ); }, ); } }