一直以来 Fish Redux 和 Flutter Redux 的最佳实践并不同,一个是 Page 级别的状态管理,一个是 App 级别状态管理,所以算是错位竞品。
引入:
dependencies:fish_redux: ^0.2.4
推荐的目录结构
sample_page-- action.dart-- page.dart-- view.dart-- effect.dart-- reducer.dart-- state.dart
从一个简单的案例开始
page.dart
import 'package:fish_redux/fish_redux.dart';import 'state.dart';import 'view.dart';import 'reducer.dart';class TestPage extends Page<TestState, Map<String, dynamic>> {TestPage():super(initState: initState,view: buildView,reducer: buildReducer(),);}
- TestPage 继承自 Page
- initState 其实是缩写, 完整的写法为:
initState: (Map<String, dynamic> args) => initState(args), - view 也是缩写, 完整的写法为:
view: (TestState state, Dispatch dispatch, ViewService viewService) => buildView(state, dispatch, viewService), - reducer 用于改变状态
state.dart
import 'package:fish_redux/fish_redux.dart';class TestState implements Cloneable<TestState> {bool isLoading;String msg;@overrideTestState clone() {return TestState()..isLoading = isLoading..msg = msg;}}TestState initState(Map<String, dynamic> args) {final state = new TestState();state.msg = args['msg'];state.isLoading = false;return state;}
- 为什么要实现 clone 方法: 在之后的 reducer 需要使用
- initState 接收一个 Map 类型的参数, 需要添加一个 msg 的 key
view.dart
import 'package:fish_redux/fish_redux.dart';import 'package:flutter/material.dart';import 'state.dart';import 'action.dart';Widget buildView(TestState state, Dispatch dispatch, ViewService viewService) {return Scaffold(appBar: AppBar(title: Text("Hello ${state.msg}")),body: Column(children: <Widget>[Center(child: state.isLoading ? Text('Loading') : Text("Hello ${state.msg}"),),FlatButton(child: Text('click me'),onPressed: () {bool loading = !state.isLoading;dispatch(TestActionCreator.changeLoading(loading));},)],),);}
- 引入了
action.dart, 用来定义 Action 的 - buildView 接收三个参数:
statedispatchviewService - 返回值为一个 Widget, 跟普通 Widget 相同的写法
- 使用状态时使用
state.msgstate.isLoading - 修改状态时使用
dispatch(TestActionCreator.changeLoading(loading))
action.dart
import 'package:fish_redux/fish_redux.dart';enum TestAction {changeLoading}class TestActionCreator {static Action changeLoading(bool loading) {return Action(TestAction.changeLoading, payload: loading);}}
- 通过 TestAction 枚举所有可能用到的 Action
- 在 TestActionCreator 中定义所有的 Action
- Action 接收两个参数, 第一个是 Action 名, 第二个是 payload
reducer.dart
import 'package:fish_redux/fish_redux.dart';import 'action.dart';import 'state.dart';Reducer<TestState> buildReducer() {return asReducer<TestState>(<Object, Reducer<TestState>>{TestAction.changeLoading: _changeLoading,});}TestState _changeLoading(TestState state, Action action) {final TestState newState = state.clone();newState.isLoading = action.payload;return newState;}
- asReducer 指定了 Action 具体的操作
- 操作 state 时需要使用深拷贝
state.clone(), 否则视图不会更新, 这里看出为什么 TestState 需要实现 Cloneable 接口了 - action.payload 为传入的参数, 对应
view.dart中的dispatch(TestActionCreator.changeLoading(loading)), 其中 loading 就是这个 payload
使用此带状态管理的页面
main.dart
TestPage().buildPage({'msg': 'world'})
前面讲到, initState 接收一个 Map 类型的参数, 需要添加一个 msg 的 key, 这里的参数 {'msg': 'world'} 便与之对应
完成代码:
import 'package:flutter/material.dart';import 'package:flutter_app_bootstrapper/pages/test/page.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: TestPage().buildPage({'msg': 'world'}),);}}
路由管理
fish_redux 还有一个很赞的功能, 可以方便地进行路由管理
直接创建路由
创建路由管理: app_route.dart
import 'package:fish_redux/fish_redux.dart';import 'package:flutter_app_bootstrapper/pages/test/page.dart';final AbstractRoutes pageRoutes = PageRoutes(pages: <String, Page<Object, dynamic>>{'test': TestPage(),},);
使用路由: main.dart, 在 MaterialApp -> home 参数中使用
pageRoutes.buildPage('test', {'msg': 'world'})
使用 global 包装
看很多项目都使用以下方式创建路由, 我并不喜欢, 不过还是放出来看看。
创建路由管理:app_route.dart
import 'package:fish_redux/fish_redux.dart';import 'package:flutter_app_bootstrapper/pages/test/page.dart';class AppRoute {static AbstractRoutes _global;static AbstractRoutes get global {if (_global == null) {_global = PageRoutes(pages: <String, Page<Object, dynamic>>{RoutePath.test: TestPage(),});}return _global;}}class RoutePath {static const String test = 'test';}
使用路由:main.dart, 在 MaterialApp -> home 参数中使用
AppRoute.global.buildPage(RoutePath.test, {'msg': 'world'}),
