异步
使用异步 atom,您可以访问真实世界的数据,同时仍然直接从您的 atom 管理它们,而且非常轻松。
我们可以将它们分为两大类:
- 异步读取原子:一旦您尝试获取其值,就会立即启动异步请求。 您可以将他们称为“聪明的 getters”。
- 异步写原子:异步请求在特定时刻启动。 您可以将它们称为“动作”。
异步读原子
原子的 read 函数可以返回一个 promise。
const countAtom = atom(1);const asyncAtom = atom(async (get) => get(countAtom) * 2);
Jotai 本质上是利用 Suspense
来处理异步流。
const ComponentUsingAsyncAtoms = () => {const [num] = useAtom(asyncAtom)// 这里的 `num` 总是 `number` 即使 asyncAtom 返回一个 Promise}const App = () => {return (<Suspense fallback={/* What to show while suspended */}><ComponentUsingAsyncAtoms /></Suspense>)}
或者,您可以通过使用 loadable
API 包装原子来避免 Jotai 为您执行的 Suspense。
注意:在 Jotai v1 API 中,不仅如果原子读取函数是异步的,而且如果它的一个或多个依赖项是异步的,原子也会变成异步的。
const anotherAtom = atom((get) => get(asyncAtom) / 2);// 即使这个原子不返回 promise,// 这是一个读取异步原子,因为 `asyncAtom` 是异步的。
(在 Jotai v2 API 中,这不成立;依赖包括异步原子的原子将是异步的。)
注意:在 Jotai v1 API 中,你无法在 write-atom 中获取 async atom 的值(无论 write 函数是否是异步的)在它的值被 resolve 之前
const asyncAtom = atom(async (get) => ...)const writeAtom = atom(null, (get, set, payload) => {get(asyncAtom) // 如果“asyncAtom”仍处于 pendding 状态,则会引发错误})
(在 Jotai v2 API 中,这不成立;您可以在写原子中读取异步原子,它会返回一个承诺。)
在 Jotai v1 API 中,如果你想确保操作永远不会失败,你可以直接在应用程序的根级别预加载原子:
const Preloader = () => {useAtomValue(asyncAtom) // The value will be pre-loadedreturn null}const Root = () => {return (<Suspense fallback={<Text>Loading...<Text>}><Preloader /> {/* 等待原子预加载 */}<App /> {/* 你的应用程序的其余部分 */}</Suspense>)}
异步写原子
异步写原子是另一种异步原子。 当 atom 的 write
函数返回一个 promise 时。
const countAtom = atom(1);const asyncIncrementAtom = atom(null, async (get, set) => {// await somethingset(countAtom, get(countAtom) + 1);});const Component = () => {const [, increment] = useAtom(asyncIncrementAtom);const handleClick = () => {increment();};// ...};
有时异步
Jotai 可以实现的一个有趣的模式是从异步切换到同步以在需要时触发挂起。
const request = async () => fetch("https://...").then((res) => res.json());const baseAtom = atom(0);const Component = () => {const [value, setValue] = useAtom(baseAtom);const handleClick = () => {setValue(request()); // 将挂起直到请求 resove};// ...};
在 TypeScript 中的使用
在 TypeScript 中,atom(0)
被推断为 PrimitiveAtom<number>
。 它不能接受Promise<number>
作为值,因此前面的代码不会进行类型检查。 为此,您需要显式键入原子并添加Promise<number>
作为接受值。
const baseAtom = atom<number | Promise<number>>(0); // 将接受同步和异步值
永远异步
有时您可能想要暂停直到一个未预先确定的时刻(或从不)。
const baseAtom = atom(new Promise(() => {})); // 将暂停,直到另行设置
Suspense
异步支持在 Jotai 中是一流的。 它的核心充分利用了 React Suspense。
从技术上讲,React 17 中仍然 不支持/未记录 React.lazy 以外的 Suspense 用法。如果这是阻塞的,那么您仍然可以使用
loadable
API 来避免 Suspense
要使用异步原子,您需要使用 <Suspense>
包装您的组件树。
如果你有一个
<Provider>
,请在<Provider>
中放置 至少一个<Suspense>
; 否则,在渲染组件时可能会导致死循环。
const App = () => (<Provider><Suspense fallback="Loading..."><Layout /></Suspense></Provider>);
在组件树中有更多的 <Suspense>
也是可能的,并且必须考虑最多从 Jotai 固有的处理中获益。
v1.3.9 之前的异步写入原子行为
(从 v1.4.0 开始不再是这种情况。)
触发写入原子的 Suspense
回退
本节仅适用于“异步写入原子”而不适用于“异步读取原子”,它与 Suspense
的工作方式不同。
如果出现以下情况,写入原子将触发 Suspense
fallback:
* 原子的写参数(第二个)是异步的* 等待的调用是直接进行的,而不是从另一个包含函数内部进行的
这 将 触发Suspense
fallback:
const writeAtom = atom(null, async (get, set) => {const response = await new Promise<string>((resolve, _reject) => {setTimeout(() => {resolve("some returned value");}, 2000);});set(somePrimitiveAtom, "The returned value is: " + response);});
这 不会 触发 Suspense
fallback:
const writeAtom = atom(null, (get, set) => {const getResponse = async () => {const response = await new Promise<string>((resolve, _reject) => {setTimeout(() => {resolve("some returned value");}, 2000);});set(somePrimitiveAtom, "The returned value is: " + response);};getResponse();});
但是上面的 两个 仍然会将 somePrimitiveAtom
设置为正确的值。