如果你一点都不清楚Recoil,建议先查看一下官方文档,本文主要是讲解一下常用api的使用。
Recoil作为facebook官方提出的状态管理,与react结合度还是很不错。
核心概念
使用Recoil,您可以创建一个数据流图,数据从atoms(共享状态)通过selectors(纯函数)一直注入到React组件。atoms是组件可以预订的状态单位。selectors可以同步或异步转换此状态。
atoms
atoms是状态单位,它们是可更新和可订阅的,当atoms被更新时,每个订阅的组件都将使用新值重新呈现(重新render)。
它们也可以在运行时创建,可以使用atoms来代替React本地组件状态。如果多个组件使用相同的原子,则所有这些组件共享其状态。
// 使用方式
const listState = atom({
key:'list',
default:[]
})
原子需要一个唯一的key,++该key可用于调试,持久性以及某些高级API,这些API可让您查看所有atoms的图++。确保key在全局上是唯一的,像React组件状态一样,它们也具有默认值
在组件中读取 atom 使用 useRecoilValue
可以读取。 读取和写入使用 useRecoilState
。
// 使用方式和hooks相同
function App(){
// 获取atom
const list = useRecoilValue(listState);
// 获取和设置
const [list,setList] = useRecoilState(listState)
}
同时,default是可以固定值、异步的Promise、或者另一个atom
function atom<T>({
key: string,
default: T | Promise<T> | RecoilValue<T>,
dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
// Promise 的写法
const listState = atom({
key: 'list',
default: (async () => {
const res = await fetch('http://api.manster.me/getCampaignData').then(res => res.json());
return res;
})(),
});
// 引用其他atom
const aState = atom({
key:'a',
default:'aaa'
})
const bState = atom({
key:'b',
default:aState
})
通常,你需要使用以下 hook 来与 atom 搭配使用。
useRecoilState()
:当你同时需要对 atom 进行读写时,使用此 hook。使用此 hook 会使组件订阅 atom。useRecoilValue()
:当你仅需要读取 atom 时,使用此 hook。使用此 hook 会使组件订阅 atom。useSetRecoilState()
:当你仅需要写入 atom 时,使用此 hook。useResetRecoilState()
:需将 atom 重置为默认值时,使用此 hook。
selector
官方定义:selector 是个function或者 代表Recoil中的派生状态。 您可以将它们视为“纯函数”,而没有副作用,对于给定的一组依赖值,该副作用始终返回相同的值。 如果仅提供get函数,则selector为只读,并返回RecoilValueReadOnly对象。 如果还提供了一个set,它将返回一个可写的RecoilState对象
function selector<T>({
key: string, // 唯一key
get: ({
get: GetRecoilValue // 从其他atom或selector获取值,所有传入该函数的 atom 或 selector 将会隐式地被添加到此 selector 的一个依赖列表中。如果这个 selector 的任何一个依赖发生改变,这个 selector 就会重新计算值。
}) => T | Promise<T> | RecoilValue<T>,
set?: (
{
get: GetRecoilValue, // 一个用来从其他 atom 或 selector 获取值的函数。该函数【不会】为 selector 订阅给定的 atom 或 selector。
set: SetRecoilState, // 一个用来设置 Recoil 状态的函数。第一个参数是 Recoil 的 state,第二个参数是新的值。新值可以是一个更新函数,或一个 DefaultValue 类型的对象,用以传递更新操作
reset: ResetRecoilState,
},
newValue: T | DefaultValue,
) => void,
dangerouslyAllowMutability?: boolean,
})
具体使用方法
const aState = atom({
key: 'a',
default: 'a',
});
const bState = atom({
key: 'b',
default: 'b',
});
const gsSelector = selector({
key: 'getandset',
get: ({ get }) => {
let a = get(aState);
let b = get(bState);
return a + b;
},
set: ({ get, set }, newValue) => {
// 这个get不会做依赖收集
const c = get(cState);
set(bState, newValue);
},
});
function Test() {
const [gs, setgs] = useRecoilState(gsSelector); // 写法类似于atom,可以直接写入;
const add = ()=>{
setgs('+1')
}
console.log('gs', gs);
return <div>
<Button onClick={add}>add</Button>
</div>;
}
异步可以这么写
const myQuery = selector({
key: 'MyDBQuery',
get: async () => {
const response = await fetch(getMyRequestUrl());
return response.json();
},
});
核心api
RecoilRoot
用法:
<RecoilRoot>
<App/>
</RecoilRoot>
Recoil的上下文,可以有多个。atom
和selector
中的key是在RecoilRoot
的全局性唯一的。多个嵌套的RecoilRoot
内层的会覆盖外层的。
atom 、selector
用法:上面都介绍了
atom
即 Recoil state。atom的初始值在 default 参数设置,值可以是 确定值、Promise、其他 atom、 selector。
selector
是纯函数,用于计算 Recoil state 的派生数据并做缓存。selector
是 Recoil derived state (派生状态),它不仅仅可以从 atom衍生数据,也可以从其他 selector 衍生数据。 selector 的属性 get 的回调函数 get 的参数*(参数为 atom或者selector)*会成为 selector 的依赖 ,当依赖改变的时候,selector 就会重新计算得到新的衍生数据并更新缓存。如果依赖不变就用缓存值。selector 的返回值由 get 方法返回,可以返回计算出的确定的值,也可以是 Promise、其他 selector、atom, 如果 atom和selector的值是 Promise ,那么表示状态值是异步获取的,Recoil 提供了专门的 api 用于获取异步状态,并支持和 React suspense
结合使用显示异步状态
设置和获取 Recoil state 的 hooks API :
读 Recoil state
useRecoilValue
读取同步状态useRecoilValueLoadable
读取异步状态,读取的状态分为hasValue、loading、hasError三个状态,并且返回的是一个loadable对象,具有以下字段
state
:表示 selector 的状态。可选的值有 'hasValue'
,'hasError'
,'loading'
。
contents
:此值代表 Loadable 的结果。如果状态为 hasValue,则值为实际结果;如果状态为 hasError,则会抛出一个错误对象;如果状态为 loading,则值为 Promise。
用法:
// 读取同步状态
const alist = useRecoilValue(listState)
// 读取异步状态
const listState = atom({
key: 'list',
default: (async () => {
const res = await fetch('http://api.manster.me/getCampaignData').then(res => res.json());
return res;
})(),
});
const listLoadable = useRecoilValueLoadable(listState);
switch (listLoadable.state) {
case 'hasValue':
return <div>...something...</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw listLoadable.msg;
}
// 如果不使用 React Suspense 可以使用 useRecoilValueLoadable 来替代。
写 Recoil state
useSetRecoilState
写状态,返回一个 setter 函数,用来更新可写 Recoil state 的值,返回一个可以用来异步改变 state 的 setter 函数。可以传给此 setter 函数一个新的值,也可以传入一个更新函数,此函数接受上一次的值作为其参数。
用法
const setA = useSetRecoilState(aState);
setA('newValue')
setA((preState)=>{
return preState+'new'
});
useResetRecoilState
重置状态,重置为default
用法:
// 一个操作性的hook
useResetRecoilState(todoListState)
读和写 Recoil state
useRecoilState
读写state
用法:
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelcius);
setTempC('newValue');
setTempF((preValue)=>{
return newValue
});
useRecoilStateLoadable
异步读写state
用法:
// 读取
const [userNameLoadable, setUserName] = useRecoilStateLoadable(userNameState);
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
setUserName('newValue')
setUserName((pre)=>{
return 'newValue'
})
useRecoilCallback
读写state但是不更新组件
用法:
// 可以用于预加载数据,但是不刷新组件,
// 出于性能考虑,您可能希望在呈现之前获取数据,这样查询就可以在我们开始渲染的时候进行。React文档给出了一些例子,这种模式也适用于Recoil。
我们改变上面的例子,当用户点击改变用户的按钮时,就开始获取下一个用户信息:
function CurrentUserInfo() {
const currentUser = useRecoilValue(currentUserInfoQuery);
const friends = useRecoilValue(friendsInfoQuery);
const changeUser = useRecoilCallback(({snapshot, set}) => userID => {
snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
set(currentUserIDState, userID); // change current user to start new render
});
return (
<div>
<h1>{currentUser.name}</h1>
<ul>
{friends.map(friend =>
<li key={friend.id} onClick={() => changeUser(friend.id)}>
{friend.name}
</li>
)}
</ul>
</div>
);
}
其中 useRecoilState
= useRecoilValue
+ useSetRecoilState
,后两个就是获取 Recoil state 的只读状态值和设置状态值。
useResetRecoilState
用于重置 Recoil state 为初始状态,也就是 default 值。
useRecoilStateLoadable
用于获取异步状态和设置状态,useRecoilValueLoadable
只读异步状态
useRecoilCallback
:上面的几个 api 都会让组件去订阅 state,当 state 改变组件就会更新,但是使用 useRecoilCallback
可以不用更新组件。 useRecoilCallback
允许组件不订阅 Recoil state 的情况下获取和设置 Recoil state,也就说组件可以获取 state 但是不用更新,反过来组件不必更新自身才能获取新 state,将读取 state 延迟到我们需要的时候而不是在组件 monted 的时候。
工具类API
atomFamily
和 atom 一样,但是可以支持传参数
const myAtomFamily = atomFamily({
key: ‘MyAtom’,
default: param => defaultBasedOnParam(param),
});
selectorFamily
和 selector 的写法一样,但是支持我们传递参数
const myMultipliedState = selectorFamily({
key: 'MyMultipliedNumber',
get: (multiplier) => ({get}) => {
return get(myNumberState) * multiplier;
},
// optional set
set: (multiplier) => ({set}, newValue) => {
set(myNumberState, newValue / multiplier);
},
});
waitForAll
并发api,参数会被依赖收集。所有异步都返回才执行。直接返回结果,没有中间态。类似于Promise.all
用法:会全部返回之后再 return 相当于 自动async(配合Suspense)
// 在 selector 中
const fetchAll = selector({
key: 'fetchAll',
get: ({ get }) => {
const list = get(
waitForAll([confirmListSelector, offlineListSelector, ingListSelector, endListSelector]),
);
return list;
},
});
waitForAny
并发api,有一个异步返回可用值,就直接返回,后续的也许返回。会被执行多次。返回一个 【状态管理器】
// 第一次返回
[ {state:'hasValue'}, {state:'loading'}]
// 第二次返回
[ {state:'hasValue'}, {state:'hasValue'}]
waitForNone
每一个依赖项的状态都会返回,所以会执行多次。各种loading 和 hasValue
以上三个wait 使用方法都是一样。
noWait
selector 的 api,功能和 useRecoilValueLoadable 一样,只不是useRecoilValueLoadable是一个hooks。moWait是一个selector的helper。
功能就是获取异步依赖项的当前状态 loading 或者 hasValue。
const noWaitSelector = selector({
key: 'noWaitSelector',
get: ({ get }) => {
const result = get(noWait(p1Selector));
return result;
},
});