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();
},
});
selectorFamily
有的时候,我们想基于一些参数来进行查询。可以使用 selectorFamily
const userNameQuery = selectorFamily({
key: 'UserName',
get: userID => async () => {
const response = await myDBQuery({userID});
if (response.error) {
throw response.error;
}
return response.name;
},
});
function UserInfo({userID}) {
const userName = useRecoilValue(userNameQuery(userID));
return <div>{userName}</div>;
}
function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<UserInfo userID={1}/>
<UserInfo userID={2}/>
<UserInfo userID={3}/>
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}
核心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;
},
});
文章评论