备注:本文首发于「百瓶技术」公众号
「百瓶 App」(各市场搜索「百瓶」体验 🥳)在 2019 年 11 月开始接入 Flutter 时,就选定了闲鱼的 Fish Redux 作为状态管理方案。经过近两年的迭代,已经完成了 80 个以上的 Page 级功能,积累了丰富的使用经验。虽然 GitHub[^2] 迭代更新较少,但是它在 2021-05-18 对外宣布正在进行 「2.0 架构的演进[^3]」以及最近释放的 「Flutter Fish_Redux 3.0起航![^10]」,说明内部还是坚持在业务中使用并且对框架进行持续优化的。
本文主要阐述 Fish Redux 两大方面的知识:
- 简单介绍核心概念以及基于 Fish Redux[^2] 的「最佳」研发流程;
- 深入剖析配置式组装。
(备注:1. 阅读本文需要对 Flutter(v1.17.1) 和 Fish Redux(v0.3.7)(或者 Redux[^1])有简单的了解;2. 文中的很多概念,比如 Redux、Fish Redux 等的详细介绍链接都会统一放到文末「参考」部分)。
核心概念
- 顾名思义:源自 阿里巴巴闲鱼(Fish)技术团队,站在巨人 Redux 的肩膀上,比如 State、Action、Reducer、Store、Middleware 等概念和 Redux 完全一致。
- 青出于蓝:解决了 Redux「集中」 和「分治」间的矛盾,并且由框架自动完成从细粒度的 Reducer 到 主 Reducer 的合并过程。
- 组件三要素:
Component = View + Effect(可选) + Reducer(可选) + Dependencies(可选)
:- View(UI 视图):完全由数据驱动,负责 Dispatch 事件(Effect 和 Reducer 具体实现),组件依赖通过
ViewService
标准化调用; - Effect:处理 副作用(非修改数据的行为,包括生命周期相关的回调);
- Reducer:修改数据,并以扁平化的方式通知组件刷新;
- Dependencies:表达组件之间的依赖关系。
- View(UI 视图):完全由数据驱动,负责 Dispatch 事件(Effect 和 Reducer 具体实现),组件依赖通过
「最佳」研发流程
假设需要完成下图 UI 的页面研发,ProtocolBuffer[^4](没接触过的直接理解为 API 文档即可)也已经设计完毕。
那么,基于 Fish Redux 的最佳研发流程是怎么样的?
简单三步完成复杂页面的协同研发:
- 首先,进行组件拆分:拆分为五个大的组件(如图框所示,红 - profiles_component、绿 - malls_components、黄 - wallets_component、紫 - banners_component、蓝 - tools_component)。由于 Fish Redux 良好的分治策略,这时候完全可以分配给五个同学分别去完成单组件的开发(此时整个页面的目录结构如下图)。
- 其次,五位同学进行单组件的开发(以 蓝 - tools_component 为例):
state.dart
:结合 ProtocolBuffer[^4] 和 UI ,定义 State 结构;reducer.dart
和action.dart
:分析 state 的哪些数据会被改变,定义好相应的 Action 和 Reducer;effect.dart
和action.dart
:分析有哪些 副作用 事件响应(比如 请求数据、页面跳转等),定义好相应的 Action 和 Effect;view.dart
:视图实现;component.dart
:配置组装,这一步通常由 插件[^6] 自动生成了,当然 「最佳实践」 建议删除没有用到的模块(比如很多组件的dependencies
)。
- 最后,Page 级组装(后文会结合源码深入剖析):
user_home_page/state.dart
:1. 以子组件的 State(比如ProfilesState
和ToolsState
)为核心定义 State;2. 建立 State 的 Connector。user_home_page/page.dart
:配置组装整个页面。
配置组装
Fish Redux 通过 声明式配置 来将三大核心要素(View、Reducer、Effect)和依赖的子项 dependencies(子组件、middleware、adapter)完成自动组装。
而且大部分代码都可以通过编辑器的 插件[^6] 自动生成,唯一需要手动编写的就是dependencies
部分。这是非常重要的一个优点,但是业内讲得比较少。
备注:配置组装的代码跟 FishRedux Example 基本一致,可以查看相关代码 [^5] 加深理解。
接下去,将重点阐述配置组装的两个最核心的流程:注册 和 首次页面渲染。
注册
一句代码完成用户中心页面的注册。
connecrtExtraStore
主要是完成一些全局状态的链接,重点看一下 UserHomePage()
部分。
UserHomePage()
「类图」
UserHomePage
:本身不需要新增任何属性或方法,按照固定范式调用 超类 相关方法完成 「页面级」 的组装,并且在注册时对应一个唯一路由。fish_redux Page()
:InitState<T, P> _initState
:页面初始状态,也是 Page 特有且必要的属性。Widget buildPage(P param)
:初次页面渲染调用(后文会详细阐述)。
fish_redux Component
:ViewBuilder<T> _view
:维护 View 层(buildView
属性)。
fish_redux Logic
:维护 reducer、 effect、 dependencies(所有的依赖子组建)等属性,核心逻辑处理。
dependencies
dependencies
接受两个参数(代码见上文图):
adapter
:为了解决 list 相关的性能问题,list 相关组件推荐使用;slots
:页面(或组件)依赖的子组件,这是我们重点要理解的部分。
UHPComponentNames.tools: ToolsConnector() + ToolsComponent()
UHPComponentNames.tools
:依赖的名字,String
类型表明(最佳实践建议提成一个常量,而不是直接用一个 String)。ToolsConnector()
:Tools 自己管理 State,通过 ToolsConnector 与 Page State 建立链接,Page State 完全不关心 toolsState 具体有哪些属性(高内聚低耦合)!ToolsComponent()
:继承自Component
,除了没有InitState
和middleware
相关(包括viewMiddleware
、effectMiddleware
和adapterMiddleware
),其他跟 Page 的组装没有差异。ToolsConnector() +
:追溯到ConnOp
的 mixins -ConnOpMixin
里重载了 「加号」,最终创建了一个_Dependent
(redux_component/dependent.dart[^2]) 实例(_Dependent<K, T>(connector: connector, logic: logic)
)。
_Dependent
的实例化,核心是创建生成 subReducer = connector.subReducer(logic.createReducer())
(下文「createReducer」相关章节会详细阐述 reducer 的创建过程):
logic.createReducer()
:最终调用abstract class Logic
的createReducer
完成 自身 和 子组件 的 reducer 组合;connector.subReducer(logic.createReducer())
:copy 返回一个新的 Reducer。
至此,Page 基于 state、effect、reducer、view 以及 dependencies 完成了实例化的整个过程。
首次页面渲染
当我们打开 UserHomePage
时,会执行如下 routes.buildPage
(即 类 Page
的 buildPage
方法),接受两个参数分别为 路由名和传入该页面的参数:
Class Page - Method buildPage
protectedWrapper
: 默认返回_PageWidget
, 也支持自定义的包装(UserHomePage
中传入属性wrapper
,用的较少不必过多关注)。- ❗️
_PageWidget
: 本质上是一个StatefulWidget
[^8],页面渲染的最核心部分。 - 💡 Lifecycle:Fish Redux 中默认的所有生命周期本质上来源于 Flutter Stateful Widget 的生命周期,Reducer 的生命周期和页面是一致的(
initState
->didChangeDependencies
->build
……)。
_PageWidget
重点看一下 _PageWidget()
的整个流程(核心要素类图):
_PageWidget()
中 initState()
和 build()
相关的执行过程:
_PageWidget()
- initState
中最重要的 createStore
的操作:
_initState(param)
: UserHomePage 中定义的initState
在这里调用,接受路由参数对 State 进行初始化操作。createReducer()
过程中最核心的是combineReducers
:- 入参
[protectedReducer, protectedDependenciesReducer]
: 1.protectedReducer
就是我们自己在reducer.dart
文件中定义的buildReducer()
; 2.protectedDependenciesReducer
指的就是 依赖的子组件的 reducer,也是通过类似combineReducers
的方式生成的(后文「createReducer - 生成子组件的 Reducer」部分再来看一下这个过程 )。 combineReducers
:会做一些 reducer 的判空过滤,最终返回一个 colsure —— Reducer,在后续的 dispatch 相关操作时会执行。
- 入参
createReducer - 生成子组件的 Reducer
slots
就是我们在page.dart
中定义的依赖子组件,createReducer()
中通过循环遍历创建子组件(createSubReducer()
)的reducer
列表,最后通过上文中相同的combineReducers
以及combineSubReducers
把 reducer 结合起来。createSubReducer()
最终是执行connector.subReducer(logic.createReducer())
(redux_component/dependent.dart):logic.createReducer()
其实就是执行logic.dart
中的createReducer()
。connector.subReducer(…)
最终执行MutableConn
的subReducer()
:get
:上文ToolsConnector
父子组件状态连接器中自己定义的,让子组件获取相应状态数据;- 根据 action 和 props 生成新的状态,并且与老的 state 进行对比;
- 如果有变化 clone 一份新的返回,并且通知 view 改变相应状态,否则直接返回当前 state。
combineReducers
与上文一致,combineSubReducers
核心最终还是会调用 上文中的subReducer
。
_PageWidget()
- build()
相关操作
build()
最终调用链路会到 ComponentWidget
, 也是一个 StatefulWidget
,主要做了两件事情:
void initState()
:根据 store、bus 等创建一个 context,这个 context 会在整个页面周期中使用。比如页面中渲染子组件的操作 ——viewService.buildComponent('your-component-name')
。Widget build(BuildContext context)
: 真正创建视图的地方
以上就是 Fish Redux 整个自动配置组装的过程,也简单说了下首次渲染的部分。
最后
源码解析部分并没有把所有细节都阐述,如果感兴趣的朋友欢迎一起探讨。
有任何问题欢迎关注 「百瓶技术」公众号后发消息提问,会第一时间回复。
参考
[^1]: Redux https://redux.js.org
[^2]: GitHub Fish Redux 源码 (2019-03-06 开源)https://github.com/alibaba/fish-redux
[^3]: 闲鱼技术:Flutter Fish Redux 架构演进 2.0 (2021-05-18)https://mp.weixin.qq.com/s/8vFDLq3WaeImyQNb--8ZyQ)
[^4]: Google Protocol Buffers: https://developers.google.com/protocol-buffers
[^5]: Fish Redux 官方例子:https://github.com/alibaba/fish-redux/tree/master/example
[^6]: Visual Studio Code Fish Redux Template Extension: https://marketplace.visualstudio.com/items?itemName=huangjianke.fish-redux-template
[^7]: Flutter architectural overview: https://flutter.dev/docs/resources/architectural-overview
[^8]: Flutter StatefulWidget: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
[^9]: Fish Redux 官方文档:https://github.com/alibaba/fish-redux/blob/master/docs/zh-cn/README.md
[^10]: Flutter Fish_Redux 3.0起航!:https://mp.weixin.qq.com/s/YYyU2yoM61VuApz9FsUxQw