Recoil 学习笔记

0 阅读6分钟

很早以前学习 Recoil 时记录下的笔记,更新到掘金上

简介

数据特性:immutable(数据不可变)

Recoil 核心概念与Api

Recoil State


Tag解释

  • 可读写的RecoilState:具备get和set功能的State。由atom构建或由selector构建且声明get和set
  • 仅可读的RecoilState:只具备get功能的state。由selector构建且仅声明get
  • 会隐式订阅的Hook:在组件中使用该Hook时,隐式订阅对应的RecoilState,当state的值更新时,该组件会重渲染
  • 不会隐式订阅的Hook:在组件中使用该Hook时,不会隐式订阅对应的RecoilState,当state的值更新时,该组件不会重渲染

atom()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

在 Recoil 中用来定义 state数据。

使用时需要和其他hook函数相结合。

入参为一个对象

  • key: 用以识别atom。在整个项目中必须保证唯一性(也不能和selector重名)
  • default:数据的默认值
  • effects_UNSTABLE:可选参数。在有副作用的atom中使用,更多参考高级用例 Atom Effect
  • dangerouslyAllowMutability:可选参数。在Recoil中采用了immutable特性,对数据做了深冻结处理,因此如果定义的数据是一个对象,对象中的属性也无法进行修改。如果一定要修改数据,可以设置 dangerouslyAllowMutability: false

selector()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

const appleSelector = selector({
  key: 'appleSelector',
  get: ({get}) => get(appleState) + 1,
  set: ({set}, newValue) => set(appleState, newValue),
})

在Recoil中作为 derived state(派生状态),用来转换state数据。可以理解为将state数据传递给一个纯函数,这个纯函数会以某种方式修改state。

使用时需要与atom和其他hook函数相结合。

如果只声明了 get,则会成为一个 仅可读的RecoilState,如果声明了 getset ,则会成为一个 可读写的RecoilState

入参为一个对象:

  • key:用以识别selector。在整个项目中必须保证唯一性(也不能和atom重名)

  • get:参数为 ({get}) 的函数,需要返回一个值。

    • 函数参数:
    • get:用于从其他atom/selector中获取值。如果get了某个atom/selector,则这个被get的atom/selector会被隐式添加到当前selector的依赖项中。如果任何依赖项发生改变,则当前selector都会重新更新,更新数据会被缓存。
    • 支持异步操作,使用异步操作时需要结合React.Suspense 或者 Loadable ,更多见下文异步操作部分
  • set:可选参数,参数为 ({get, set, reset}, newValue) 的函数,不需要返回值。如果设置了此属性,则会成为可读写的RecoilState

    • 函数参数:

    • get:用于从其他atom/selector中获取值。与 get.get 参数不同,不会隐式添加依赖项

    • set:用于修改atom数据.

      • 当与 useResetRecoilState 结合使用时,第二个参数 newValue 为 默认值,可通过一下方式判断

      • import { DefaultValue } from 'recoil';
        
        set: ({set}, newValue) => {
         	if (newValue instanceof DefaultValue) {
            
          }
        }
        
    • reset:用于重制atom数据

  • dangerouslyAllowMutability:可选参数。效果同atom()的dangerouslyAllowMutability

useRecoilState()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

const [state, setState] = useRecoilState(appleState);

<div>apple number: {state}</div>
<div onClick={() => setState((oldState) => oldState + 1)}>add apple</div>

类似 useState ,返回值为一个元组

传入的参数为 可读写的RecoilState

会隐式订阅的Hook

useRecoilValue()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

const state = useRecoilValue(appleState);

<div>apple number: {state}</div>

只返回Recoil的值,不会返回修改函数

传入参数可为 可读写的RecoilState只可读的RecoilState

会隐式订阅的Hook

useSetRecoilState()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

const setState = useSetRecoilState(appleState);

<div onClick={() => setState((oldState) => oldState + 1)}>add apple</div>

只返回修改函数,不会返回Recoil的值

传入参数可为 可读写的RecoilState

不会隐式订阅的Hook

useResetRecoilState()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

const resetState = useResetRecoilState(appleState);

<div onClick={resetState}>reset apple number</div>

返回一个函数,执行后会将state值还原为默认值。

传入参数可为 可读写的RecoilState

不会隐式订阅的Hook

isRecoilValue()

官方文档:recoiljs.org/docs/api-re…

const appleState = atom({
  key: 'apple',
  default: 0,
});

isRecoilValue(appleState); // true
isRecoilValue(0) // false

用来判断入参是否为 RecoilState

异步操作

Recoil 还可以通过 Selector 来处理异步操作

import { atom, selector } from 'recoil';

const asyncAtom = atom({
    key: 'async',
    default: 0
});

function asyncProcess (val) {
    // 模拟接口请求
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(val + 1);
        }, 3000);
    })
}

const asyncSelector = selector({
    key: 'asyncSelector',
  	// 通过设置 async await 来截获异步数据
    get: async ({get}) => {
        return = await asyncProcess(get(asyncAtom));
    },
    set: ({set}, newValue) => {
        set(asyncAtom, newValue);
    }
})

对于异步 Selector 也可以使用 useRecoilStateuseRecoilValue 等操作 get 的hook,不过需要和 React.Suspense 一起使用(在获取到值之前不渲染)

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

function AsyncStateSuspense() {
    return (
        <React.Suspense fallback={<div>loading</div>}>
            <AsyncState />
        </React.Suspense>
    )
}

function AsyncState() {
    const asyncVal = useRecoilValue(asyncSelector);
    return (
        <div>{asyncVal}</div>
    )
}

function App() {
  return (
  	<AsyncStateSuspense />
  )
}

考虑到 React.Suspense 还是一个实验性功能,不太推荐在生产环境中使用,或者不想被 React.Suspense 阻塞渲染,可以使用 Loadable 来进行处理。

Loadable

官方文档:recoiljs.org/docs/api-re…

loadable 是一种状态,或者说一个结果值,用来表示当前 atom 或者 selector 的状态。在使用 useRecoilStateLoadableuseRecoilValueLoadable 时,得到的值为 Loadable

loadable主要包含2个值

  • state: 表示当前状态。有3种状态,值均为string
    • 'hasValue':表示指定 atom 或者 selector 有值。 在异步流程结束拿到值后,会变为该状态
    • 'hasError':表示指定 atom 或者 selector 发生了错误。在异步流程出错后,会变为该状态
    • 'loading':表示正在获取指定 atom 或者 selector 的值。在异步流程开始时为该状态
  • contents:Recoil State的值
    • 如果状态为hasValue,则为实际值
    • 如果状态为hasError,则为Error对象;
    • 如果状态为loading,则可以使用toPromise()获取Promise值的。

除了2个值,还有几个用以访问当前状态的辅助方法:

  • getValue(): 返回当前值,需要与 React.Suspense 结合使用。

    • 如果状态为hasValue,则返回值;
    • 如果状态为hasError,则抛出该错误;
    • 如果状态为loading,则保持loading状态,渲染React.Suspense的fallback内容,不渲染当前组件
  • toPromise(): 返回promise信息

  • valueMaybe(): 返回当前值

    • 如果状态为hasValue,返回值
    • 否则返回undefined
  • valueOrThrow(): 返回当前值

    • 如果状态为hasValue,返回值
    • 否则抛出该错误;
  • map((value: any) => any): 通过执行传入的函数以获取一个新的loadable 。入参函数第一个值为当前 loadablecontents ,map方法会执行该函数,返回一个新的 loadable,新 loadablecontents 为入参函数的返回值

    • 如果状态为loading,返回原loadable
    • 否则返回新的 loadable
    const [stateLoadable, setStateLoadable] = useRecoilStateLoadable(asyncSelector);
    
    const newLoadable = stateLoadable.map((contents) => {
      // 函数只会在 state !== 'loading' 时执行
      return 123123
    });
    

useRecoilStateLoadable()

官方文档:recoiljs.org/docs/api-re…

function useRecoilStateLoadable<T>(state: RecoilState<T>): [Loadable<T>, (T | (T => T)) => void]

类似 useRecoilState,区别在于返回值的第一个参数为 Loadable,并且不会抛出 error或者promise(React.Suspense 中需要),error 或者 promise 会保留在第一个返回值 Loadable

会隐式订阅的Hook

示例

// asyncSelector 见上例

function AsyncState() {
    const [stateLoadable, setStateLoadable] = useRecoilStateLoadable(asyncSelector);
    const { state, contents } = stateLoadable;

    switch (state) {
        case 'hasValue':
            return (
                <div onClick={() => setStateLoadable(contents)}>
                    {contents}
                </div>
            );
        case 'loading':
            return <div>Loading...</div>;
        case 'hasError':
            throw contents;
        default:
            return null;
    }
}

function App() {
  return (
  	<AsyncState />
  )
}

useRecoilValueLoadable()

官方文档:recoiljs.org/docs/api-re…

类似 useRecoilValue() ,只用在读取数据上

会隐式订阅的Hook

function useRecoilValueLoadable<T>(state: RecoilValue<T>): Loadable<T>

示例

// asyncSelector 见上例

function AsyncState() {
    const stateLoadable = useRecoilValueLoadable(asyncSelector);
    const { state, contents } = stateLoadable;

    switch (state) {
        case 'hasValue':
            return <div>{contents}</div>;
        case 'loading':
            return <div>Loading...</div>;
        case 'hasError':
            throw contents;
        default:
            return null;
    }
}

function App() {
  return (
  	<AsyncState />
  )
}

snapshot

useRecoilCallback()