一直以来 Fish Redux 和 Flutter Redux 的最佳实践并不同,一个是 Page 级别的状态管理,一个是 App 级别状态管理,所以算是错位竞品。

引入:

  1. dependencies:
  2. fish_redux: ^0.2.4

推荐的目录结构

  1. sample_page
  2. -- action.dart
  3. -- page.dart
  4. -- view.dart
  5. -- effect.dart
  6. -- reducer.dart
  7. -- state.dart

从一个简单的案例开始

page.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'state.dart';
  3. import 'view.dart';
  4. import 'reducer.dart';
  5. class TestPage extends Page<TestState, Map<String, dynamic>> {
  6. TestPage():
  7. super(
  8. initState: initState,
  9. view: buildView,
  10. reducer: buildReducer(),
  11. );
  12. }
  1. TestPage 继承自 Page
  2. initState 其实是缩写, 完整的写法为: initState: (Map<String, dynamic> args) => initState(args),
  3. view 也是缩写, 完整的写法为: view: (TestState state, Dispatch dispatch, ViewService viewService) => buildView(state, dispatch, viewService),
  4. reducer 用于改变状态

state.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. class TestState implements Cloneable<TestState> {
  3. bool isLoading;
  4. String msg;
  5. @override
  6. TestState clone() {
  7. return TestState()
  8. ..isLoading = isLoading
  9. ..msg = msg;
  10. }
  11. }
  12. TestState initState(Map<String, dynamic> args) {
  13. final state = new TestState();
  14. state.msg = args['msg'];
  15. state.isLoading = false;
  16. return state;
  17. }
  1. 为什么要实现 clone 方法: 在之后的 reducer 需要使用
  2. initState 接收一个 Map 类型的参数, 需要添加一个 msg 的 key

view.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'package:flutter/material.dart';
  3. import 'state.dart';
  4. import 'action.dart';
  5. Widget buildView(TestState state, Dispatch dispatch, ViewService viewService) {
  6. return Scaffold(
  7. appBar: AppBar(title: Text("Hello ${state.msg}")),
  8. body: Column(
  9. children: <Widget>[
  10. Center(
  11. child: state.isLoading ? Text('Loading') : Text("Hello ${state.msg}"),
  12. ),
  13. FlatButton(
  14. child: Text('click me'),
  15. onPressed: () {
  16. bool loading = !state.isLoading;
  17. dispatch(TestActionCreator.changeLoading(loading));
  18. },
  19. )
  20. ],
  21. ),
  22. );
  23. }
  1. 引入了 action.dart, 用来定义 Action 的
  2. buildView 接收三个参数: state dispatch viewService
  3. 返回值为一个 Widget, 跟普通 Widget 相同的写法
  4. 使用状态时使用 state.msg state.isLoading
  5. 修改状态时使用 dispatch(TestActionCreator.changeLoading(loading))

action.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. enum TestAction {
  3. changeLoading
  4. }
  5. class TestActionCreator {
  6. static Action changeLoading(bool loading) {
  7. return Action(TestAction.changeLoading, payload: loading);
  8. }
  9. }
  1. 通过 TestAction 枚举所有可能用到的 Action
  2. 在 TestActionCreator 中定义所有的 Action
  3. Action 接收两个参数, 第一个是 Action 名, 第二个是 payload

reducer.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'action.dart';
  3. import 'state.dart';
  4. Reducer<TestState> buildReducer() {
  5. return asReducer<TestState>(<Object, Reducer<TestState>>{
  6. TestAction.changeLoading: _changeLoading,
  7. });
  8. }
  9. TestState _changeLoading(TestState state, Action action) {
  10. final TestState newState = state.clone();
  11. newState.isLoading = action.payload;
  12. return newState;
  13. }
  1. asReducer 指定了 Action 具体的操作
  2. 操作 state 时需要使用深拷贝 state.clone(), 否则视图不会更新, 这里看出为什么 TestState 需要实现 Cloneable 接口了
  3. action.payload 为传入的参数, 对应 view.dart 中的 dispatch(TestActionCreator.changeLoading(loading)), 其中 loading 就是这个 payload

使用此带状态管理的页面

main.dart

  1. TestPage().buildPage({'msg': 'world'})

前面讲到, initState 接收一个 Map 类型的参数, 需要添加一个 msg 的 key, 这里的参数 {'msg': 'world'} 便与之对应

完成代码:

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_app_bootstrapper/pages/test/page.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. title: 'Flutter Demo',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: TestPage().buildPage({'msg': 'world'}),
  13. );
  14. }
  15. }

路由管理

fish_redux 还有一个很赞的功能, 可以方便地进行路由管理

直接创建路由

创建路由管理: app_route.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'package:flutter_app_bootstrapper/pages/test/page.dart';
  3. final AbstractRoutes pageRoutes = PageRoutes(
  4. pages: <String, Page<Object, dynamic>>{
  5. 'test': TestPage(),
  6. },
  7. );

使用路由: main.dart, 在 MaterialApp -> home 参数中使用

  1. pageRoutes.buildPage('test', {'msg': 'world'})

使用 global 包装

看很多项目都使用以下方式创建路由, 我并不喜欢, 不过还是放出来看看。

创建路由管理:app_route.dart

  1. import 'package:fish_redux/fish_redux.dart';
  2. import 'package:flutter_app_bootstrapper/pages/test/page.dart';
  3. class AppRoute {
  4. static AbstractRoutes _global;
  5. static AbstractRoutes get global {
  6. if (_global == null) {
  7. _global = PageRoutes(pages: <String, Page<Object, dynamic>>{
  8. RoutePath.test: TestPage(),
  9. });
  10. }
  11. return _global;
  12. }
  13. }
  14. class RoutePath {
  15. static const String test = 'test';
  16. }

使用路由:main.dart, 在 MaterialApp -> home 参数中使用

  1. AppRoute.global.buildPage(RoutePath.test, {'msg': 'world'}),

参考资料