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 提供的 example 为例。
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 强大的扩展能力制作更多组件来满足不同业务对于框架的需求。
本文作者:闲鱼技术 - 海潴
阅读原文
本文为云栖社区原创内容,未经允许不得转载。