- 前端
【免费下载链接】fish-redux
An assembled flutter application framework.
本文以仓库文档 docs/concept/connector.md 为骨架展开,并结合 lib/src/redux_connector 目录下的源码实现、example/lib/todo_list_page 示例工程以及 test/lib/redux_connector 下的测试用例进行深度验证与扩展。
导读
Connector 是 Fish Redux 中表达"数据连接关系"的核心抽象:它定义了如何从大 State 中读取小块数据(Get),以及当小块数据被修改时如何同步写回大 State(Set)。它是让集中式 Reducer 能够按多层级、多模块自动拼装的关键,大幅降低了 Redux 的使用复杂度。读完本文,你将掌握 Connector 的工作原理、ConnOp/ImmutableConn/MutableConn的差异与选择依据、它在Dependencies与Dependent中的装配方式,以及MapLike、Reselect、ConnHelper等配套工具的实际用法。
一、Connector 是什么:从 Redux combineReducers 的局限说起
要理解 Fish Redux 中的 Connector,可以先回顾 What's connector 一文对 ReduxcombineReducers的剖析。combineReducers是一个将 Reducer 分治的函数,它允许一个管理庞大数据的 Reducer 由多层、更小的 Reducer 组合而成。其核心实现本质上只做了三件事:
const nextState = {}; // 1. 浅拷贝任意对象 const previousStateForKey = state[key]; // 2. 读取字段 nextState[key] = nextStateForKey; // 3. 写入字段将上述三点抽象后,可以得到两个更通用的能力:
- State 的 clone 能力(浅拷贝);
- Get & Set 的能力——这正是 Connector 的概念。
Fish Redux 认为,只有同时具备这两点,才能完全继承 Redux 的精华,并将其设计提升到更通用的维度。而 Connector 恰好承担了其中的 Get & Set 部分:它是一条固化在大组件与小组件之间的"数据管道"。
二、Connector 的定位与三大作用
根据 connector.md 的原始定义,Connector 具有三个关键作用:
- 表达数据连接关系:描述如何从大 State 中读取小数据,以及小数据被修改后如何同步回大 State;
- 作为集中式 Reducer 自动拼装的关键:让一个集中式的 Reducer 可以由多层级、多模块的小 Reducer 自动装配。这使得开发者不再关心组装过程,只关心"什么动作促使数据怎么变化",从而大幅降低 Redux 的使用复杂度;
- 固化在 Dependencies 配置中:在配置阶段就将大组件与小组件之间的连接关系(数据管道)固化下来,因此使用小组件时无需再传入任何动态参数。
从概念结构上看,Connector 处在 Dependencies 与 Dependent 的组合之中:Dependent = connector<T, P> + subComponent | subAdapter,Connector 负责数据读写,Dependent 负责把 Connector 与子组件/子 Adapter 绑定。
三、核心类型:ConnOp、ImmutableConn 与 MutableConn
3.1 ConnOp:最常用的函数式 Connector
原文档给出了 ConnOp 的示例代码:
class DetialState { Profile profile; String message; } ConnOp<DetialState, String> messageConnector() { return ConnOp<DetialState, String>( get: (DetialState state) => state.message, set: (DetialState state, String message) => state.message = message, ); }从源码看,ConnOp的定义位于 lib/src/redux_connector/connector.dart:
class ConnOp<T, P> extends MutableConn<T, P> with ConnOpMixin<T, P> { final P Function(T) _getter; final void Function(T, P) _setter; const ConnOp({ P Function(T) get, void Function(T, P) set, }) : _getter = get, _setter = set; @override P get(T state) => _getter(state); @override void set(T state, P subState) => _setter(state, subState); }要点如下:
T是大 State 类型,P是子 State(subState)类型;- 通过构造函数传入
get与set两个函数,即可完成数据的读取与写回,无需新建类; ConnOp继承自MutableConn(可变 State 语义),并混入了ConnOpMixin,因此它支持+运算符直接与子逻辑(Component/Adapter)拼装成Dependent。
3.2 ConnOpMixin:+运算符背后的装配语法糖
ConnOpMixin定义于 lib/src/redux_connector/op_mixin.dart:
mixin ConnOpMixin<T, P> on AbstractConnector<T, P> { Dependent<T> operator +(AbstractLogic<P> logic) => createDependent<T, P>(this, logic); }它使得Connector + 子逻辑成为一条合法表达式,直接产出一个Dependent。在 example/lib/todo_list_page/flow_adapter/adapter.dart 中可以看到这一用法:
return ToDoConnector(index: index) + ToDoComponent();3.3 ImmutableConn 与 MutableConn:可变与不可变 State 的两套语义
ImmutableConn与MutableConn定义在 lib/src/redux/connector.dart 中,分别面向两种 State 风格:
ImmutableConn<T, P>(不可变 State):set返回一个全新的T实例,每次修改都会产生新对象,适合配合不可变数据模型使用。其subReducer会先get出子 State,若为 null 则直接返回原 State;若子 reducer 产生变化,则调用set生成新 State 并返回。MutableConn<T, P>(可变 State):set是原地修改,需要配合Cloneable接口的clone()完成拷贝。其subReducer在检测到子 State 变化时,会调用内部_clone<T>(state)先拷贝一份再写入,以避免污染上层 State。
_clone的实现支持三种情况(见 lib/src/redux/connector.dart):
- 实现了
Cloneable接口的对象:调用clone(); List:toList();Map<String, dynamic>:拷贝所有键值对;- 其他类型:抛出
ArgumentError,提示无法克隆该类型。
在示例工程中,example/lib/todo_list_page/todo_component/state.dart 的ToDoState实现了Cloneable<ToDoState>并提供clone(),以便在可变语义下安全写回。
选择建议:State 为不可变模型时使用
ImmutableConn(或继承它),State 为可变模型时使用MutableConn/ConnOp。ConnOp本质上是MutableConn的函数式便捷封装。
四、Connector 如何参与 Reducer 自动装配
Connector 之所以能成为"集中式 Reducer 自动拼装"的关键,在于它把"如何从大 State 中取小 State、如何把小 State 的变更写回大 State"封装为通用的子 Reducer 转换逻辑。
其核心机制是AbstractConnector上的subReducer(Reducer<P> reducer)方法(由ImmutableConn/MutableConn实现),它把作用于小 State 的 Reducer 升级为可作用于大 State 的 SubReducer。随后,在 lib/src/redux_component/dependent.dart 的_Dependent构造函数中:
subReducer = logic.createReducer != null ? connector.subReducer(logic.createReducer()) : null;于是"子组件的 Reducer + 该组件的 Connector"被组合成一个作用于父级大 State 的 SubReducer,再由Dependencies收集所有子 Dependent 的 SubReducer,与组件自身的 Reducer 一起自动复合。这正是 dependencies.md 中所说的:"A component's Reducer is automatically compounded by the Reducer configured by the Component itself and all of the Reducers under its Dependencies."
同时,_Dependent还借助 Connector 实现了子组件数据的隔离读取:
Get<P> subGetter(Get<T> getter) => () => connector.get(getter());以及子组件的构建:
component.buildComponent( store, () => connector.get(getter()), // 子组件拿到的 State 是 Connector 从父 State 中读取的小 State ... );这解释了原文档第三点的意义:连接关系(数据管道)在Dependencies配置阶段就被固化,运行时子组件无需知道父组件传入什么动态参数。
五、在 Dependencies 中配置 Connector
原文档指出 Connector 使用在Dependencies配置中。以 docs/concept/dependencies.md 的示例为骨架,一个组件可以在构造时声明多个具名 slot,每个 slot 都由Component.asDependent(Connector)绑定:
/// 在 component 中注册 class ItemComponent extends ItemComponent<ItemState> { ItemComponent() : super( view: buildItemView, reducer: buildItemReducer(), dependencies: Dependencies<ItemState>( slots: <String, Dependent<ItemState>>{ 'appBar': AppBarComponent().asDependent(AppBarConnector()), 'body': ItemBodyComponent().asDependent(ItemBodyConnector()), 'ad_ball': ADBallComponent().asDependent(ADBallConnector()), 'bottomBar': BottomBarComponent().asDependent(BottomBarConnector()), }, ), ); }在 View 中则通过service.buildComponent('slotName')直接按 slot 名取用子组件,无需传任何参数(完整 View 示例见 doc/concept/dependencies.md)。
若使用+运算符语法,则等价于ConnOp + AbstractLogic直接生成Dependent。示例工程的流式 Adapter 就是这么用的:
FlowAdapter<PageState> get adapter => FlowAdapter<PageState>( reducer: buildReducer(), view: (PageState state) => DependentArray<PageState>( length: state.itemCount, builder: (int index) { return ToDoConnector(index: index) + ToDoComponent(); }, ));对应的ToDoConnector在 example/lib/todo_list_page/flow_adapter/connector.dart 中通过继承ConnOp并重写get/set完成带索引的数据读写:
class ToDoConnector extends ConnOp<PageState, ToDoState> { ToDoConnector({this.index}); final int index; @override ToDoState get(PageState state) { if (index >= state.toDos.length) { return null; } return state.toDos[index]; } @override void set(PageState state, ToDoState subState) { state.toDos[index] = subState; } }六、进阶:MapLike 键值 Connector 与 Reselect 缓存
除了基础的ConnOp,lib/src/redux_connector目录还提供了若干面向特定场景的 Connector 工具。
6.1 MapLike:以字段名为键的 Connector
lib/src/redux_connector/map_like.dart 定义了MapLike抽象类(内部用Map<String, Object>存字段),并配套提供了:
withMapLike<T extends MapLike, P>(String key):返回一个按指定 key 读写子数据的ConnOp;AutoInitConnector<T extends MapLike, P>:带"自动初始化"能力的 Connector——当父 State 中尚不存在该 key 时,get会调用init(state)生成初始子数据并写回后返回;set时还可通过可选参数传入写入回调(_setHook)。若不显式传key,则用generator()自动生成唯一 key。
测试用例 test/lib/redux_connector/map_like_test.dart 覆盖了三种场景:显式 key 的读写、缺省 key 时自动生成、以及带 hook 的写入回调,验证了get/set的完整行为。
6.2 Reselect / ReselectMixin:派生数据缓存
lib/src/redux_connector/reselect.dart 提供了带缓存的 Connector 基类(Reselect1至Reselect6与Reselect),它们把"从 State 中抽取若干因子 + 计算派生结果"封装成可缓存的get:只有当因子列表发生变化时才重算结果,否则返回缓存。
在示例工程中,example/lib/todo_list_page/state.dart 的ReportConnector混入了ReselectMixin:
class ReportConnector extends ConnOp<PageState, ReportState> with ReselectMixin<PageState, ReportState> { @override ReportState computed(PageState state) { return ReportState() ..done = state.toDos.where((ToDoState tds) => tds.isDone).length ..total = state.toDos.length; } @override List<dynamic> factors(PageState state) { return <int>[ state.toDos.where((ToDoState tds) => tds.isDone).length, state.toDos.length ]; } }当factors返回的因子列表未变化时,get直接返回缓存结果,避免重复计算。
6.3 ConnHelper:Connector 的合成与装配
lib/src/redux_connector/helper.dart 提供两个静态方法:
ConnHelper.to<T, P, K>(connOne, connTwo):将两个 Connector 串联成一个新的 Connector(get依次执行one.get再two.get,对应的subReducer也逐层组合),适用于跨层级的数据读取;ConnHelper.join<T, P>(conn, logic):等价于createDependent,把 Connector 与子逻辑装配成Dependent。
七、小结
Connector 是 Fish Redux 组件树中数据流通的"管道":通过 Get/Set 定义了大小 State 之间的读写关系,通过subReducer把子 Reducer 升级为父级 SubReducer,从而让集中式 Reducer 可以按多层级、多模块自动拼装。在实际工程中:
- 简单场景直接使用
ConnOp的函数式写法; - 不可变 State 使用
ImmutableConn,可变 State 使用MutableConn并配合Cloneable; - 键值型 State 可选用
withMapLike或AutoInitConnector; - 需要派生缓存时混入
ReselectMixin或继承Reselect系列基类; - 多 Connector 串联使用
ConnHelper.to。
这些工具类都可以在 lib/src/redux_connector 中找到完整实现,并有 test/lib/redux_connector 下的测试用例(map_like_test.dart、reselect_test.dart、redux_connector_test.dart)作为行为验证依据,可直接阅读源码与测试进一步深入。
- 前端
【免费下载链接】fish-redux
An assembled flutter application framework.
相关推荐
douyin-downloader 抖音批量下载工具:零基础上手,30 分钟装好、配好、批量抓完
douyin downloader 抖音批量下载工具:零基础上手,30 分钟装好、配好、批量抓完 做运营和素材整理的人大概都卡过这个点:盯上某个作者主页的一整页
前端ODS架构深度剖析:24个服务的Docker Compose分层模型
ODS架构深度剖析:24个服务的Docker Compose分层模型 ODS(Osmantic)是一个一键部署的自托管 AI 服务器方案:一条命令即可把你的 P
前端深入解析 Fish Redux 的 Dependencies:组件依赖声明与 Reducer 自动复合机制
深入解析 Fish Redux 的 Dependencies:组件依赖声明与 Reducer 自动复合机制 导读 :本文围绕 Fish Redux 框架中表达组
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考