跳转到内容

atom

atom 函数用于创建原子配置(atom config)。 我们称之为”原子配置”,因为它只是一个定义,尚未持有任何值。 在上下文明确的情况下,我们也会直接称其为”原子”。

原子配置是一个不可变对象。原子配置对象本身不持有值,原子的值存在于 Store 中。

要创建一个原始原子(配置),你只需提供一个初始值。

import { atom } from 'jotai'
const priceAtom = atom(10)
const messageAtom = atom('hello')
const productAtom = atom({ id: 12, name: 'good stuff' })

你也可以创建派生原子。有三种模式:

  • 只读原子
  • 只写原子
  • 读写原子

创建派生原子时,需要传入一个读取函数和一个可选的写入函数。

const readOnlyAtom = atom((get) => get(priceAtom) * 2)
const writeOnlyAtom = atom(
null, // it's a convention to pass `null` for the first argument
(get, set, update) => {
// `update` is any single value we receive for updating this atom
set(priceAtom, get(priceAtom) - update.discount)
// or we can pass a function as the second parameter
// the function will be invoked,
// receiving the atom's current value as its first parameter
set(priceAtom, (price) => price - update.discount)
},
)
const readWriteAtom = atom(
(get) => get(priceAtom) * 2,
(get, set, newPrice) => {
set(priceAtom, newPrice / 2)
// you can set as many atoms as you want at the same time
},
)

读取函数中的 get 用于读取原子的值。它是响应式的,读取依赖会被自动追踪。

写入函数中的 get 同样用于读取原子的值,但不会被追踪。此外,在 Jotai v1 API 中,它无法读取尚未解析的异步值。

写入函数中的 set 用于写入原子的值。它会调用目标原子的写入函数。

关于在渲染函数中创建原子的注意事项

Section titled “关于在渲染函数中创建原子的注意事项”

原子配置可以在任何位置创建,但引用相等性(referential equality)非常重要。原子也可以动态创建。若要在渲染函数中创建原子,必须使用 useMemouseRef 来获取稳定的引用。如果不确定该使用哪个,请使用 useMemo。否则可能会导致 useAtom 进入无限循环。

const Component = ({ value }) => {
const valueAtom = useMemo(() => atom({ value }), [value])
// ...
}
// primitive atom
function atom<Value>(initialValue: Value): PrimitiveAtom<Value>
// read-only atom
function atom<Value>(read: (get: Getter) => Value): Atom<Value>
// writable derived atom
function atom<Value, Args extends unknown[], Result>(
read: (get: Getter) => Value,
write: (get: Getter, set: Setter, ...args: Args) => Result,
): WritableAtom<Value, Args, Result>
// write-only derived atom
function atom<Value, Args extends unknown[], Result>(
read: Value,
write: (get: Getter, set: Setter, ...args: Args) => Result,
): WritableAtom<Value, Args, Result>
  • initialValue:原子在值被修改之前返回的初始值。
  • read:每次读取原子时都会被求值的函数。read 的签名是 (get) => Value,其中 get 是一个接收原子配置并返回其在 Provider 中存储的值的函数(如下所述)。依赖会被追踪,因此只要 get 至少使用过一次某个原子,当该原子的值发生变化时,read 就会被重新求值。
  • write:主要用于修改原子值的函数;更准确地说,当我们调用 useAtom 返回的元组的第二个值(即 useAtom()[1])时,它会被调用。原始原子中该函数的默认实现会直接修改原子的值。write 的签名是 (get, set, ...args) => Resultget 与上面描述的类似,但不会追踪依赖。set 是一个接收原子配置和新值的函数,用于更新 Provider 中的原子值。...args 是调用 useAtom()[1] 时传入的参数。Resultwrite 函数的返回值。
const primitiveAtom = atom(initialValue)
const derivedAtomWithRead = atom(read)
const derivedAtomWithReadWrite = atom(read, write)
const derivedAtomWithWriteOnly = atom(null, write)

原子分为两种:可写原子和只读原子。原始原子始终是可写的。派生原子只有在指定了 write 函数时才是可写的。原始原子的 write 等价于 React.useStatesetState

创建的原子配置可以有一个可选的 debugLabel 属性。调试标签用于在调试时显示原子。更多信息请参阅调试指南

注意:虽然调试标签不必唯一,但通常建议使其具有可区分性。

创建的原子配置可以有一个可选的 onMount 属性。onMount 是一个接收 setAtom 函数的函数,可以选择性地返回一个 onUnmount 函数。

onMount 函数在原子首次在 Provider 中被订阅时调用,onUnmount 在原子不再被订阅时调用。在某些情况下(如 React 严格模式),原子可能会先卸载然后立即重新挂载。

const anAtom = atom(1)
anAtom.onMount = (setAtom) => {
console.log('atom is mounted in provider')
setAtom(c => c + 1) // increment count on mount
return () => { ... } // return optional onUnmount function
}
const Component = () => {
// `onMount` will be called when the component is mounted in the following cases:
useAtom(anAtom)
useAtomValue(anAtom)
// however, in the following cases,
// `onMount` will not be called because the atom is not subscribed:
useSetAtom(anAtom)
useAtomCallback(
useCallback((get) => get(anAtom), []),
)
// ...
}

调用 setAtom 函数会触发原子的 write 函数。通过自定义 write 可以改变其行为。

const countAtom = atom(1)
const derivedAtom = atom(
(get) => get(countAtom),
(get, set, action) => {
if (action.type === 'init') {
set(countAtom, 10)
} else if (action.type === 'inc') {
set(countAtom, (c) => c + 1)
}
},
)
derivedAtom.onMount = (setAtom) => {
setAtom({ type: 'init' })
}

从 Jotai v2 开始,read 函数有了第二个参数 options

使用 AbortController 来中止异步函数。当新的计算(调用 read 函数)开始前,中止操作会被触发。

使用方式:

const readOnlyDerivedAtom = atom(async (get, { signal }) => {
// use signal to abort your function
})
const writableDerivedAtom = atom(
async (get, { signal }) => {
// use signal to abort your function
},
(get, set, arg) => {
// ...
},
)

signal 的值是 AbortSignal。你可以检查 signal.aborted 布尔值,或通过 addEventListener 监听 abort 事件。

对于 fetch 的使用场景,可以直接传递 signal

请参阅下面的 fetch 用法示例。

这是一个用于调用当前原子自身写入函数的特殊函数。

⚠️ 此功能主要供内部使用和第三方库作者使用。请仔细阅读源代码以了解其行为,并查阅发布说明以了解任何破坏性/非破坏性变更。

在 StackBlitz 中打开

import { Suspense } from 'react'
import { atom, useAtom } from 'jotai'
const userIdAtom = atom(1)
const userAtom = atom(async (get, { signal }) => {
const userId = get(userIdAtom)
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}?_delay=2000`,
{ signal },
)
return response.json()
})
const Controls = () => {
const [userId, setUserId] = useAtom(userIdAtom)
return (
<div>
User Id: {userId}
<button onClick={() => setUserId((c) => c - 1)}>Prev</button>
<button onClick={() => setUserId((c) => c + 1)}>Next</button>
</div>
)
}
const UserName = () => {
const [user] = useAtom(userAtom)
return <div>User name: {user.name}</div>
}
const App = () => (
<>
<Controls />
<Suspense fallback="Loading...">
<UserName />
</Suspense>
</>
)
export default App