阅读 2174

如何在 React Hooks 项目中进行状态管理?

1. 选手入场

一提起 React 状态管理,很多人就提也不想提。

Redux: 1 号选手,人气最高的一个,得与 react-redux 一同使用。自己不管异步,所以常听到的什么 redux-thunkredux-saga 是其下属,专门擦屁股的。特点就是难用,如果按照官方文档的写法用,基本就是想死。推荐阅读:www.zhihu.com/question/33…

Mobx: 2 号选手,使用监听的方式,与 React 的思想背道而驰,思想有问题,流氓!

dva: Redux 封装,使用了 redux-saga,generator 的写法,想死。

Rematch: Redux 封装,类似 dva,model 中分为 reducerseffects,不够简洁。

广告:所以还是 🐤 Retalk 最简单,史上最简单的 Redux 封装,走过路过不要错过:github.com/nanxiaobei/…

2. React Hooks 来了

不管怎么说,Hooks 来了,以前的状态管理都是给以前的开发方式用的,为了对 Hooks 有点表示,也就加几个 useBlabla() 完事。

与 Hooks 的轻灵相比,它们都显得笨重、老态龙钟、敷衍。

有没有专用于 Hooks 的状态管理呢?目前还没有众望所归的,基本都是老选手搞个兼职,有点不伦不类。

不够革命。

3. 我们为什么需要状态管理?

一个是为了解决相邻组件的通信问题。

虽然可以通过「状态提升」解决,但有两个问题:

  1. 每次子组件更新,都会触发负责下发状态的父组件的整体更新(使用 Context 也有这个问题),然后写一大堆 PureComponentshouldComponentUpdate,代码还能看吗?React 设计中的糟粕写了个够,太惨。

  2. 逻辑比较多的话,都写在父组件里,代码还能看吗?根本不考虑父组件的感受。

所以「状态提升」不是个好思路,还是需要状态管理。

另外,状态管理最重要的一个好处,就是 ——

它可以把「与服务器交互」和「操作数据」的逻辑,从组件中提取出去,组件只用接收处理好的数据即可。这就相当于分离了个 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)和操作数据的方法(reducerseffects)。

reducerseffects,或者 Vuex 中的 mutationsactions,是为了区分直接改变和异步操作,同步函数和异步函数。不够简洁,能不能合并成一个?

答案可以的,我们称之为 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') 获取其它。

如何拿到这两个方法呢?

  1. 通过 this 访问。但我们是给 Hooks 设计状态管理,Hooks 就是为了抛弃 this,还是那句话 —— 不能违背系统的设计哲学

  2. Vuex 中,是在函数第一个参数注入 context,调用时比较反直觉,放弃这种方式。

  3. 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,福禄克斯,诞生了:


🍸 flooksgithub.com/nanxiaobei/…


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 构成一个自组织,同时它们又可以访问到世界各地。它们不用去有关部门报道,就能在世界的任何角落相遇。

这是我们的哲学:独立,自由,个体,世界。

关注下面的标签,发现更多相似文章
评论