作者: 闲鱼技术 - 海潴
--
fish_redux 是闲鱼技术团队打造的 flutter 应用开发框架, 旨在解决页面内组件间的高内聚, 低耦合问题. 开源地址: https://github.com/alibaba/fish-redux
从 react_redux 说起
redux 对于前端的同学来说是一个比较熟悉的框架了, fish_redux 借鉴了 redux 单项数据流思
想. 在 flutter 上说到 redux, 大家可能第一反应会类比到 react 上的 react_redux. 在 react_redux 中有个重要的概念 --connect,
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
简单得说, connect 允许使用者从 Redux store 中获取数据并绑定到组件的 props 上, 可以 dispatch 一个 action 去修改数据.
那么 fish_redux 中的 connector 是做什么的呢? 为什么说 connector 解决了组件内聚的问题? 我们应该如何理解它的设计呢?
connector in fish_redux
尽管都起到了连接的作用, 但 fish_redux 与 react_redux 在抽象层面有很大的不同.
fish_redux 本身是一个 flutter 上的应用框架, 建立了自己的 component 体系, 用来解决组件内的高内聚和组件间的低耦合. 从 connector 角度来说, 如何解决内聚问题, 是设计中的重要考量.
fish_redux 自己制造了 Component 树, Component 聚合了 state 和 dispatch, 每一个子 Component 的 state 通过 connector 从父 Component 的 state 中筛选. 如图所示:
可以看到, fish_redux 的 connector 的主要作用把父子 Component 关联起来, 最重要的操作是 filter.state 从上之下是一个严谨的树形结构, 它的结构复用了 Component 的树形结构. 类似一个漏斗形的数据管道, 管理数据的分拆与组装. 它表达了如何组装一个 Component.
而对于 react_redux 来说, 它主要的作用在于把 react 框架和 redux 绑定起来, 重点在于如何让 React component 具有 Redux 的功能.
从图中可以看到, react_redux 和 React 是平行的结构, 经过 mapStateToProps 后的 state 也不存在严谨的树形结构, 即对于一个 React component 来说, 它的 state 来自于 Redux store 而不是父 component 的 state. 从框架设计的角度来说, react_redux 最重要的一个操作就是 attach.
源码分析
说完概念, 我们从源码的角度来看看 fish_redux 中的 connector 是如何运作的, 以 fish_redux 提供的为例.
- class ToDoListPage extends Page<PageState, Map<String, dynamic>> {
- ToDoListPage()
- : super(
- ...
- dependencies: Dependencies<PageState>(
- adapter: ToDoListAdapter(),
- slots: <String, Dependent<PageState>>{
- 'report': ReportConnector() + ReportComponent()
- }),
- ...
- );
- }
在 ToDoListPage
的构造函数中, 向父类构造传递了一个 Dependencies 对象, 在构造 Dependencies 时, 参数
slots 中包含了名叫 "report" 的 item, 注意这个 item 的生成, 是由一个
ReportConnector+ReportComponent 产生的.
从这里我们得出一个简单却非常重要的结论:
在 fish_redux 中, 一个 Dependent = connector + Component .
Dependent 代表页面拼装中的一个单元, 它可以是一个 Component(通过 buildComponent 函数产
生), 也可以是一个 Adapter(由 buildAdapter 函数产生). 这样设计的好处是, 对于 View 拼装操作
来说, Dependent 对外统一了 API 而不需要透出更多的细节.
根据上面我们得出的结论, connector 用来把一个更小的 Component 单元链接到一个更大的
Component 或 Adapter 上. 这与我们之前的描述相符合.
connector 到底是什么
知道了 connector 的基本作用, 我们来看一下它到底链接了哪些东西以及如何链接.
先来看一下 ReportConnector
类的定义:
class ReportConnector extends ConnOp<PageState, ReportState>
ReportConnector 继承了 ConnOp 类, 所有 connector 的操作包括 + 操作, 都来自于 ConnOp 类.
set/get
既然是数据管道, 就会有获取和放置
set 函数的入参很好得表达了 T 和 P 的意思, 即把一个 P 类型的 subState 合并到 T 类型的
state 中.
再回头看 get 函数, 就很好理解了, get 函数表达的就是如何从 T 类型的 state 中获取 P 类型
的 subState 供 Dependent 使用.
operator +
+ 操作符的重载是我们最初看到 connector 作用的地方, 也是 connector 发挥作用的入口.
Logic 是 Component 和 Adapter 的父类, 它表示页面组装元素的逻辑层, 里面包含了
reducer/effect/higherEffect 等与逻辑相关的元素以及它的组装过程.
operator + 调用了 createDependent 函数, 接着会调用到_Dependent 类的构造函数, 这里将
logic 和 connector 放入_Dependent 内部, 在后面 fish_redux 对 Component 组装的过程中, connector 会随着外部对_Dependent 中函数的调用发挥作用.
connector 正式登场
铺垫了这么多, 是该 connector 正式发挥作用的时候了.
get
我们以 Component 为例, 会调用到_Dependent 的 buildComponent 函数:
- Widget buildComponent(MixedStore<Object> store, Get<T> getter) {
- final AbstractComponent<P> component = logic;
- return component.buildComponent(store, () => connector.get(getter()));
- }
这里的 logic 实际就是一个 Component 对象, 在调用 Component 的 buildComponent 函数的
时候, 使用 get 函数从一个大的父 state 中获取到当前 Component 需要的数据集. 接下去, 这个变换后的子 state 将被用在例如 ViewBuilder 或 Redcuer 函数中.
这是 connector 在数据获取上的作用.
set
还是在_Dependent 类里面, 看 createSubReducer 函数:
- SubReducer<T> createSubReducer() {
- final Reducer<P> reducer = logic.reducer;
- return reducer != null ? connector.subReducer(reducer) : null;
- }
首现从一个 Logic(这里实际上是一个 Component) 对象中获取到外部设置进来的 reducer, 接着
调用 subReducer 返回一个 SubReducer 对象. SubReducer 是一个被 wrap 后的 Reducer
subReducer 的实现在 MutableConn 中, ConnOp 继承了 MutableConn 类, 也获得了这个能
力.
- SubReducer<T> subReducer(Reducer<P> reducer) {
- return (T state, Action action, bool isStateCopied) {
- final P props = get(state);
- if (props == null) {
- return state;
- }
- final P newProps = reducer(props, action);
- final bool hasChanged = newProps != props;
- final T copy = (hasChanged && !isStateCopied)
- ? _clone<T>(state) : state;
- if (hasChanged) {
- set(copy, newProps);
- }
- return copy;
- };
- }
它首现通过 get 函数得到一个变换后的数据集 props, 接着调用原始的 reducer 函数进行逻辑处
理, 这里有一个优化也是 SubReducer 的作用, 如果数据集在经过 reducer 处理之后发生了变化,
并且 state 已经被 copy 过一次了 (isStateCopied==true), 就直接把 newProps 通过 set 函数
更新到 state 中去.(这个优化可以防止多个子 state 发生变化的时候父 state 被拷贝多次)
至此, connector 在数据更新上的作用也体现出来了.
ReportConnector
最后, 就好理解 ReportConnector 的实现了:
- class ReportConnector extends ConnOp<PageState, ReportState> {
- @override
- ReportState get(PageState state) {
- final ReportState reportState = ReportState();
- reportState.total = state.toDos.length;
- reportState.done =
- state.toDos.where((ToDoState tds) => tds.isDone).toList().length;
- return reportState;
- }
- @override
- void set(PageState state, ReportState subState) {}
- }
很明显的, 在 get 函数中, ReportState 从 PageState 中获得了 total/done 字段.
总结
闲鱼客户端的详情页完全使用了 fish_redux 进行了重构, 通过高内聚的 Component+connector 形式, 使得 Component 可以被大量复用, 很好得支持了 5 中类型的详情页. 未来我们会基于 fish_redux 强大的扩展能力制作更多组件来满足不同业务对于框架的需求.
来源: https://yq.aliyun.com/articles/703396