一直以来 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;
@override
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;
}
- 为什么要实现 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 接收三个参数:
state
dispatch
viewService
- 返回值为一个 Widget, 跟普通 Widget 相同的写法
- 使用状态时使用
state.msg
state.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 {
@override
Widget 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'}),