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)非常重要。原子也可以动态创建。若要在渲染函数中创建原子,必须使用 useMemo 或 useRef 来获取稳定的引用。如果不确定该使用哪个,请使用 useMemo。否则可能会导致 useAtom 进入无限循环。
const Component = ({ value }) => { const valueAtom = useMemo(() => atom({ value }), [value]) // ...}// primitive atomfunction atom<Value>(initialValue: Value): PrimitiveAtom<Value>
// read-only atomfunction atom<Value>(read: (get: Getter) => Value): Atom<Value>
// writable derived atomfunction 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 atomfunction 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) => Result。get与上面描述的类似,但不会追踪依赖。set是一个接收原子配置和新值的函数,用于更新 Provider 中的原子值。...args是调用useAtom()[1]时传入的参数。Result是write函数的返回值。
const primitiveAtom = atom(initialValue)const derivedAtomWithRead = atom(read)const derivedAtomWithReadWrite = atom(read, write)const derivedAtomWithWriteOnly = atom(null, write)原子分为两种:可写原子和只读原子。原始原子始终是可写的。派生原子只有在指定了 write 函数时才是可写的。原始原子的 write 等价于 React.useState 的 setState。
debugLabel 属性
Section titled “debugLabel 属性”创建的原子配置可以有一个可选的 debugLabel 属性。调试标签用于在调试时显示原子。更多信息请参阅调试指南。
注意:虽然调试标签不必唯一,但通常建议使其具有可区分性。
onMount 属性
Section titled “onMount 属性”创建的原子配置可以有一个可选的 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' })}高级 API
Section titled “高级 API”从 Jotai v2 开始,read 函数有了第二个参数 options。
options.signal
Section titled “options.signal”使用 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 用法示例。
options.setSelf
Section titled “options.setSelf”这是一个用于调用当前原子自身写入函数的特殊函数。
⚠️ 此功能主要供内部使用和第三方库作者使用。请仔细阅读源代码以了解其行为,并查阅发布说明以了解任何破坏性/非破坏性变更。
Stackblitz
Section titled “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