Derive
언제 유용할까?
- 캐시에 대한 로컬 업데이트로 인해 미세한 지연이 발생할 때
- 불필요한 재계산으로 인해 성능 문제가 발생할 때
Jōtai는 웹 프레임워크(예: React) 외부에서 비동기 데이터를 다루기 위한 강력한 기본 기능을 제공하며, UI와 비즈니스 로직이 데이터 레이어와 적절히 통합될 수 있도록 합니다. 많은 데이터 페칭 통합은 아톰을 통해 클라이언트 측 캐시를 엿볼 수 있게 해줍니다. 캐시가 아직 채워지지 않았다면, 아톰은 값의 Promise로 해결되어야 합니다. 하지만 값이 이미 캐시에 존재하고 낙관적 업데이트를 수행한다면, 해당 값을 즉시 다운스트림에서 사용할 수 있습니다.
이러한 이중 성격(때로는 비동기, 때로는 동기)의 아톰을 기반으로 데이터 그래프를 구축할 때, 주의하지 않으면 불필요한 리렌더링, 오래된 값, 그리고 React의 경우 미세한 지연이 발생할 수 있습니다.
jotai-derive는 값이 사용 가능해지는 즉시(값을 기다리거나 동기적으로 처리하는 방식으로) 동작하는 비동기 데이터 그래프를 구축하기 위한 기본 기능을 제공합니다.
설치
이 기능을 사용하려면 jotai-derive
를 설치해야 합니다.
npm install jotai-derive
derive
이중 성격을 가진 아톰이 있다고 가정해 보겠습니다. 이 아톰은 때로는 값을 아직 알 수 없는 상태(예: 데이터를 가져오는 중)일 수 있고, 다른 때는 로컬에서 아톰을 업데이트하여 값을 즉시 알 수 있는 상태(예: 낙관적 업데이트)일 수 있습니다.
// `jotai-derive`는 `jotai-apollo`뿐만 아니라 대부분의 데이터 가져오기 솔루션에 적용 가능합니다.import { atomWithQuery } from 'jotai-apollo';// 이중 성격을 가진 아톰 예시const userAtom: Atom<User | Promise<User>> =atomWithQuery(...);
아래는 일반적으로 파생된 아톰을 생성하는 방법입니다. 단점은 값이 알려져 있는 경우에도 항상 await
를 사용해야 하기 때문에 불필요한 지연과 재계산이 발생한다는 것입니다.
// `jotai-derive` 없이import { atom } from 'jotai'// 타입은 Atom<Promise<string>>입니다.// get(userAtom)이 항상 Promise를 반환하지 않더라도,// `uppercaseNameAtom`을 동기적으로 계산할 수 있음에도 불구하고// 항상 await를 사용해야 합니다.const uppercaseNameAtom = atom(async (get) => {const user = await get(userAtom)return user.name.toUppercase()})
다음은 jotai-derive
를 사용하여 더 타이트한 비동기 데이터 처리 파이프라인을 만드는 방법입니다.
// `jotai-derive` 사용import { derive } from 'jotai-derive'// Atom<string | Promise<string>>const uppercaseNameAtom = derive([userAtom], // 필요할 때만 await 됨(user) => user.name.toUppercase(),)
여러 비동기 의존성
여러 아톰에서 값을 도출하기 위해 배열에 하나 이상의 아톰을 전달할 수 있습니다. 이때 해당 값들은 동일한 순서로 프로듀서 함수에 전달됩니다.
import { derive } from 'jotai-derive'// Atom<string | Promise<string>>const welcomeMessageAtom = derive([userAtom, serverNameAtom],(user, serverName) => `Welcome ${user.name} to ${serverName}!`,)
soon
더 고급 사용법, 예를 들어 조건부 의존성을 다룰 때는 soon
과 soonAll
함수를 사용할 수 있습니다. (derive
는 이 함수들을 감싸는 유틸리티 래퍼입니다.)
조건부 의존성
// `soon`을 직접 사용할 때 pipes를 사용하면 코드가 더 깔끔해진다.import { pipe } from 'remeda';import { soon } from 'jotai-derive';// Atom<RestrictedItem | Promise<RestrictedItem>>const queryAtom = ...;// Atom<boolean | Promise<boolean>>const isAdminAtom = ...;// Atom<null | RestrictedItem | Promise<null | RestrictedItem>>const restrictedItemAtom = atom((get) =>pipe(get(isAdminAtom),soon((isAdmin) => (isAdmin ? get(queryAtom) : null))));
조건부 의존성 (다중 조건)
// `soon`을 직접 사용할 때 파이프를 사용하면 코드가 더 깔끔해진다.import { pipe } from 'remeda';import { soon, soonAll } from 'jotai-derive';// Atom<RestrictedItem | Promise<RestrictedItem>>const queryAtom = ...;// Atom<boolean | Promise<boolean>>const isAdminAtom = ...;// Atom<boolean | Promise<boolean>>const enabledAtom = ...;// Atom<null | RestrictedItem | Promise<null | RestrictedItem>>const restrictedItemAtom = atom((get) =>pipe(soonAll(get(isAdminAtom), get(enabledAtom)),soon(([isAdmin, enabled]) => (isAdmin && enabled ? get(queryAtom) : null))));