React状态管理库Recoil浅入浅出
一、引言
Recoil 的 slogan 十分简单:一个react状态管理库(A state management library for React)
Recoil 的产生源于 Facebook 内部一个可视化数据分析相关的应用,在使用 React 的实现的过程中,因为现有状态管理工具不能很好的满足应用的需求,因此催生出了 Recoil 。
这个应用带有复杂的交互,可以被总结为以下特点:
- 大量需要共享状态的场景
- 大量需要派生状态(基于某些状态计算出一个新的状态)的场景
- 状态可以被持久化,进而通过被持久化的状态恢复当时场景
二、注意📢
但需要注意的是,此项目是facebook内部的实验性项目,截止2022年11月8日,此项目目前的最新的版本号是0.7.6,还没有出正式版本。
包括其官方文档看着看着就会出来个 UNSTABLE 或者UNSAFE的标签
如果你的项目,大型项目或者维护很持久的项目(ps: 3、5年以上的)个人不是很推荐用,小项目可以试水,完全没有问题。写法很舒服,函数式调用及其简易。
三、其他状态管理方案比较
当然共享状态还有其他方法:
React自身
Redux
Mobx
Context
3.1. 各个方案NPM Trends对比
3.2. Stars 对比
如果用这些方法的会出现什么问题或者说一些缺点不太舒服的地方?当然它们本身都很优秀,不然也不会那么大的下载量。
3.3. React自身
React本身解决数据共享是通过提升状态来解决的,这个就自然就会因为某个组件的状态改变而导致所有的子组件重新渲染,尽管我们可以使用memo
来优化,但是唤醒的问题依然存在,对比前后的Props的操作依然无法避免。另一个问题是,一旦又有一个组件需要观察共享的数据,那又需要继续提升数据,又麻烦了
3.4. Redux
Redux 其大致工作流程,一般来说是这样的:
- 用户在页面上进行某些操作,通过
dispatch
发送一个action
。 - Redux 接收到这个
action
后通过reducer
函数获取到下一个状态。 - 将新状态更新进
store
,store
更新后通知页面进行重新渲染。
从这个流程中不难看出,Redux 的核心就是一个 「发布-订阅」 模式。view 订阅了 store 的变化,一旦 store 状态发生修改就会通知所有的订阅者,view 接收到通知之后会进行重新渲染。
3.5. Mobx
Mobx 是 React 的另一种经过战火洗礼的状态管理方案,和 Redux 不同的地方是 Mobx 是一个响应式编程(Reactive Programming
)库
Mobx 借助于装饰器的实现,使得代码更加简洁易懂。由于使用了可观察对象,所以 Mobx 可以做到直接修改状态,而不必像 Redux 一样编写繁琐的 actions 和 reducers。
这里的 action
不是必须的,但为了保证状态不会被随意修改,还是建议开启严格模式,只允许在 action
里面修改状态。
1 | import { action, observable } from 'mobx'; |
3.6. Redux 和 Mobx总结
- 一个Action会唤醒所有的订阅数据的组件,即使他们订阅的数据并没有发生变化,而且只能在数据变化后,通过浅比较(或者深比较)的方式对比前后的数据是否一致,来阻止无效渲染。(Mobx基本同理)
- 只能通过状态提升至公共祖先来共享状态,但可能导致一颗巨大的树重新渲染。
- 上下文(context)只能存储一个值,而不能存储一组不确定的值,且每个值都有自己的使用者(consumers)。
- 这两种方式都很难将组件树的叶子节点(使用状态的地方)与组件树的顶层(状态必须存在的地方)进行代码分拆。
3.7. Context
如果仅仅在依赖数据变化时候才更新的场景,使用Context可以完美的解决,当Context的数据变化时,只有监听了相关Context的组件才会重新渲染。但Context的问题是过于动态性的场景可能有些许问题,比如用户通过点击按钮添加内部的某个小组件,这样的话 对应的Context也需要动态的插入到顶层组件,方便共享数据给其他组件,但是由于React的diff策略,如果在顶层组件动态插入Context或任何组件,就会导致子组件树不断被 销毁重建,损耗性能。
四、Recoil介绍
Recoil的设计思想就是我们把状态拆分成一个个的Atom(原子),再由Selector派生出更多的状态,最后React的组件树订阅自己需要的状态,当有原子状态更新,只有改变的原子及其下游节点有订阅他们的组件才会更新。
Copy:也就是说,Recoil其实构建了一个有向无环图,这个图和React组件树正交,他的状态和React组件树是完全解耦的。
Recoil独立于React,单独构建出一套自己的状态树,这个状态树平行于组件树而存在,状态树由Atom和Selector构成。
状态树的基本单位是Atom(原子),一个Atom表示一份可变,可被订阅的状态,当Atom代表的状态改变时,只会重新渲染订阅了这个Atom的组件,而不会影响其他组件。
五、Recoil使用
Recoil是专门为React设计的状态管理库,他的API满满的“react风格”。 Recoil 只支持hooks API,在使用上来说可以说十分简洁。
5.1 RecoilRoot
1 | import React from 'react'; |
useRecoilState
当组件同时需要读写状态时,推荐使用该 hook。
const [comState, setComState] = useRecoilState(commonState);
useRecoilValue
当一个组件需要在不写入 state 的情况下读取 state 时,推荐使用该 hook。
const comState1 = useRecoilValue(commonState);
useSetRecoilState
返回一个 setter 函数,用来更新可写 Recoil state 的值,状态变化不会导致组件重新渲染。
当一个组件需要写入而不需要读取 state 时,推荐使用此 hook。
如果组件使用了 useRecoilState()
来获取 setter 函数,那么同时它也会订阅更新,并在 atom 或 selector 更新时重新渲染。使用 useSetRecoilState()
允许组件在值发生改变时而不用给组件订阅重新渲染的情况下设置值。
const setComState = useSetRecoilState(commonState);
5.2 Atom
一个 atom 代表一个状态。Atom 可在任意组件中进行读写。读取 atom 值的组件隐式订阅了该 atom,因此任何 atom 的更新都将导致订阅该 atom 的组件重新渲染:
1 | import {atom} from "recoil"; |
在需要向 atom 读取或写入的组件中,应该使用 useRecoilState()
,如下所示:
1 | import {useRecoilState, useRecoilValue, useSetRecoilState} from "recoil"; |
5.3 Selector
selector 代表一个派生状态,派生状态是状态的转换。你可以将派生状态视为将状态传递给以某种方式修改给定状态的纯函数的输出:
1 | import {selector} from "recoil"; |
这里想要获取textState的长度,因此只需读取值就可以 了,可以使用 useRecoilValue()
这一 hook:
1 | import {useRecoilValue} from "recoil"; |
六、Example
七、总结
虽然React相关的状态管理库很多,但是Recoil的一些思想还是非常先进的,社区对Recoil关注度也很高,目前 star 18k。但还是跟上述的注意事项一样,还不是稳定版本,npm下载量并不是特别高,大项目生产环境使用不推荐。
附1. 简易版Recoil
1 | import { useState, useEffect, useCallback } from 'react'; |