「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
「百瓶 App」(各市场搜索「百瓶」体验 🥳)在 2019 年 11 月开始接入 Flutter 时,就选定了闲鱼的 Fish Redux 作为状态管理方案。经过近两年的迭代,已经完成了 80 个以上的 Page 级功能,积累了丰富的使用经验。虽然 GitHub1 迭代更新较少,但是它在 2021-05-18 对外宣布正在进行 「2.0 架构的演进2」以及最近释放的 「Flutter Fish_Redux 3.0起航!3」,说明内部还是坚持在业务中使用并且对框架进行持续优化的。
本文主要阐述 Fish Redux 两大方面的知识:
- 简单介绍核心概念以及基于 Fish Redux1 的「最佳」研发流程;
- 深入剖析配置式组装。
(备注:1. 阅读本文需要对 Flutter(v1.17.1) 和 Fish Redux(v0.3.7)(或者 Redux4)有简单的了解;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 的页面研发,ProtocolBuffer5(没接触过的直接理解为 API 文档即可)也已经设计完毕。
那么,基于 Fish Redux 的最佳研发流程是怎么样的?
简单三步完成复杂页面的协同研发:
- 首先,进行组件拆分:拆分为五个大的组件(如图框所示,红 - profiles_component、绿 - malls_components、黄 - wallets_component、紫 - banners_component、蓝 - tools_component)。由于 Fish Redux 良好的分治策略,这时候完全可以分配给五个同学分别去完成单组件的开发(此时整个页面的目录结构如下图)。
- 其次,五位同学进行单组件的开发(以 蓝 - tools_component 为例):
- 最后,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 基本一致,可以查看相关代码 7 加深理解。
接下去,将重点阐述配置组装的两个最核心的流程:注册 和 首次页面渲染。
注册
一句代码完成用户中心页面的注册。
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.dart1) 实例(_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: 本质上是一个StatefulWidget8,页面渲染的最核心部分。 - 💡 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 整个自动配置组装的过程,也简单说了下首次渲染的部分。
最后
源码解析部分并没有把所有细节都阐述,如果感兴趣的朋友欢迎一起探讨。
有任何问题欢迎关注 「百瓶技术」公众号后发消息提问,会第一时间回复。
更多精彩请关注我们的公众号“ 百瓶技术 ”,有不定期福利呦!
参考
Footnotes
-
GitHub Fish Redux 源码 (2019-03-06 开源)github.com/alibaba/fis… ↩ ↩2 ↩3
-
闲鱼技术:Flutter Fish Redux 架构演进 2.0 (2021-05-18)mp.weixin.qq.com/s/8vFDLq3Wa…) ↩
-
Flutter Fish_Redux 3.0起航!:mp.weixin.qq.com/s/YYyU2yoM6… ↩
-
Google Protocol Buffers: developers.google.com/protocol-bu… ↩ ↩2
-
Visual Studio Code Fish Redux Template Extension: marketplace.visualstudio.com/items?itemN… ↩ ↩2
-
Fish Redux 官方例子:github.com/alibaba/fis… ↩
-
Flutter StatefulWidget: api.flutter.dev/flutter/wid… [9]: Fish Redux 官方文档:github.com/alibaba/fis… ↩