likes
comments
collection

Recoil 新一代的 React 函数式编程 状态管理工具

作者站长头像
站长
· 阅读数 2

我为什么要用 Recoil?

起因是最近重构了一个 props 传递层级非常深,组件之间状态通信非常频繁的详情页面。整个代码梳理下来,很难维护,重构时考虑,做组件细分,把数据单拎出来,对数据做一个松散管理。

对比了下之前用过的 Redux、Mobx 感觉有点重,因为项目 纯 ts + Func hooks 形式重构,应用这两种比较常规的状态管理工具有两个弊端:

  1. 对于项目代码侵入比较大,要编写大量的状态管理代码(dispatch、action、reducer)
  2. 因为是外部库,它们并不能访问 React 内部的调度程序

主要还是觉得这两个库,对于项目使用来说,有点重了,没必要为了一个不算很复杂的数据管理和通信引入并不算小的 npm 依赖包。起初考虑用 Context 来处理,但是多个组件订阅要写多个 Provider 或者有一个根组件的 Provider 来接入数据,这就有会导致很多不必要的重绘和代码量。刚好有同事提了一嘴 Recoil(fb 自己为 react 提供的状态管理工具库),看了下资料,感觉跟自己的项目契合度还不错,遂试用了下,效果不错。


简单的介绍下 Recoil

Recoil 是 FaceBook 公司提出的状态管理方案(个人感觉像是给 react Func Comps 量身打造的轻量级状态管理工具)。我们都知道 React 强调的是 immutable,而 Recoil 强调的同样也是 immutable,immuteable 给带来的好处就是增强组件整体的应用性能。对于 immutable 不是很清楚的,建议查阅相关文档,这里不做阐述。

Recoil 采用分散管理原子状态的设计模式.

Recoil 提出了一个新的管理状态单位 Atom (原子化),它是可更新和订阅的,当一个 Atom 更新之后,每个订阅它的组件都会与之更新重新渲染,如果多个组件使用同一个 Atom ,那么这些组件将会共享他们的状态。

Recoil 新一代的 React 函数式编程 状态管理工具

从上图不难看出,它的核心理念是原子化拆分数据,通过订阅发布的模型对数据进行管理。

Recoil基础

初始化

使用Recoil的组件需要使用RecoilRoot组件包裹起来

import React from 'react';
import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

定义状态

上面我们已经提到了 Atom 的概念, Atom 是一种新的状态,但是和传统的 state 不同,它可以被任何组件订阅,当一个 Atom 被更新时,每个被订阅的组件都会用新的值来重新渲染。

export const pageInfoState = atom({
  key: 'pageInfoState',
  default: {}
});

其中 key 必须在 RecoilRoot 作用域内唯一。default 定义默认值。

订阅和更新状态

useRecoilState:类似 useState 的一个 Hook,可以取到 atom 的值以及 setter 函数。useSetRecoilState:只获取 setter 函数,如果只使用了这个函数,状态变化不会导致组件重新渲染。useRecoilValue:只获取状态。useResetRecoilState: 重置状态。

import { nameState } from './store'
// useRecoilState
const NameInput = () => {
  const [name, setName] = useRecoilState(nameState);
  const onChange = (event) => {
   setName(event.target.value);
  };
  return <>
   <input type="text" value={name} onChange={onChange} />
   <div>Name: {name}</div>
  </>;
}

// useRecoilValue
const SomeOtherComponentWithName = () => {
  const name = useRecoilValue(nameState);
  return <div>{name}</div>;
}

// useSetRecoilState  
const SomeOtherComponentThatSetsName = () => {
  const setName = useSetRecoilState(nameState);
  return <button onClick={() => setName('Jon Doe')}>Set Name</button>;
}

// useReSetRecoilState  
const SomeOtherComponentThatSetsName = () => {
  const ressetName = useSetRecoilState(nameState);
  return <button onClick={ressetName}>Set Name</button>;
}

从上面我们可以看到我们取值和改变值是使用 hooks 的形式,对于 react 函数组件来说是非常友好的,代码侵入非常小。

派生状态

selector 表示派生状态,它使我们能够建立依赖于其他 atom 的状态(也可以进行一些计算操作)。它有一个强制性的 get 函数,有点类似 Vue 中的 computed 相似.

const lengthState = selector({
  key: 'lengthState', 
  get: ({get}) => {
    const text = get(nameState);
    return text.length;
  },
});

function NameLength() {
  const length = useRecoilValue(charLengthState);
  return <>Name Length: {length}</>;
}

异步状态

Recoil 提供了通过数据流图将状态和派生状态映射到 React 组件的方法。真正强大的功能是图中的函数也可以是异步的。这使得我们可以在异步 React 组件渲染函数中轻松使用异步函数。使用 Recoil,你可以在选择器的数据流图中无缝地混合同步和异步功能。只需从选择器 get 回调中返回 Promise ,而不是返回值本身。

例如下面的例子,如果用户名存储在我们需要查询的某个数据库中,那么我们要做的就是返回一个 Promise 或使用一个 async 函数。如果 userID 发生更改,就会自动重新执行新查询。结果会被缓存,所以查询将仅对每个唯一输入执行一次(所以一定要保证 selector 纯函数的特性,否则缓存的结果将会和最新的值不一致)。

const userNameQuery = selector({
  key: 'userName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(userNameQuery);
  return <div>{userName}</div>;
}

总结

使用下来以后,感觉 Recoil 的分散式状态管理,有点像 observable + computed 的模式。具体源码我还没有去看过,找个时间看过之后,再补一个源码解读。使用方式上,完全是迎合函数式组件 Hooks 的使用方式,没有提供 Component 的使用方式。

官方出品必属精品,官方文档所宣扬的高性能和可利用 React 内部的调度机制,包括之前承诺的并发模式,还是比较值得期待的(听听就好了)。主要还是看跟自己项目的契合度,合适你就用,工程和项目复杂度上来了,该稳重还是选稳重的状态管理方案吧,反正我下一阶段打算,把 Recoil 这玩意儿,在项目里面铺开体验了。

Recoil 官方文档链接

https://recoil.js.cn/