React状态管理:Recoil

作者:renzp94
时间:2021-08-10 14:55:16

React中有时需要不同层级的组件状态共享,虽然context可以实现,但在比较复杂的场景中有点乏力,所以慢慢的变出现了状态管理,比较常用的状态管理库有:Reduxmobx。但这些库其实都是第三方维护,在React Europe 2020 Conference上,Facebook开源了一款状态管理Recoil

特点

  • 极简的React设计风格:拥有与React一样的工作方式与原理。将其添加到应用中可获取快速、灵活的状态共享
  • 数据流图:针对派生数据(Derived data)和异步查询采用纯函数以及高效订阅的方式进行处理
  • 应用程序全局监听:通过监听应用程序中所有状态的变化来实现持久化存储,路由,时间旅行调试或撤销,并且不会影响代码分割

安装

npm install recoil // or yarn add recoil

核心概念

Recoil创建一个数据流有向图,状态的变化从该图的顶点(atom(共享状态))开始,流经selector(纯函数),再流向React组件。Atom是组件可以订阅的stateselector可以同步或异步改变此state。可以大概想象一下,数据流形成的向图:一个atom被多个组件订阅,则此atom指向每个组件,第二个也被多个组件订阅,则第二个atom也指向多个组件,这两个atom的被订阅组件可能是同一个,也可能不是同一个,则便形成了atom指向多个组件的交叉向图。

Recoil的概念很少,只有AtomSelector,下面依次了解一下

Atom

Atom是状态的单位,可以更新也可订阅,当atom被更新,每个被订阅的组件都会用新值重新渲染。可用atom来代替组件内部状态,当多个组件使用相同的atom,则这些组件共享atom的状态。

创建

AtomRecoil.atom函数创建,接收一个对象:

  • key:标识当前atom的唯一key值,请记住:key需要保持全局唯一
  • default:默认值
  • dangerouslyAllowMutability:设置允许atom中的对象可变,对象变化不代表status的变化
import { atom } from 'recoil
const countState = atom({
    key:'count',
    default: 0
})

使用

创建的atom不能直接使用,需要通过Recoil提供的hook来使用,比如:需要读写atom的状态值,则可以使用Recoil.useRecoilState函数

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

const countState = atom({
    key: 'count',
    default: 0
});

const Counter = () => {
    const [count, setCount] = useRecoilState(countState);

    return (
        <>
            <button onClick={() => setCount((count) => count - 1)}>-</button>
            <span>{count}</span>
            <button onClick={() => setCount((count) => count + 1)}>+</button>
        </>
    );
};

const App = () => {
    return (
        <RecoilRoot>
            <Counter />
        </RecoilRoot>
    );
};

export default App;

注意:所有使用Recoil的组件需要使用RecoilRoot包裹

上述例子和使用useState比起来似乎没有什么变化只是新创建了一个countState并通过useRecoilState使用,但是countState是可以多组件共享的,为了实现多组件共享,将上述代码改造一下。

store/count.ts

import { atom } from 'recoil';

export default atom({
    key: 'count',
    default: 0
});

components/Count/index.tsx

import React from 'react';
import { useRecoilValue } from 'recoil';
import coutnState from '../../store/count';

const Count = () => {
    const count = useRecoilValue(coutnState);
    return <span>{count}</span>;
};

export default Count;

components/Count/CountButton.tsx

import React from 'react';
import { useSetRecoilState } from 'recoil';
import coutnState from '../../store/count';

const CountButton = (props: { type: string }) => {
    const setCount = useSetRecoilState(coutnState);
    const onAdd = () => setCount((count) => count + 1);
    const onDec = () => setCount((count) => count - 1);

    const text = props.type === 'add' ? '+' : '-';
    const onClick = props.type === 'add' ? onAdd : onDec;

    return <button onClick={onClick}>{text}</button>;
};

export default CountButton;

App.tsx

import React from 'react';
import { RecoilRoot } from 'recoil';
import Count from './components/Count';
import CountButton from './components/Count/CountButton';

const App = () => {
    return (
        <RecoilRoot>
            <CountButton type="dec" />
            <Count />
            <CountButton type="add" />
        </RecoilRoot>
    );
};

export default App;

通过改造的代码之后,可以看出只要使用Recoil用于状态管理是多么简单

Selector

Selector是一个纯函数,入参是atomselector。当传入的atomselector更新时,将会重新执行selector函数重新渲染组件,类似Vue中的compoted。一般被用于计算基于state的派生数据,可避免冗余的state

创建

SelectorRecoil.selector创建,接收一个对象:

  • key:同atomkey
  • get:用于计算的函数,接收一个对象,对象中有一个get函数用于获取其他的atomselector的值
import React from 'react';
import { atom, RecoilRoot, selector, useRecoilState, useRecoilValue } from 'recoil';

const countState = atom({
    key: 'count',
    default: 0
});

const doubleCountSelector = selector({
    key: 'doubleCount',
    get: ({ get }) => get(countState) * 2
});

const Count = () => {
    const [count, setCount] = useRecoilState(countState);
    const doubleCount = useRecoilValue(doubleCountSelector);

    return (
        <>
            <div>
                <button onClick={() => setCount((count) => count - 1)}>-</button>
                <span>count: {count}</span>
                <button onClick={() => setCount((count) => count + 1)}>+</button>
            </div>
            <div>double count: {doubleCount}</div>
        </>
    );
};

const App = () => {
    return (
        <RecoilRoot>
            <Count />
        </RecoilRoot>
    );
};

export default App;

上述只是Selector的基础用法,还有更多的Selector用法,比如set,异步Selector,此文章只是先简单了解一下Recoil