JotaiJotai

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

atom

atom

atom函数用于创建一个原子配置。我们称之为"原子配置",因为它只是一个定义,还没有保存任何值。如果上下文清晰,我们也可以简单地称之为"原子"。

原子配置是一个不可变对象。原子配置对象不保存任何值。原子值存在于一个存储中。

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

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, // 传递`null`作为第一个参数是一种约定
(get, set, update) => {
// `update`是我们接收到的用于更新此原子的任何单一值
set(priceAtom, get(priceAtom) - update.discount)
// 或者我们可以将函数作为第二个参数传递
// 该函数将被调用,
// 接收原子的当前值作为其第一个参数
set(priceAtom, (price) => price - update.discount)
},
)
const readWriteAtom = atom(
(get) => get(priceAtom) * 2,
(get, set, newPrice) => {
set(priceAtom, newPrice / 2)
// 你可以同时设置尽可能多的原子
},
)

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

写入函数中的get也用于读取原子值,但它不会被跟踪。此外,它不能读取Jotai v1 API中未解析的异步值。

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

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

原子配置可以在任何地方创建,但引用相等性很重要。它们也可以动态创建。在渲染函数中创建原子时,需要useMemouseRef来获取稳定的引用。如果对于使用useMemouseRef进行记忆化有疑问,使用useMemo。否则,它可能会与useAtom一起导致无限循环。

const Component = ({ value }) => {
const valueAtom = useMemo(() => atom({ value }), [value])
// ...
}

签名

// 基本原子
function atom<Value>(initialValue: Value): PrimitiveAtom<Value>
// 只读原子
function atom<Value>(read: (get: Getter) => Value): Atom<Value>
// 可写的派生原子
function atom<Value, Args extends unknown[], Result>(
read: (get: Getter) => Value,
write: (get: Getter, set: Setter, ...args: Args) => Result,
): WritableAtom<Value, Args, Result>
// 只写的派生原子
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) => Valueget是一个函数,它接收一个原子配置并返回在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 属性

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

注意:虽然调试标签不必是唯一的,但通常建议使它们可区分。

onMount 属性

创建的原子配置可以有一个可选的属性onMountonMount是一个函数,它接受一个函数setAtom并可选地返回onUnmount函数。

当原子在提供者中第一次被订阅时,调用onMount函数, 当它不再被订阅时,调用onUnmount。 在某些情况下(如React严格模式), 一个原子可以被卸载然后立即装载。

const anAtom = atom(1)
anAtom.onMount = (setAtom) => {
console.log('atom已在提供者中挂载')
setAtom(c => c + 1) // 在装载时增加计数
return () => { ... } // 返回可选的onUnmount函数
}
const Component = () => {
// 当组件在以下情况下装载时,将调用`onMount`:
useAtom(anAtom)
useAtomValue(anAtom)
// 然而,在以下情况下,
// `onMount`将不会被调用,因为原子没有被订阅:
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

自Jotai v2起,read函数有第二个参数options

options.signal

它使用AbortController,以便你可以中止异步函数。 在新的计算(调用read函数)开始之前触发中止。

如何使用它:

const readOnlyDerivedAtom = atom(async (get, { signal }) => {
// 使用信号来中止你的函数
})
const writableDerivedAtom = atom(
async (get, { signal }) => {
// 使用信号来中止你的函数
},
(get, set, arg) => {
// ...
},
)

signal值是AbortSignal。 你可以检查signal.aborted布尔值,或者使用addEventListenerabort事件。

对于fetch用例,我们可以简单地传递signal

参见下面的fetch使用示例。

options.setSelf

它是一个特殊的函数,用于调用自身原子的写函数。

⚠️ 它主要为内部使用和第三方库作者提供。仔细阅读源代码以理解行为。检查发布说明以获取任何破坏性/非破坏性的更改。

codesandbox

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