React状态管理库-Recoil

6,532 阅读5分钟

前言

Recoil是FaceBook公司提出的状态管理方案,相当于和React是师出同门,虽然我目前在工作当中用的是Mobx,并且使用起来也是十分的爽,但是Recoil非常有必要去了解一下,所以在学习之后写一篇文章记录一下!

我们都知道React强调的是immuteable,而Recoil强调的同样也是immuteable,而mobx则是mutable,而immuteable给带来的好处就是增强组件整体的应用性能,相对来说,确定可变对象是否变更是复杂的,因为如果对一个可变的对象,我们需要判断它是否改变,这就需要当前对象与先前副本进行比较,我们需要遍历整颗对象树,并且比较每个变量和值,这个过程将会变得很复杂,而确定不可变对象是否改变是非常容易的,我们只需要判断对象的引用是否相同,如果引用改变了那就改变了,经此而已。对React来说,因为使用的是不可变数据,那么对UI渲染来说,判断组件是否重新渲染则来的更容易些。

关于Recoil

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

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

因为 React 本身提供的 state 状态在跨组件状态相对来时是比较困难,当然我们可以用context,所以我们在开发时一般借助一些其他的库如 Redux、Mobx 来帮助我们管理状态。这些库目前正被广泛使用,我们也并没有遇到什么大问题,那么 Facebook 为什么还要推出一款新的状态管理框架呢?

使用 Redux、Mobx 当然可以,并没有什么问题,主要原因是它们本身并不是 React 库,我们是借助这些库的能力来实现状态管理。像 Redux 它本身虽然提供了强大的状态管理能力,但是使用的成本非常高,你还需要编写大量冗长的代码,另外像异步处理或缓存计算也不是这些库本身的能力,甚至需要借助其他的外部库。

并且,它们并不能访问 React 内部的调度程序,而 Recoil 在后台使用 React 本身的状态,在未来还能提供并发模式这样的能力。

Recoil基础

初始化

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

import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
  useSetRecoilState
} from 'recoil';

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

定义状态

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

export const nameState = atom({
  key: 'nameState',
  default: '栗鼠怪'
});

其中 key 必须在 RecoilRoot 作用域内唯一,也可以认为是 state 树打平时 key 必须唯一的要求。 default 定义默认值,既然数据定义分散了,默认值定义也是分散的。

订阅和更新状态

  • useRecoilState:类似 useState 的一个 Hook,可以取到 atom 的值以及 setter 函数
  • useSetRecoilState:只获取 setter 函数,如果只使用了这个函数,状态变化不会导致组件重新渲染
  • useRecoilValue:只获取状态
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>;
}

从上面我们可以看到我们取值和改变值是使用hook的形式,对于习惯react函数组件来说是非常友好的

派生状态

selector 表示一段派生状态,它使我们能够建立依赖于其他 atom 的状态。它有一个强制性的 get 函数,其作用与 redux 的 reselect, MobX 的 @computed, 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,我们当可以从Recoil学到关于React状态管理的基本功,它的设计模式是分散式管理,这里有点像mobx,但是在使用方式上完全拥抱react函数式中Hook的使用,关于派生状态我们可以使用recoilselector,也可以使用useMemo。毕竟是 Facebook 官方推出的状态管理框架,其主打的是高性能以及可以利用 React 内部的调度机制,包括其承诺即将会支持的并发模式,这一点还是非常值得期待的。最近笔者也有去了解RTK,即redux-toolkit,下篇文章应该会去记录和学习它的使用!