☰
深入理解 Fish Redux 的 Connector<T, P>:数据管道与 Reducer 自动装配的核心抽象
2026/9/29 3:04:39 网站建设 项目流程
  • 前端

【免费下载链接】fish-redux

An assembled flutter application framework.

项目地址:https://gitcode.com/gh_mirrors/fi/fish-redux
点击查看免费下载

本文以仓库文档 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. 写入字段

将上述三点抽象后,可以得到两个更通用的能力:

  1. State 的 clone 能力(浅拷贝);
  2. Get & Set 的能力——这正是 Connector 的概念。

Fish Redux 认为,只有同时具备这两点,才能完全继承 Redux 的精华,并将其设计提升到更通用的维度。而 Connector 恰好承担了其中的 Get & Set 部分:它是一条固化在大组件与小组件之间的"数据管道"。

二、Connector 的定位与三大作用

根据 connector.md 的原始定义,Connector 具有三个关键作用:

  1. 表达数据连接关系:描述如何从大 State 中读取小数据,以及小数据被修改后如何同步回大 State;
  2. 作为集中式 Reducer 自动拼装的关键:让一个集中式的 Reducer 可以由多层级、多模块的小 Reducer 自动装配。这使得开发者不再关心组装过程,只关心"什么动作促使数据怎么变化",从而大幅降低 Redux 的使用复杂度;
  3. 固化在 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.

项目地址:https://gitcode.com/gh_mirrors/fi/fish-redux
点击查看免费下载
上一篇:怪物猎人世界数据监控终极指南:如何让隐藏的游戏信息一目了然
下一篇:TanStack Table 调试选项解析:深入 DebugOptions 类型别名与 table-core 的 debug 实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询