1. 选手入场
一提起 React 状态管理,很多人就提也不想提。
Redux: 1 号选手,人气最高的一个,得与 react-redux
一同使用。自己不管异步,所以常听到的什么 redux-thunk
、redux-saga
是其下属,专门擦屁股的。特点就是难用,如果按照官方文档的写法用,基本就是想死。推荐阅读:https://www.zhihu.com/question/333621770/answer/740545003
Mobx: 2 号选手,使用监听的方式,与 React 的思想背道而驰,思想有问题,流氓!
dva: Redux 封装,使用了 redux-saga
,generator 的写法,想死。
Rematch: Redux 封装,类似 dva,model
中分为 reducers
和 effects
,不够简洁。
广告:所以还是 Retalk 最简单,史上最简单的 Redux 封装,走过路过不要错过:
nanxiaobei/retalkgithub.com![fb76ece9c8476fdddbddb2d2f2ca55b9.png](https://i-blog.csdnimg.cn/blog_migrate/c4b466550f4a50376f6b73e2e61da4b9.jpeg)
2. React Hooks 来了
不管怎么说,Hooks 来了,以前的状态管理都是给以前的开发方式用的,为了对 Hooks 有点表示,也就加几个 useBlabla()
完事。
与 Hooks 的轻灵相比,它们都显得笨重、老态龙钟、敷衍。
有没有专用于 Hooks 的状态管理呢?目前还没有众望所归的,基本都是老选手搞个兼职,有点不伦不类。
不够革命。
3. 我们为什么需要状态管理?
一个是为了解决相邻组件的通信问题。
虽然可以通过「状态提升」解决,但有两个问题:
- 每次子组件更新,都会触发负责下发状态的父组件的整体更新(使用 Context 也有这个问题),然后写一大堆
PureComponent
、shouldComponentUpdate
,代码还能看吗?React 设计中的糟粕写了个够,太惨。 - 逻辑比较多的话,都写在父组件里,代码还能看吗?根本不考虑父组件的感受。
所以「状态提升」不是个好思路,还是需要状态管理。
另外,状态管理最重要的一个好处,就是 ——
它可以把「与服务器交互」和「操作数据」的逻辑,从组件中提取出去,组件只用接收处理好的数据即可。这就相当于分离了个 Service 层出去,很好的开发模式。
代码逻辑分离,各司其职,组件去干组件该干的事,这才是最大的好处。
所以我们需要状态管理。
4. 我们需要什么样的状态管理?
如果一开始是从 Redux 上手,会发现它提供了一个全局的 store。然后呢?然后它也没说什么话,有什么问题就自己解决吧,我先睡了。
实际业务开发中,遇到的最常见的场景,就是需要区分不同的「模块」,这是最基本的需求。
Vuex 比较务实,提供了 modules
的概念。
Redux 呢?当然是得靠你自己啦,我们提供一个理念就行了,这么简单的东西,你就自己去搞吧。
如果不从 API 设计上进行规范,必然会导致无数不同的实现。(单押 x 2)
CHAOS.
我们需要什么样的状态管理?
我们需要符合中国特色社会主义初级阶段国情的状态管理。
我们需要可以划分不同的模块,模块是独立的,同时模块间又是可以自由沟通的。
很多状态管理库只考虑了模块的「独立」,但是对「沟通」保持沉默,这是不友好的。
独立,且连通 —— 这才是模块的意义所在。
5. 模块如何划分?
一般来说,推荐按照基本的路由入口,划分出不同的模块,这也是 dva 中的思路(它的文件夹干脆就叫 routes
)。
这也符合自然的思维方式,划分路由,也就是天然的认为它们属于不同的模块。
而每个状态管理的模块,我们称之为 “model”,建议与路由入口组件绑定起来,每个入口组件及其子组件,整体对应一个 model。
---A
index.jsx
model.js
---B
index.jsx
model.js
---C
index.jsx
model.js
6. 模块如何沟通?
模块是独立的,毋庸置疑,模块模块,不独立还叫模块吗?
那模块如何沟通呢?
在组件中,可以获取到自身 model 和其它的 model,这并不难。
主要是,在 model 内部,自身各个方法需要互相调用,同时需要拿到其它 model 的数据和方法,这才是沟通的意义。
还得足够简单。
这样才是一个良好而彻底的设计。
7. Hooks 组件中如何获取 model?
最直接的,我们希望在组件中,通过 Hooks 去访问到某个 model。
const { someStateInA, someActionInA } = useModel(a);
// or
const { someStateInA, someActionInA, someStateInB, someActionInB } = useModels(a, b);
useModel()
中传入某个 model,这样设计的话,需要哪个 model,就得在组件中引入 model 的文件,不够自由,不能依赖于必须去引入文件。
只要操作麻烦,代码写的变多,就不是好的设计。
另外 Hooks 是为了让代码更清晰,我们不能像 Mobx 一样,违背整体系统的设计哲学。
所以 model 获取最好是分开的,useModels(a, b)
一次性引入多个 model 的方式,不够清晰。
不能依赖文件,引入得清晰,所以:
const { someStateInA, someActionInA } = useModel('a');
const { someStateInB, someActionInB } = useModel('b');
依赖一个字符串,这样就不需要在每个组件中引入文件。useModel()
只能访问一个 model,代码足够清晰。
这是我们需要的设计。
8. model 结构如何设计?
一个 model 总体来说可以分为两大块,数据(state
)和操作数据的方法(reducers
、effects
)。
reducers
与 effects
,或者 Vuex 中的 mutations
与 actions
,是为了区分直接改变和异步操作,同步函数和异步函数。不够简洁,能不能合并成一个?
答案可以的,我们称之为 actions
。
更新 state 的方法,直接注入 model 中,所以一个 action 可以是同步也可以是异步,它只是被当做函数来调用。
于是,model 中分为两块就好:
exports default {
state: {},
actions: {},
}
9. model 沟通如何设计?
model 沟通,主要是实现在 action 中去访问其它需要的东西。
自由而无限制的沟通。
一个 action 中需要访问到以下:1. 自身 state,2. 自身 action,3. 其它模块 state,4. 其它模块 action,5. 自身 state updater。
简化一下,就是需要访问:1. 自身 model,2. 其它 model,3. 自身 state updater。
再简化一下:1. model,2. 自身 state updater。
所以其实只需要两个方法:getModel()
和 setState()
。
getModel()
获取自身,getModel('other')
获取其它。
如何拿到这两个方法呢?
- 通过
this
访问。但我们是给 Hooks 设计状态管理,Hooks 就是为了抛弃this
,还是那句话 —— 不能违背系统的设计哲学。 - Vuex 中,是在函数第一个参数注入 context,调用时比较反直觉,放弃这种方式。
- model 文件中 import 方法进来,太麻烦了,只要操作麻烦,代码写的变多,就不是好的设计。
所以唯一的方式(Rematch 的设计中也能看到),就是在参数中注入方法。
也就是把 actions
变为一个函数:
exports default {
state: {},
actions: ({ getModel, setState }) => ({
someAction() {
const { someState, someOtherAction } = getModel();
}
}),
}
这里考虑到一个美学问题,getModel()
因为很多 action 都会用到,而实际写出来时,因为 l
高度的问题,又比较丑,所以就把 API 简化为了 model()
。
model()
获取自身,model('other')
获取其它。
10. flooks 是什么?
于是, flooks,福禄克斯,诞生了:
nanxiaobei/flooksgithub.com![fb76ece9c8476fdddbddb2d2f2ca55b9.png](https://i-blog.csdnimg.cn/blog_migrate/c4b466550f4a50376f6b73e2e61da4b9.jpeg)
import { setModel, useModel } from 'flooks';
const a = {
state: {},
actions: ({ model, setState }) => ({
someAction() {
const { someState, someOtherAction } = model();
...
setState(payload);
}
}),
}
setModel('a', a);
function A() {
cosnt { someState, someAction } = useModel('a');
return (
...
)
}
天然的模块化支持。
只有两个 API,setModel()
和 useModel()
。
加上 actions
的两个参数,model()
和 setState()
,就这么多。
足够简单,足够实现一切。
One more thing. 设计哲学
去中心化,去中心化,去中心化。
一切都为了更简单的开发。
再不需要顶层分发 store,不需要 store 文件夹或 store.js 文件。
无需顶层 createStore()
,无需顶层 Provider
。
注册 model 时,无需先在中心化文件中引入 model,自身文件夹内就可以搞定一切。
直接用 setModel()
把组件和 model 连接起来,就这么简单。
模块化,积木一样组合,简洁,灵活,这是我们的理念。
每个组件和 model 构成一个自组织,同时它们又可以访问到世界各地。它们不用去有关部门报道,就能在世界的任何角落相遇。
这是我们的哲学:独立,自由,个体,世界。