JotaiJotai

상태
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은 아톰 값을 쓰기 위한 것입니다.
이것은 대상 아톰의 쓰기 함수를 호출합니다.

렌더 함수에서 atom 생성 시 주의사항

atom 설정은 어디서든 생성할 수 있지만, 참조 동일성(referential equality)이 중요합니다.
동적으로 생성하는 것도 가능합니다.
렌더 함수에서 atom을 생성할 때는 안정적인 참조를 얻기 위해 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) => Value이며, get은 아톰 설정을 받아 Provider에 저장된 값을 반환하는 함수. 의존성이 추적되므로, get이 아톰에 대해 최소 한 번 사용되면 아톰 값이 변경될 때마다 read가 재평가됨.
  • write: 주로 아톰의 값을 변경하는 데 사용되는 함수. 더 자세히 설명하자면, useAtom이 반환하는 쌍의 두 번째 값인 useAtom()[1]을 호출할 때마다 이 함수가 호출됨. 기본 아톰에서 이 함수의 기본값은 해당 아톰의 값을 변경함. write의 시그니처는 (get, set, ...args) => Result. get은 위에서 설명한 것과 유사하지만 의존성을 추적하지 않음. set은 아톰 설정과 새로운 값을 받아 Provider에서 아톰 값을 업데이트하는 함수. ...argsuseAtom()[1]을 호출할 때 받는 인자들. Resultwrite 함수의 반환값.
const primitiveAtom = atom(initialValue)
const derivedAtomWithRead = atom(read)
const derivedAtomWithReadWrite = atom(read, write)
const derivedAtomWithWriteOnly = atom(null, write)

아톰에는 두 가지 종류가 있음: 쓰기 가능한 아톰과 읽기 전용 아톰. 기본 아톰은 항상 쓰기 가능함. 파생 아톰은 write가 지정된 경우에만 쓰기 가능함. 기본 아톰의 writeReact.useStatesetState와 동일함.

debugLabel 속성

생성된 아톰 설정에는 선택적 속성인 debugLabel을 추가할 수 있습니다. 디버그 레이블은 디버깅 시 아톰을 표시하는 데 사용됩니다. 자세한 내용은 디버깅 가이드를 참고하세요.

참고: 디버그 레이블은 반드시 고유할 필요는 없지만, 일반적으로 구분 가능하도록 만드는 것이 좋습니다.

onMount 속성

생성된 아톰 설정에는 선택적 속성인 onMount를 포함할 수 있습니다. onMountsetAtom 함수를 인자로 받고, 선택적으로 onUnmount 함수를 반환하는 함수입니다.

onMount 함수는 아톰이 프로바이더에서 처음으로 구독될 때 호출되며, onUnmount는 더 이상 구독되지 않을 때 호출됩니다. React strict mode와 같은 경우, 아톰이 언마운트된 후 즉시 다시 마운트될 수 있습니다.

const anAtom = atom(1)
anAtom.onMount = (setAtom) => {
console.log('atom is mounted in provider')
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 }) => {
// signal을 사용하여 함수를 중단할 수 있음
})
const writableDerivedAtom = atom(
async (get, { signal }) => {
// signal을 사용하여 함수를 중단할 수 있음
},
(get, set, arg) => {
// ...
},
)

signal 값은 AbortSignal입니다. signal.aborted 불리언 값을 확인하거나, addEventListener와 함께 abort 이벤트를 사용할 수 있습니다.

fetch 사용 사례에서는 간단히 signal을 전달할 수 있습니다.

아래 예제에서 fetch 사용법을 확인할 수 있습니다.

options.setSelf

이 함수는 self atom의 쓰기 함수를 호출하는 특별한 함수입니다.

⚠️ 주로 내부 사용과 서드파티 라이브러리 개발자를 위해 제공됩니다. 동작을 이해하려면 소스 코드를 주의 깊게 읽어보세요. 주요 변경 사항(breaking/non-breaking)이 있는지 릴리스 노트를 확인하세요.

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)}>이전</button>
<button onClick={() => setUserId((c) => c + 1)}>다음</button>
</div>
)
}
const UserName = () => {
const [user] = useAtom(userAtom)
return <div>사용자 이름: {user.name}</div>
}
const App = () => (
<>
<Controls />
<Suspense fallback="로딩 중...">
<UserName />
</Suspense>
</>
)
export default App