跳转到内容

Async

使用异步原子,你可以轻松地在原子中直接访问和管理真实世界的数据。

我们可以将它们分为两大类:

  • 异步读取原子:当你尝试获取其值时,异步请求会立即发起。你可以把它们理解为”智能 getter”。
  • 异步写入原子:异步请求在特定时刻发起。你可以把它们理解为”action”。

原子的 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={/* 挂起时显示的内容 */}>
<ComponentUsingAsyncAtoms />
</Suspense>
)
}

你也可以使用 loadable API 包装原子,从而避免 Jotai 默认的 Suspense 行为。

如果另一个原子使用了异步原子,它会返回一个 promise。因此,我们也需要将该原子设为异步的。

const anotherAtom = atom(async (get) => (await get(asyncAtom)) / 2)

这同样适用于带有写入函数的原子。

const asyncAtom = atom(async (get) => ...)
const writeAtom = atom(null, async (get, set, payload) => {
await get(asyncAtom)
// ...
})

异步写入原子是另一种异步原子。当原子的 write 函数返回一个 promise 时,它就是异步写入原子。

const countAtom = atom(1)
const asyncIncrementAtom = atom(null, async (get, set) => {
// await something
set(countAtom, get(countAtom) + 1)
})
const Component = () => {
const [, increment] = useAtom(asyncIncrementAtom)
const handleClick = () => {
increment()
}
// ...
}

Jotai 有一个有趣的模式:可以在异步和同步之间切换,从而在需要时触发 Suspense。

const request = async () =>
fetch('https://jsonplaceholder.typicode.com/todos/1').then((res) =>
res.json(),
)
const baseAtom = atom(0)
const Component = () => {
const [value, setValue] = useAtom(baseAtom)
const handleClick = () => {
setValue(request()) // 将挂起直到请求完成
}
// ...
}

在 TypeScript 中,atom(0) 会被推断为 PrimitiveAtom<number>,它不能接受 Promise<number> 作为值,因此上面的代码无法通过类型检查。为了解决这个问题,你需要显式指定原子的类型,并将 Promise<number> 加入可接受的值类型中。

const baseAtom = atom<number | Promise<number>>(0) // 同时接受同步和异步值

有时你可能想要一直挂起,直到某个不确定的时刻(或永远挂起)。

const baseAtom = atom(new Promise(() => {})) // 将一直挂起,直到被 set 赋值

异步支持是 Jotai 的一等特性。它在核心层面完全利用了 React Suspense。

严格来说,在 React 17 中,除了 React.lazy 之外的 Suspense 用法仍然是不受支持/未文档化的。如果这构成阻碍,你仍然可以使用 loadable API 来避免挂起。

要使用异步原子,你需要用 <Suspense> 包裹组件树。

如果使用了 <Provider>,请确保在该 <Provider> 内部放置至少一个 <Suspense>;否则可能导致渲染时的无限循环。

const App = () => (
<Provider>
<Suspense fallback="Loading...">
<Layout />
</Suspense>
</Provider>
)

在组件树中放置更多的 <Suspense> 也是可以的,而且推荐这样做,以充分利用 Jotai 内置的异步处理能力。