JotaiJotai

상태
React를 위한 기본적이고 유연한 상태 관리

useAtom

useAtom

useAtom 훅은 상태에서 아톰을 읽어오는 데 사용됩니다.
상태는 아톰 설정과 아톰 값으로 이루어진 WeakMap으로 볼 수 있습니다.

useAtom 훅은 React의 useState와 마찬가지로, 아톰 값과 업데이트 함수를 튜플 형태로 반환합니다.
이 훅은 atom()로 생성된 아톰 설정을 인자로 받습니다.

아톰 설정이 생성될 때는 아직 값이 연결되어 있지 않습니다.
useAtom을 통해 아톰이 사용되면 초기값이 상태에 저장됩니다.
만약 아톰이 파생된 아톰이라면, 초기값을 계산하기 위해 읽기 함수가 호출됩니다.
아톰이 더 이상 사용되지 않으면, 즉 해당 아톰을 사용하는 모든 컴포넌트가 언마운트되고 아톰 설정이 더 이상 존재하지 않으면, 상태의 값은 가비지 컬렉션됩니다.

const [value, setValue] = useAtom(anAtom)

setValue는 하나의 인자를 받으며, 이 인자는 아톰의 쓰기 함수에 세 번째 매개변수로 전달됩니다.
최종 결과는 쓰기 함수가 어떻게 구현되었는지에 따라 달라집니다.
쓰기 함수가 명시적으로 설정되지 않았다면, 아톰은 setValue에 전달된 값을 그대로 받습니다.

참고: atom 섹션에서 언급했듯이, 아톰을 생성할 때 참조 동등성(referential equality)이 중요합니다.
이를 제대로 처리하지 않으면 무한 루프가 발생할 수 있습니다.

const stableAtom = atom(0)
const Component = () => {
const [atomValue] = useAtom(atom(0)) // 이 코드는 매 렌더링마다 아톰 인스턴스가 재생성되므로 무한 루프를 일으킵니다.
const [atomValue] = useAtom(stableAtom) // 이 코드는 문제없이 동작합니다.
const [derivedAtomValue] = useAtom(
useMemo(
// 이 코드도 문제없이 동작합니다.
() => atom((get) => get(stableAtom) * 2),
[],
),
)
}

참고: React는 컴포넌트를 호출하는 역할을 담당하므로, 컴포넌트는 멱등성(idempotent)을 가져야 하며 여러 번 호출될 준비가 되어 있어야 합니다.
프롭스나 아톰이 변경되지 않았더라도 추가 리렌더링이 발생하는 경우가 많습니다.
커밋 없이 발생하는 추가 리렌더링은 React 18의 useReducer의 기본 동작이므로 예상된 현상입니다.

Signatures

// 원시적이거나 쓰기 가능한 파생 아톰
function useAtom<Value, Update>(
atom: WritableAtom<Value, Update>,
options?: { store?: Store },
): [Value, SetAtom<Update>]
// 읽기 전용 아톰
function useAtom<Value>(
atom: Atom<Value>,
options?: { store?: Store },
): [Value, never]

아톰 의존성 작동 방식

"read" 함수를 호출할 때마다 의존성과 의존 대상(dependents)을 새로 고칩니다.

read 함수는 아톰의 첫 번째 매개변수입니다. B가 A에 의존한다는 것은 A가 B의 의존성이고, B가 A의 의존 대상임을 의미합니다.

const uppercaseAtom = atom((get) => get(textAtom).toUpperCase())

아톰을 생성할 때는 의존성이 존재하지 않습니다. 처음 사용할 때 read 함수를 실행하고 uppercaseAtomtextAtom에 의존한다는 결론을 내립니다. 따라서 uppercaseAtomtextAtom의 의존 대상에 추가됩니다. uppercaseAtom의 read 함수를 다시 실행할 때(textAtom 의존성이 업데이트되었기 때문에), 의존성이 다시 생성됩니다. 이 경우 동일한 의존성이 생성됩니다. 그런 다음 textAtom에서 오래된 의존 대상을 제거하고 최신 버전으로 대체합니다.

아톰은 필요할 때 생성할 수 있습니다

여기서 보여준 기본 예제들은 컴포넌트 외부에서 전역적으로 아톰을 정의하는 방법을 보여줍니다. 하지만 아톰을 어디서나, 언제든지 생성할 수 있습니다. 아톰은 객체 참조 식별자로 구분된다는 점만 기억하면 됩니다.

렌더링 함수 내에서 아톰을 생성하려면 일반적으로 useRefuseMemo 같은 훅을 사용해 메모이제이션을 적용합니다. 그렇지 않으면 컴포넌트가 렌더링될 때마다 아톰이 다시 생성됩니다.

아톰을 생성하고 useState에 저장하거나 다른 아톰에 저장할 수도 있습니다. 이슈 #5에서 예제를 확인할 수 있습니다.

아톰을 전역적으로 캐시할 수도 있습니다. 이 예제저 예제를 참고하세요.

매개변수화된 아톰을 사용하려면 유틸리티의 atomFamily를 확인하세요.

useAtomValue

const countAtom = atom(0)
const Counter = () => {
const setCount = useSetAtom(countAtom)
const count = useAtomValue(countAtom)
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}

useSetAtom 훅과 유사하게, useAtomValue는 읽기 전용 아톰에 접근할 수 있게 해줍니다. 하지만 이 훅은 읽기-쓰기 아톰의 값에도 접근할 수 있습니다.

useSetAtom

const switchAtom = atom(false)
const SetTrueButton = () => {
const setCount = useSetAtom(switchAtom)
const setTrue = () => setCount(true)
return (
<div>
<button onClick={setTrue}>Set True</button>
</div>
)
}
const SetFalseButton = () => {
const setCount = useSetAtom(switchAtom)
const setFalse = () => setCount(false)
return (
<div>
<button onClick={setFalse}>Set False</button>
</div>
)
}
export default function App() {
const state = useAtomValue(switchAtom)
return (
<div>
State: <b>{state.toString()}</b>
<SetTrueButton />
<SetFalseButton />
</div>
)
}

아톰의 값을 읽지 않고 업데이트해야 할 때 useSetAtom()을 사용할 수 있습니다.

이 방법은 성능이 중요한 경우에 특히 유용합니다. const [, setValue] = useAtom(valueAtom)을 사용하면 valueAtom이 업데이트될 때마다 불필요한 리렌더링이 발생할 수 있기 때문입니다.