JotaiJotai

Jotai
React 原始而灵活的状态管理

异步

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

我们可以将它们分为两个主要类别:

  • 异步读取原子:异步请求在你尝试获取其值的瞬间立即开始。你可以将它们视为 "智能获取器"。
  • 异步写入原子:异步请求在特定时刻开始。你可以将它们视为 "动作"。

异步读取原子

原子的 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 为你做的内在挂起。

如果另一个原子使用了异步原子,它将返回一个 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) => {
// 等待某事
set(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()) // 将挂起,直到请求解析
}
// ...
}

在 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.lazy 之外的 Suspense 使用在 React 17 中仍然不受支持/未记录。如果这是阻碍,那么你仍然可以使用 loadable API 来避免挂起

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

如果你有一个 <Provider>,请在该 <Provider> 内放置至少一个 <Suspense>;否则,它可能会在渲染组件时造成无尽的循环。

const App = () => (
<Provider>
<Suspense fallback="加载中...">
<Layout />
</Suspense>
</Provider>
)

在组件树中有更多的 <Suspense> 也是可能的,并且必须考虑以最大程度地利用 Jotai 的内在处理。