Async
비동기 아톰을 사용하면 실제 데이터에 접근할 수 있으면서도 아톰에서 직접 관리할 수 있고, 이를 매우 쉽게 처리할 수 있습니다.
이를 두 가지 주요 범주로 나눌 수 있습니다:
- 비동기 읽기 아톰: 값을 가져오려고 시도하는 즉시 비동기 요청이 시작됩니다. 이를 "스마트 게터"라고 생각할 수 있습니다.
- 비동기 쓰기 아톰: 특정 순간에 비동기 요청이 시작됩니다. 이를 "액션"이라고 생각할 수 있습니다.
비동기 읽기 아톰
아톰의 read
함수는 Promise를 반환할 수 있습니다.
const countAtom = atom(1)const asyncAtom = atom(async (get) => get(countAtom) * 2)
Jotai는 기본적으로 비동기 흐름을 처리하기 위해 Suspense
를 활용합니다.
const ComponentUsingAsyncAtoms = () => {const [num] = useAtom(asyncAtom)// 여기서 `num`은 항상 `number` 타입입니다. asyncAtom이 Promise를 반환하더라도 말이죠.}const App = () => {return (<Suspense fallback={/* 서스펜드 중에 보여줄 내용 */}><ComponentUsingAsyncAtoms /></Suspense>)}
또는, Jotai가 자동으로 처리하는 서스펜드를 피하고 싶다면, 아톰을 loadable
API로 감싸면 됩니다.
다른 아톰이 비동기 아톰을 사용한다면, Promise를 반환합니다. 따라서 해당 아톰도 비동기로 만들어야 합니다.
const anotherAtom = atom(async (get) => (await get(asyncAtom)) / 2)
이것은 쓰기 함수가 있는 아톰에도 적용됩니다.
const asyncAtom = atom(async (get) => ...)const writeAtom = atom(null, async (get, set, payload) => {await get(asyncAtom)// ...})
비동기 쓰기 아톰
비동기 쓰기 아톰은 또 다른 종류의 비동기 아톰입니다. 아톰의 write
함수가 Promise를 반환할 때 사용합니다.
const countAtom = atom(1)const asyncIncrementAtom = atom(null, async (get, set) => {// 무언가를 기다림set(countAtom, get(countAtom) + 1)})const Component = () => {const [, increment] = useAtom(asyncIncrementAtom)const handleClick = () => {increment()}// ...}
비동기 처리의 유연성
Jotai를 사용하면 비동기에서 동기로 전환하여 원할 때 Suspense를 트리거할 수 있는 흥미로운 패턴을 구현할 수 있습니다.
const request = async () => fetch('https://...').then((res) => res.json())const baseAtom = atom(0)const Component = () => {const [value, setValue] = useAtom(baseAtom)const handleClick = () => {setValue(request()) // 요청이 완료될 때까지 Suspense가 발생}// ...}
TypeScript에서 사용하기
TypeScript에서 atom(0)
은 PrimitiveAtom<number>
로 추론됩니다. 이 경우 Promise<number>
를 값으로 받을 수 없기 때문에 앞의 코드는 타입 검사를 통과하지 못합니다. 이를 해결하려면 아톰을 명시적으로 타입 지정하고 Promise<number>
를 허용하는 값으로 추가해야 합니다.
const baseAtom = atom<number | Promise<number>>(0) // 동기 및 비동기 값을 모두 허용
Async forever
때로는 예측할 수 없는 시점(또는 영원히)까지 실행을 중단하고 싶을 수 있습니다.
const baseAtom = atom(new Promise(() => {})) // 다른 값이 설정될 때까지 실행이 중단됩니다
Suspense
Jotai는 비동기 지원을 퍼스트클래스 기능으로 제공합니다. Jotai는 React Suspense를 핵심적으로 활용합니다.
기술적으로 React 17에서는 React.lazy 이외의 Suspense 사용이 아직 지원되지 않거나 문서화되지 않았습니다. 이 문제가 블로킹 요소라면,
loadable
API를 사용하여 Suspense를 피할 수 있습니다.
비동기 아톰을 사용하려면 컴포넌트 트리를 <Suspense>
로 감싸야 합니다.
<Provider>
를 사용하는 경우,<Provider>
내부에 최소 하나 이상의<Suspense>
를 배치해야 합니다. 그렇지 않으면 컴포넌트를 렌더링하는 동안 무한 루프가 발생할 수 있습니다.
const App = () => (<Provider><Suspense fallback="Loading..."><Layout /></Suspense></Provider>)
컴포넌트 트리 내에 더 많은 <Suspense>
를 배치하는 것도 가능하며, Jotai의 내장된 처리를 최대한 활용하기 위해 고려해야 합니다.