JotaiJotai

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

迁移到v2 API

参考: https://github.com/pmndrs/jotai/discussions/1514

Jotai v1 在2022年6月发布,收到了各种反馈。 React也提出了对promise的一流支持。 Jotai v2 将有一个新的API。

不幸的是,新功能伴随着一些破坏性的变化。

新功能是什么

Vanilla库

Jotai带有vanilla(非React)函数 和React函数分开。 它们从像jotai/vanilla这样的备用入口点提供。

Store API

Jotai公开了store接口,以便您可以直接操作原子值。

import { createStore } from 'jotai' // 或者从 'jotai/vanilla'
const store = createStore()
store.set(fooAtom, 'foo')
console.log(store.get(fooAtom)) // 打印 "foo"
const unsub = store.sub(fooAtom, () => {
console.log('fooAtom 在 store 中的值已经改变')
})
// 调用 unsub() 来取消订阅。

您也可以创建自己的React Context来传递一个store。

更灵活的原子 write 函数

write函数可以接受多个参数, 并返回一个值。

atom(
(get) => get(...),
(get, set, arg1, arg2, ...) => {
...
return someValue
}
)

什么是破坏性的

异步原子不再特殊

异步原子只是带有promise值的普通原子。 原子的getter函数不解析promises。 另一方面,useAtom钩子继续解析promises。

splitAtom这样的一些工具期望同步原子, 并且不会与异步原子一起工作。

可写原子类型已更改(仅限TypeScript)

// 旧的
WritableAtom<Value, Arg, Result extends void | Promise<void>>
// 新的
WritableAtom<Value, Args extends unknown[], Result>

一般来说,我们应该避免直接使用WritableAtom类型。

一些函数被删除

  • Provider的initialValues属性被删除,因为store更灵活。
  • Provider的scope属性被删除,因为你可以创建自己的context。
  • abortableAtom工具被删除,因为该功能默认包含在内
  • waitForAll工具被删除,因为Promise.all就可以工作

迁移指南

异步原子

异步原子的读函数的get函数 不解析promises,所以你必须放await.then()

简单来说,改变就像下面这样。 (如果你是TypeScript用户,类型会告诉你在哪里改变。)

之前的API
const asyncAtom = atom(async () => 'hello')
const derivedAtom = atom((get) => get(asyncAtom).toUppercase())
新的API
const asyncAtom = atom(async () => 'hello')
const derivedAtom = atom(async (get) => (await get(asyncAtom)).toUppercase())
// 或者
const derivedAtom = atom((get) => get(asyncAtom).then((x) => x.toUppercase()))

Provider的initialValues属性

之前的API
const countAtom = atom(0)
// 在组件中
<Provider initialValues={[[countAtom, 1]]}>
...
新 API
const countAtom = atom(0)
const HydrateAtoms = ({ initialValues, children }) => {
useHydrateAtoms(initialValues)
return children
}
// 在组件中
<Provider>
<HydrateAtoms initialValues={[[countAtom, 1]]}>
...

Provider的 scope 属性

旧 API
const myScope = Symbol()
// 父组件
<Provider scope={myScope}>
...
</Provider>
// 子组件
useAtom(..., myScope)
新 API
const MyContext = createContext()
const store = createStore()
// 父组件
<MyContext.Provider value={store}>
...
</MyContext.Provider>
// 子组件
const store = useContext(MyContext)
useAtom(..., { store })

abortableAtom 工具

你不再需要之前的 abortableAtom 工具, 因为现在普通的 atom 已经支持了。

旧 API
const asyncAtom = abortableAtom(async (get, { signal }) => {
...
}
新 API
const asyncAtom = atom(async (get, { signal }) => {
...
}

waitForAll 工具

你不再需要之前的 waitForAll 工具, 因为我们可以使用原生的 Promise API。

旧 API
const allAtom = waitForAll([fooAtom, barAtom])
新 API
const allAtom = atom((get) => Promise.all([get(fooAtom), get(barAtom)]))

注意,在渲染函数中创建 atom 可能会导致无限循环

splitAtom 工具(或其他一些工具)与异步 atoms

splitAtom 工具只接受同步的 atoms。 你需要在传递之前解包异步 atoms。

这也适用于其他一些工具,如 jotai-tanstack-queryatomsWithQuery

旧 API
const splittedAtom = splitAtom(asyncArrayAtom)
新 API
const splittedAtom = splitAtom(unwrap(asyncArrayAtom, () => []))

在编写时,unwrap 是不稳定的,且未被文档记录。 你可以改用 loadable,它在加载状态上提供了更多的控制。 如果你需要使用 <Suspense>,atoms-in-atom 模式会有所帮助。

更多信息,请参考以下讨论:

其他一些变化

工具

  • atomWithStorage 工具的 delayInit 已被移除并默认启用。它将始终在首次渲染时渲染 initialValue,并在后续渲染时渲染存储的值(如果有)。新的行为与 v1 不同。更多信息请参见 https://github.com/pmndrs/jotai/discussions/1737
  • useHydrateAtoms 只能接受可写的 atoms。

导入声明

v2 API 也为库作者和非 React 用户提供了从其他入口点导入的方式。

  • jotai/vanilla
  • jotai/vanilla/utils
  • jotai/react
  • jotai/react/utils
// 自 v1.11.0 起可用
import { atom } from 'jotai/vanilla'
import { useAtom } from 'jotai/react'
// 自 v2.0.0 起可用
import { atom } from 'jotai' // 与 'jotai/vanilla' 相同
import { useAtom } from 'jotai' // 与 'jotai/react' 相同

注意:如果你不使用 ESM,你可能更倾向于使用 jotai/vanilla 等,而不是 jotai,以便更好地进行树摇(tree shaking)。