很早以前学习 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()
const appleState = atom({
key: 'apple',
default: 0,
});
在 Recoil 中用来定义 state数据。
使用时需要和其他hook函数相结合。
入参为一个对象
key
: 用以识别atom。在整个项目中必须保证唯一性(也不能和selector重名)default
:数据的默认值effects_UNSTABLE
:可选参数。在有副作用的atom中使用,更多参考高级用例 Atom EffectdangerouslyAllowMutability
:可选参数。在Recoil中采用了immutable特性,对数据做了深冻结处理,因此如果定义的数据是一个对象,对象中的属性也无法进行修改。如果一定要修改数据,可以设置dangerouslyAllowMutability: false
selector()
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
,如果声明了 get
和set
,则会成为一个 可读写的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()
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()
const appleState = atom({
key: 'apple',
default: 0,
});
const state = useRecoilValue(appleState);
<div>apple number: {state}</div>
只返回Recoil的值,不会返回修改函数
传入参数可为 可读写的RecoilState
或 只可读的RecoilState
会隐式订阅的Hook
useSetRecoilState()
const appleState = atom({
key: 'apple',
default: 0,
});
const setState = useSetRecoilState(appleState);
<div onClick={() => setState((oldState) => oldState + 1)}>add apple</div>
只返回修改函数,不会返回Recoil的值
传入参数可为 可读写的RecoilState
不会隐式订阅的Hook
useResetRecoilState()
const appleState = atom({
key: 'apple',
default: 0,
});
const resetState = useResetRecoilState(appleState);
<div onClick={resetState}>reset apple number</div>
返回一个函数,执行后会将state值还原为默认值。
传入参数可为 可读写的RecoilState
不会隐式订阅的Hook
isRecoilValue()
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
也可以使用 useRecoilState
、useRecoilValue
等操作 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
loadable
是一种状态,或者说一个结果值,用来表示当前 atom
或者 selector
的状态。在使用 useRecoilStateLoadable
和 useRecoilValueLoadable
时,得到的值为 Loadable
loadable
主要包含2个值
- state: 表示当前状态。有3种状态,值均为string
- 'hasValue':表示指定
atom
或者selector
有值。 在异步流程结束拿到值后,会变为该状态 - 'hasError':表示指定
atom
或者selector
发生了错误。在异步流程出错后,会变为该状态 - 'loading':表示正在获取指定
atom
或者selector
的值。在异步流程开始时为该状态
- 'hasValue':表示指定
- 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
。入参函数第一个值为当前loadable
的contents
,map方法会执行该函数,返回一个新的loadable
,新loadable
的contents
为入参函数的返回值- 如果状态为
loading
,返回原loadable
- 否则返回新的
loadable
const [stateLoadable, setStateLoadable] = useRecoilStateLoadable(asyncSelector); const newLoadable = stateLoadable.map((contents) => { // 函数只会在 state !== 'loading' 时执行 return 123123 });
- 如果状态为
useRecoilStateLoadable()
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()
类似 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 />
)
}