React状态管理:Recoil指南
RecoilRoot
提供上下文。此组件必须是所有使用Recoil hook
组件的根组件
initializeState
:可使用MutableSnapshot
初始化atom
状态,可选函数(({set,setUnvalidatedAtomValues}) => void
)override
:是否重新创建新的Recoil
作用域,默认为true
。一般用于多个<RecoilRoot>
嵌套,当为true
时会重新创建一个Recoil
作用域,新的作用域会覆盖上级<RecoilRoot>
中同名。为false
则只渲染子组件,没有其他作用
通常情况下,atom
或selector
需要配合以下hook
使用: useRecoilState(读写)
,useRecoilValue(只读)
,useSetRecoilState(只写)
,useResetRecoilState(重置)
,现在只需要了解,这些hook
后续会讲解
atom()
一个atom
表示Recoil
的state
,接收一个对象,返回一个可写的RecoilState
对象.接收对象属性如下:
key
:标识当前atom
的唯一key
值,请记住:key
需要保持全局唯一default
:默认值,可是任意类型的值,也可以是RecoilState(atom或selector)
,也可以是一个异步函数dangerouslyAllowMutability
:设置允许atom
中的对象可变,对象变化不代表status
的变化
Recoil
管理atom
的state
的变化,并通知订阅该atom
的组件何时渲染
import React from 'react';
import { atom, RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
const countState = atom({
key: 'countState',
default: 0
});
const Count = () => {
const count = useRecoilValue(countState);
return <span>{count}</span>;
};
const CountButton = (props: { type: string }) => {
const [count, setCount] = useRecoilState(countState);
return (
<button onClick={() => setCount(count + (props.type === 'add' ? 1 : -1))}>
{props.type === 'add' ? '+' : '-'}
</button>
);
};
const App = () => {
return (
<RecoilRoot>
<CountButton type="dec" />
<Count />
<CountButton type="add" />
</RecoilRoot>
);
};
export default App;
如果需要在不订阅组件的情况下读取atom
的值,可使用useRecoilCallback
.如果需要基于上一个值更新的话,可以向set
函数传入一个函数,函数的参数就是最新的值,返回一个需要设置的值.
当default
传入是一个函数时,则在使用hook
返回的值也是一个函数.
import React from 'react';
import { atom, RecoilRoot, useRecoilValue } from 'recoil';
const msgState = atom({
key: 'msg',
default: () => 'Hello'
});
const Msg = () => {
const msg = useRecoilValue(msgState);
return <div>{msg()}</div>;
};
const App = () => {
return (
<RecoilRoot>
<Msg />
</RecoilRoot>
);
};
export default App;
切记default
不能传入一个异步函数,如果需要异步获取数据,可以使用selector
包装一下,但一般如果需要异步请求的话,建议放在selector
中
import React, { Suspense } from 'react';
import { atom, RecoilRoot, selector, useRecoilValue } from 'recoil';
const msgState = atom({
key: 'msg',
default: selector({
key: 'msgSelector',
get: async () => {
const data = await new Promise((resolve) => {
setTimeout(() => resolve('Hello'), 2000);
});
return data;
}
})
});
const Msg = () => {
const msg = useRecoilValue(msgState);
return <div>{msg}</div>;
};
const App = () => {
return (
<RecoilRoot>
<Suspense fallback={<div>loading...</div>}>
<Msg />
</Suspense>
</RecoilRoot>
);
};
export default App;
selector()
selector
是一个纯函数,是派生状态,可基于atom
或selector
返回处理过的状态,类似Vue
中的compoted
。接收一个对象,对象属性如下:
get
:一个处理state
的函数,返回处理过的值,也可返回一个异步的Promise
或相同类型的atom
或selector
。函数的参数是一个对象:get
:用来获取其他的atom
或selector
值的函数,所有传入此函数的atom
或selector
会被隐式的添加到此selector
的依赖列表中,当依赖发生改变时,则会重新计算getCallback
:用于创建Recoil-aware
回调的函数
set
:若设置了该属性,则selector
会返回一个可写的state
,当改变selector
则会调用此函数。此函数接收两个参数,第一个参数是一个对象,第二个参数是一个新值,第一个参数对象属性如下:get
:用来获取其他的atom
或selector
值的函数set
:用来设置Recoil
状态的函数,第一个参数是Recoil
的state
,第二个参数是新的值,新值可以是一个更新函数或一个DefaultVaue
类型的对象
dangerouslyAllowMutability
:设置允许atom
中的对象可变,对象变化不代表status
的变化
import React from 'react';
import { atom, RecoilRoot, selector, useRecoilState, useRecoilValue } from 'recoil';
const firstNameState = atom({
key: 'firstName',
default: 'Code'
});
const lastNameState = atom({
key: 'lastName',
default: 'Book'
});
const fullNameSelector = selector<string>({
key: 'fullName',
get: ({ get }) => `${get(firstNameState)}-${get(lastNameState) ?? ''}`,
set: ({ set }, value) => {
const [firstName, lastName] = value.split('-');
set(firstNameState, firstName);
set(lastNameState, lastName);
}
});
const UpperFullNameSelector = selector<string>({
key: 'upperFullName',
get: ({ get }) => get(fullNameSelector).toUpperCase()
});
const Name = () => {
const [firstName, setFirstName] = useRecoilState(firstNameState);
const [lastName, setLastName] = useRecoilState(lastNameState);
const [fullName, setFullName] = useRecoilState(fullNameSelector);
const upperFullName = useRecoilValue(UpperFullNameSelector);
return (
<>
<div>
<span>firstName:</span>
<input type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} />
</div>
<div>
<span>lastName:</span>
<input type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} />
</div>
<div>
<span>fullName:</span>
<input type="text" value={fullName} onChange={(e) => setFullName(e.target.value)} />
</div>
<div>{upperFullName}</div>
</>
);
};
const App = () => {
return (
<RecoilRoot>
<Name />
</RecoilRoot>
);
};
export default App;
selector
还支持异步,由于React
的渲染函数是同步的,所以需要Suspense
边界包裹组件,需要捕获错误,可用ErrorBoundary
包裹,如果需要查询时传入参数,可使用selectorFamily
import React, { Suspense } from 'react';
import { atom, RecoilRoot, selector, selectorFamily, useRecoilValue } from 'recoil';
// const userIdState = atom({
// key:'userId',
// default: 1
// })
// const userInfoSelector = selector({
// key:'userInfo',
// get: async ({get}) => {
// const data = await new Promise(resolve => {
// setTimeout(()=> resolve({id:get(userIdState),name:'用户001'}),2000)
// })
// return data
// }
// })
const userInfoSelector = selectorFamily({
key: 'userInfo',
get: (id) => async () => {
const data = await new Promise((resolve) => {
setTimeout(() => resolve({ id, name: '用户001' }), 2000);
});
return data;
}
});
const AsyncSelector = () => {
const userInfo = useRecoilValue(userInfoSelector(1));
return (
<>
<div>用户id:{userInfo.id}</div>
<div>用户名:{userInfo.name}</div>
</>
);
};
const App = () => {
return (
<RecoilRoot>
<Suspense fallback={<div>loading...</div>}>
<AsyncSelector />
</Suspense>
</RecoilRoot>
);
};
export default App;
如果需要并行请求数据可以使用waitForAll
一次并行请求多条接口,也可以通过waiForOne
实现增量请求更新。
一般数据会在渲染之前请求获取,在Recoil
中可以使用useRecoilCallback
useRecoilState()
对
Recoil state
读写,返回一个数组,数组第一个元素是state
当前值,第二个元素是设置state
值的函数。
useRecoilValue()
读取
Recoil state
的值,返回state
的当前值。
useSetRecoilState()
设置
Recoil state
useResetRecoilState()
重置
Recoil state
useRecoilStateLoadable()
读写异步
selector
的值。返回一个数组,数组第一个元素是Loadable
,第二个元素是设置的函数。使用此hooks
可以不使用Suspense
来使用异步selector
Loadable
属性:
state
:selector
所处状态hasValue
:成功loading
:请求中hasError
:失败contents
:当前表示的值,hasValue
状态时表示实际值,loading
时表示Promise
,hasError
时表示Error
对象
useRecoilValueLoadable()
仅获取异步
selector
的值。返回的是一个Loadable
,同useRecoilStateLoadable
import React, { Suspense } from 'react';
import { RecoilRoot, selectorFamily, useRecoilValue, useRecoilValueLoadable } from 'recoil';
const userInfoSelector = selectorFamily({
key: 'userInfo',
get: (id) => async () => {
const data = await new Promise((resolve) => {
setTimeout(() => resolve({ id, name: '用户001' }), 2000);
});
return data;
}
});
const AsyncSelector = () => {
const userInfo = useRecoilValueLoadable(userInfoSelector(1));
switch (userInfo.state) {
case 'hasValue':
return (
<>
<div>用户id:{userInfo.contents.id}</div>
<div>用户名:{userInfo.contents.name}</div>
</>
);
break;
case 'loading':
return <div>loading...</div>;
case 'hasError':
return <div>error</div>;
}
};
const App = () => {
return (
<RecoilRoot>
<AsyncSelector />
</RecoilRoot>
);
};
export default App;
isRecoilValue()
是否为
atom
或selector
atomFamily()
返回一个返回可写的RecoilState atom
函数,用于表示一个atom
的集合。当你需要给atom
传参数时,可以使用此函数。
import React from 'react';
import { atomFamily, RecoilRoot, useRecoilValue } from 'recoil';
const msgState = atomFamily({
key: 'msg',
default: (msg: string) => msg.toUpperCase()
});
const Msg = () => {
const msg = useRecoilValue(msgState('hello'));
return <div>{msg}</div>;
};
const App = () => {
return (
<RecoilRoot>
<Msg />
</RecoilRoot>
);
};
export default App;
selectorFamily()
返回一个函数,该函数返回一个只读的RecoilValueReadOnly
或者可写的RecoilState selector
。当你需要给selector
传参数时,可以使用此函数
constSelector()
一个永远提供常量值的selector
。(不太清楚有什么用)
errorSelector()
一个总是抛出已有错误的selector
。(不太清楚有什么用)
noWait()
select helper
方法,返回值代表所提供的atom
或selector
当前状态的Loadable
,与useRecoilValueLoadable()
类似,但noWait
是一个selector
不是钩子函数
import React from 'react';
import { noWait, RecoilRoot, selector, useRecoilValue } from 'recoil';
const msgSelector = selector({
key: 'msgSelector',
get: async () => {
const data = await new Promise((resolve) => {
setTimeout(() => resolve('Hello'), 2000);
});
return data;
}
});
const Msg = () => {
const msgLoadable = useRecoilValue(noWait(msgSelector));
return {
hasValue: <div>{msgLoadable.contents}</div>,
loading: <div>loading...</div>,
hasError: <div>erro:{msgLoadable.contents}</div>
}[msgLoadable.state];
};
const App = () => {
return (
<RecoilRoot>
<Msg />
</RecoilRoot>
);
};
export default App;
waitFroAll()
并发计算多个异步依赖项的并发helper
方法,即:可用于并发请求数据。接收一个数组或者对象。
import React from 'react';
import { RecoilRoot, selector, useRecoilValueLoadable, waitForAll } from 'recoil';
const userSelector = selector({
key: 'userSelector',
get: async () => {
console.log('user start');
const data = await new Promise((resolve) =>
setTimeout(() => resolve({ id: 1, name: 'codebook' }), 2000)
);
console.log('user end');
return data;
}
});
const listSelector = selector({
key: 'listSelector',
get: async () => {
console.log('list start');
const data = await new Promise((resolve) => setTimeout(() => resolve([1, 2, 3, 4]), 2500));
console.log('list end');
return data;
}
});
const Msg = () => {
const { state, contents } = useRecoilValueLoadable(waitForAll([userSelector, listSelector]));
switch (state) {
case 'hasValue':
const [user, list] = contents;
return (
<>
<div>用户名:{user.name}</div>
<div>列表数据:</div>
{list.map((item) => (
<div key={item}>{item}</div>
))}
</>
);
case 'loading':
return <div>loading...</div>;
case 'hasError':
return <div>error: {contents}</div>;
}
};
const App = () => {
return (
<RecoilRoot>
<Msg />
</RecoilRoot>
);
};
export default App;
waitForAllSettled
并发计算多个异步依赖项的并发helper
方法,即:可用于并发请求数据。接收一个数组或者对象。一直等待,直到有一个成功返回。
import React from 'react';
import { RecoilRoot, selector, useRecoilValueLoadable, waitForAllSettled } from 'recoil';
const userSelector = selector({
key: 'userSelector',
get: async () => {
console.log('user start');
const data = await new Promise((resolve) =>
setTimeout(() => resolve({ id: 1, name: 'codebook' }), 2000)
);
console.log('user end');
return data;
}
});
const listSelector = selector({
key: 'listSelector',
get: async () => {
try {
console.log('list start');
const data = await new Promise((resolve, reject) => setTimeout(reject, 3000));
return data;
} finally {
console.log('list end');
}
}
});
const Msg = () => {
const { state, contents } = useRecoilValueLoadable(
waitForAllSettled([userSelector, listSelector])
);
switch (state) {
case 'hasValue':
const [{ contents: user }, { contents: list }] = contents; // => user: {id:1,name:'codebook} list: undefined
return (
<>
<div>用户名:{user.name}</div>
<div>列表数据:</div>
{list?.map((item) => (
<div key={item}>{item}</div>
))}
</>
);
case 'loading':
return <div>loading...</div>;
case 'hasError':
return <div>error: {contents}</div>;
}
};
const App = () => {
return (
<RecoilRoot>
<Msg />
</RecoilRoot>
);
};
export default App;
waitForNone()
并发计算多个异步依赖项的并发helper
方法,即:可用于并发请求数据。接收一个数组或者对象。与waitForAll
类似,区别在于waitForNone
会立即为每个依赖返回一个Loadable
,而不是直接返回值
waitForAny()
并发计算多个异步依赖项的并发helper
方法,即:可用于并发请求数据。接收一个数组或者对象。一直等待,直到有一个成功返回
useRecoilCallback
类似useCallback
,这个回调可以访问Recoil
状态的只读Snapshot
,并且能够异步更新当前的Recoil
状态。使用场景:
- 异步读取
Recoil
状态,而无需订阅React
组件在atom
或selector
更新时重新渲染 - 把昂贵的查询延迟到一个你不想在渲染时执行的异步操作
- 在你想读取 或写入
Recoil
状态的地方执行副作用 - 动态更新一个
atom
或selector
,可能并不知道在渲染时要更新哪个atom
或selector
,所以不能使用useRecoilState()
Pre-fetching
渲染前的数据
接收参数和useCallback
一样,包括传入空数组只在初始化后执行一次,传入依赖项,当依赖项发生变化,重新执行。
回调函数的参数:
snapshot
:Snapshot
提供一个只读的Recoil atom
状态gotoSnapshot
:更新全局状态以匹配提供的Snapshot
set
:设置atom
或selector
的值reset
:重置atom
或selector
的值