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
用于写入原子值。它将调用目标原子的写入函数。
在渲染函数中创建原子的注意事项
原子配置可以在任何地方创建,但引用相等性很重要。它们也可以动态创建。在渲染函数中创建原子时,需要useMemo
或useRef
来获取稳定的引用。如果对于使用useMemo
或useRef
进行记忆化有疑问,使用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) => 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 属性
创建的原子配置可以有一个可选的属性debugLabel
。调试标签用于在调试中显示原子。有关更多信息,请参阅调试指南。
注意:虽然调试标签不必是唯一的,但通常建议使它们可区分。
onMount 属性
创建的原子配置可以有一个可选的属性onMount
。onMount
是一个函数,它接受一个函数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
布尔值,或者使用addEventListener
与abort
事件。
对于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