recoil

简介

Recoil是一个由Facebook开源的react状态管理库。(他并不是脱离框架的库,他是专门为react而生的。)

设计理念

假设有如下场景,List中的一个节点更新,Canvas中的节点也相对更新。 image.png 常规做法就是将一个state通过父组件传递给List和Canvas两个组件,这样的话每次state发生改变后,所有节点都会发生更新。

当然,我们还可以使用Context,将节点的状态存储到Context,当Provider中的props发生改变,Provider的所有后代使用者都会重新渲染 image.png 为了避免全量渲染的问题,可以把每个子节点的状态存储在独立的Context中,这样每多一个节点都要额外增加一层Provider。

image.png Recoil通过创建一个正交的图,将state和使用到state的组件对应起来,实现精准更新。

image.png

image.png 从这幅图中可以看出,Atom既可以被组件订阅,也可以被selector订阅,Selectors可以依赖Atom,也可以依赖其他的Selector。Selector也可以做为一个独立的状态被组件订阅。

基本使用

Atom

Atom表示Recoil中的state,atom()函数返回一个可写可订阅的RecoilState对象,一般情况下他接受一个唯一标识的key和default默认值。

import {atom} from 'recoil';

const numAtom = atom({
    key: 'numAtom',
    default: 0
})
复制代码

Selector

selector表示派生状态,他是一个纯函数,对于给定的一组输入,它们应该始终产生相同的结果。selector如果只提供get方法,那selector就是只读的,如果还提供了set方法,那selector就是可写的。selector也可以被组件订阅,在发生改变的时候通知组件重新渲染。

const numLength = selector({
    key: 'numLength',
    get: ({get}) => {
    // get方法可以获取其他atom或selector的值并依赖他们,当get中传入的atom或selector发生改变
    // 当前selector就会重新计算值
        const num = get(numAtom);
        return num.length;
    },
    set: ({get, set}, newValue) => {
    // 这里的get不会订阅给定的atom或selector
        set(state, newValue);
    }
})
const length = useRecoilValue(numLength);
复制代码

selector还支持异步函数,可以将一个Promise作为返回值:

const List = selector({
    key: 'List',
    get: async ({get}) => {
        return await action('web/api/notice/list', get(page))
    }
})
复制代码

useRecoilState

import {useRecoilState, useRecoilValue, useSetRecoilState, useResetRecoilState} from 'recoil';
// 对atom读写
const [numAtomValue, setNumAtomValue] = useRecoilState(numAtom);
// 只读atom
const numAtomValue = useRecoilValue(numAtom);
// 只写atom,这种场景下组件不会订阅atom
const setNumAtomValue = useSetRecoilState(numAtom);
// 返回一个函数,该函数可以将atom重置为默认值
const resetNum = useResetRecoilState(numAtom);
复制代码

其他API

useRecoilStateLoadable

该API可以读取异步selector的值

state:当前的状态 hasValue loading hasError

contents:返回的数据

function List({page}) {
  const [listDataLoadable, setListData] = useRecoilStateLoadable(getList(page));
  switch (listDataLoadable.state) {
    case 'hasValue':
      return <Table data={listDataLoadable.contents}/>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      return(`出错了:${listDataLoadable.contents}`);
  }
}
复制代码

noWait

一个selector辅助方法,返回值为所提供的atom或selector当前状态的loadable。 该方法类似于useRecoilStateLoadable,只不过它返回一个selector,所以她可以被其他的selectors以及钩子所使用。所以针对请求也有了第二种对于loading success error的处理方法

const myQuery = selector({ 
key: 'MyQuery', 
get: ({get}) => { 
    const loadable = get(noWait(dbQuerySelector));
    return { 
        hasValue: {data: loadable.contents}, 
        hasError: {error: loadable.contents}, 
        loading: {data: 'placeholder while loading'}, 
       }[loadable.state]; 
    }
})
复制代码

atomFamily

当需要atom的默认值可以参数化的情况下,可以使用atomFamily API

const myAtom = atomFamily({
    key: 'myAtom',
    default: params => defaultByParams(params)
})

const [myAtomState, setMyAtomState] = useRecoilState(myAtom({id: 1}))
复制代码

useGetRecoilValueInfo

该API可以查看atom或selector的状态,值和其他信息。主要用于开发过程中调试。

loadable:返回当前的状态

type:类型 atom或selector

dep:该节点所依赖的atoms或selectors

isModified:如果是修改过的atom,则True

useRecoilRefresher

该API可以刷新selector,适用刷新列表或当请求发生错误时重试

isRecoilValue

该API可以判断值是否时一个atom或selector

原理

整体思路

  1. 创建一个 atom 对象
  2. 使用 selector 的时候,会通过 get 来获取到依赖的 atom,生成一个 Map 映射关系
  3. 使用 useRecoilState Hook 的时候,会将当前 atom/selector 和组件的 forceUpdate 方法进行映射
  4. 当对状态进行修改的时候,会从映射关系里面取出来对应的组件 forceUpdate 方法,进行精准更新

StoreState

比较关键的有knownAtoms,knownSelectors,nodeToComponentSubscriptions image.png

Atom

atom 是 Recoil 里面的原子状态,它是分散的状态,但在底层实现上还是会聚拢在一个 Store 里面,只是从写法上是分散的。initAtom在组件里面get的时候调用,将atom注册到knownAtom上,这样atom就被聚拢到一个大的Store里面。

image.png

atom 方法最后返回了一个用 registerNode 生成的 node 节点,这里就是我们能获取到的真实的 atom。

registerNode 的实现比较简单,主要是 new 了一个类,然后将其放到 recoilValues 里面。这两个类只有一个属性 key,所以最后拿到的 recoilValue 只是一个 key 值。

image.png

selector

selector 的实现和 atom 类似,最后也是返回了一个 registerNode,它的 init 方法里面也会将 key 加入到 knownSelectors 字段里面。

image.png 可以看到在 try...catch 里面调用了传进来的 get,他调用了 setDepsInStore 来设置 selector 和 atom 的依赖关系。将 atom 的 key 加入到 deps 里面,从而映射了一个从 selector key 到多个 atom key 的 Map 关系。最后将映射关系存入到 storeState 的 nodeToNodeSubscriptions 字段里面。

image.png

useRecoilValue

useRecoilValue 是个 Hook,它用于组件订阅 atom 变化。它的源码位置在 Recoil_Hooks.js 文件里面。主要看 useRecoilValueLoadable 这个方法。

useRecoilState

它和 useRecoilValue 被集成到了 useRecoilState 里面。它的主要源码在 Recoil_RecoilValueInterface.js 的 setRecoilValue 里面。

总结

分散管理、读写分离、按需渲染、派生缓存。

分类:
前端
标签: