入门
什么是 Fish Redux
简介
Fish Redux 是一个以 Redux 作为数据管理的思想,以数据驱动视图,组装式的 Flutter 应用框架, 它特别适用于构建中大型的复杂应用。
构成
Fish Redux 采用目前主流的开发方式,也是符合 Flutter 的设计理念,即可插拔的组件式开发。小部件是一个的组件(Component) ,复杂页面是由多个组件组成的组件。
为了降低耦合度和提高可扩展性,Fish Redux 将组件拆分成几个部分。
数据
核心部分。定义了组件需要用到的数据,也是组件的重要组成,其分为两部分:
- 参与视图工作的 Redux
- 不参与视图工作的 LocalProps
视图
最基本的,也就是最重要的部分,每一个组件都应该是可视的。
所以在组件构建时,我们必须为组件提供一个用于构建视图的函数。
依赖
描述了组件与组件之间的关系,也是可插拔的组件式开发的一个重要特性。其分为两部分:
- 为列表而优化的 Adapter
- 组件整体的组成部分 slot
安装
引入项目
在 pubspec.yaml
的 dependencies
下加入 fish_redux
并运行 flutter get packages
。
...
dependencies:
fish_redux: ^0.2.4
...
增强开发体验
模板生成插件
VS Code
Android Studio
调试工具
flipperkit_fish_redux_middleware
构建应用
组件
组件(Component)是 Fish Redux 最基本的元素。其与页面的区别在于,State 的数据是通过连接器(Connector)与其关联的父组件进行交流的。
既然是以数据驱动视图的开发,首先我们先为定义一个 State
类,以及组件数据必要的连接器:
class FishButtonState {
String text;
}
class FishButtonConnector extends Reselect1<HomePageState, FishButtonState, String> {
FishButtonState computed(String buttonText) {
return new FishButtonState()
..text = buttonText;
}
String getSub0(HomePageState state) {
return state.buttonText;
}
void set(HomePageState state, FishButtonState subState) {
state.buttonText = subState.text;
}
}
对于连接器(Connector)的更多详情,请参考连接器。
然后为组件声明一个必要的,用于提供视图的函数:
Widget viewBuilder(FishButtonState state, Dispatch dispatch, ViewService veiwService) {
return FlatButton(
onPressed: () {},
child: Text(state.text),
);
}
该函数返回 Widget
,接收三个参数:
- T state - 组件的 State 实例
- Dispatch dispatch - 用于发出数据修改意图的函数
- ViewService viewService - 一些可能需要用到的 API
最后定义 Component
类:
class FishButton extends Component<FishButtonState> {
FishButton() : super(
view: viewBuilder,
);
}
页面
页面(Page)是一个行为丰富的组件,因为它的实现是在组件的基础上增加了 AOP 能力,以及自有的 State 。
同样的,先定义 State
类:
class HomePageState {
String buttonText;
}
因为页面的 State 是自有的,所以构建页面,除了 State
类和提供视图的函数外,还需要一个为页面初始化 State 的函数,例如:
HomePageState initState(String buttonText) {
return new HomePageState()
..buttonText = buttonText;
}
该函数返回 State
类的实例,接受一个参数,参数类型与定义 Page
类时,提供的第二个类型一致。
定义 Page
类,并挂载组件:
class HomePage extends Page<HomePageState, String> {
HomePage() : super(
initState: initState,
view: viewBuilder,
dependencies: Dependencies<HomePageState>(
adapter: null,
slots: <String, Dependent<HomePageState>>{
'FishButton': FishButton() + FishButtonConnector(),
}),
);
);
}
对于依赖(Dependencies)的更多详情,请参考依赖。
页面的视图提供函数与组件一样。这里主要为示例在视图中使用已加入依赖的组件:
Widget viewBuilder(HomePageState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
body: Container(
child: viewService.buildComponent('FishButton'),
),
);
}
数据驱动
多数情况下,驱动视图的数据并非一成不变的,这也是使用 Redux 的原因。
Fish Redux 遵循 Redux 单向数据流的设计核心,在修改 State 的时候下,必须通过触发 Action ,然后调用 Reducer 去修改数据。
State
一个可变的 State 需要实现 Cloneable<T>
类。其核心在于 clone
函数,它总是返回一个新的实例,使框架感知到 State 已经改变:
class HomePageState implements Cloneable<HomePageState> {
String title;
clone() {
return new HomePageState()
..title = title;
}
}
Action
在 Redux 中,修改 State
是通过调用 dispatch
函数去触发 Action
来进行的,
但需要注意的是,Action
仅仅是表达了修改 State
的意图。
dispatch(new Action('changeToOtherTitle', payload: 'Other title'));
Action
的构造器接收两个参数:
- Object type - 必要参数,Action 实例的类型
- dyanmic payload - 可选参数,Action 实例携带的参数
为了更好的协作开发和减少低级错误,建议声明一个 Action
类型的枚举类,以及定义一个集合返回 Action
的函数的类,这样可以约束到 payload
的类型:
// 枚举类
enum HomePageAction { changeToEnglishTitle, changeToOtherTitle }
// 函数类
class HomePageActionCreator {
static Action changeToEnglishTitle() {
return const Action(HomePageAction.changeToEnglishTitle);
}
static Action changeToOtherTitle(String title) {
return Action(HomePageAction.changeToOtherTitle, payload: title);
}
}
// 调用
dispatch(HomePageActionCreator.changeToOtherTitle('Fish Redux'));
Reducer
State 的实际操作者是 Reducer 。
Reducer 是一个函数,它返回新的 State
实例,且接受两个参数:
- T state - 当前的状态
- Action action - 触发的 Action 实例
HomePageState _changeTitle(HomePageState state, Action action) {
final newState = state.clone();
switch (action.type) {
case HomePageAction.changeToEnglishTitle:
newState.title = 'Title';
break;
case HomePageAction.changeToOtherTitle:
newState.title = action.payload; // 使用参数
break;
}
return newState;
}
通常,组件的 Reducer 不只一个,所以应当使用 asReducer
函数去将同个组件的 Reducer 组合成一个大的 Reducer ,并提供给组件。
Reducer<HomePageState> buildReducer() {
return asReducer({
HomePageAction.changeToEnglishTitle: _changeTitle,
HomePageAction.changeToOtherTitle: _changeTitle,
});
}
class HomePage extends Page<HomePageState, String> {
HomePage() : super(
initState: initState,
view: viewBuilder,
reducer: buildReducer(),
);
}
当触发 Action 时,框架内部会自动找到 Action 类型对应的 Reducer ,所以这里作为键的 Action 类型必须是唯一的。
Effect
由于原生的 Redux 是同步的,单向的,纯函数的,导致一些行为无法被处理,例如异步请求。
对此,我们可以使用 Effect 去解决。
Effect 也是一个函数,它返回一个值或者 Future
,接收两个参数:
- Action action - Action 的实例
- Context<T> context - 当前数据流的上下文
void _getTile(Action action, Context<HomePageState> context) async {
final res = await http.get(url);
context.dispatch(HomeActionCreator.changeToOtherTitle(res.data));
}
和 Reducer 一样,组件通常也是有多个 Effect ,所以我们会使用 combineEffects
函数将同个组件的 Effect 组合起来,并提供给组件。
class HomePage extends Page<HomePageState, String> {
HomePage() : super(
initState: initState,
view: viewBuilder,
reducer: buildReducer(),
effect: buildEffect(),
);
}
Effect<HomePageState> buildEffect() {
return combineEffects(<Object, Effect<HomePageState>>{
HomePageAction.getTitle: _getTitle,
});
}