JotaiJotai

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

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

더 고급 사용법, 예를 들어 조건부 의존성을 다룰 때는 soonsoonAll 함수를 사용할 수 있습니다. (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))
)
);

데모