fish_redux

一直以来 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(),
    );
}
  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

import 'package:fish_redux/fish_redux.dart';
class TestState implements Cloneable<TestState> {
  bool isLoading;
  String msg;
  
  TestState 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;
}
  1. 为什么要实现 clone 方法: 在之后的 reducer 需要使用
  2. 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));
          },
        )
      ],
    ),
  );
}
  1. 引入了 action.dart, 用来定义 Action 的
  2. buildView 接收三个参数: state dispatch viewService
  3. 返回值为一个 Widget, 跟普通 Widget 相同的写法
  4. 使用状态时使用 state.msg state.isLoading
  5. 修改状态时使用 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);
  }
}
  1. 通过 TestAction 枚举所有可能用到的 Action
  2. 在 TestActionCreator 中定义所有的 Action
  3. 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;
}
  1. asReducer 指定了 Action 具体的操作
  2. 操作 state 时需要使用深拷贝 state.clone(), 否则视图不会更新, 这里看出为什么 TestState 需要实现 Cloneable 接口了
  3. 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 {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TestPage().buildPage({'msg': 'world'}),
    );
  }
}

参考资料

MIT Licensed | Copyright © 2018-present 滇ICP备16006294号

Design by Quanzaiyu | Power by VuePress