[번역] Jotai에 React Query가 필요하지 않을 수도 있습니다

eunbinn·2023년 3월 13일
20

FrontEnd 번역

목록 보기
18/18
post-thumbnail

출처: https://blog.axlight.com/posts/you-might-not-need-react-query-for-jotai/

서론

Jotai 개발이 시작되었을 떄(v1이 출시되기 전), Jotai의 목표는 간단했습니다. 큰 상태 객체를 사용하는 useState와 useContext에서 종종 문제가 되었던 리렌더링을 최적화 하는 것이었습니다. 또한 Redux에 의해 대중화되어 널리 사용되고 있는 selector 함수를 사용하고 싶지 않았습니다.

초기에는 데이터 페칭 솔루션을 포함하고 싶었지만 Jotai 자체를 복잡하게 만들고 싶지는 않았습니다. 그래서 jotai/query 패키지를 만들었습니다. React Query v3와 통합한 것인데, 잘 작동했지만 사용자의 니즈에 맞지 않는 부분이 있었습니다. Jotai용 데이터 페칭 API를 만드는 것이 목적이었지만 사용자들은 React Query의 모든 기능을 원했습니다. 고심 끝에 결국 새로운 라이브러리인 jotai-tanstack-query를 만들었습니다. 이 패키지는 TanStack Query v4를 통합하여 모든 기능을 제공합니다.

이제 다시 원래의 과제로 돌아왔습니다. Jotai를 위한 데이터 페칭 API는 무엇일까요? 이 글은 그 질문의 답을 찾기 위한 여정을 담고 있습니다. 당장 적용할 수 있는 결론에 도달하지 못할 수도 있지만, 일단 시작해보겠습니다.

Jotai가 할 수 있는 것

Jotai는 내부적으로 atom들을 저장하는 스토어를 가지고 있습니다. 이 스토어는 atom 자체가 key가 되고 atom의 값을 최신 값으로 갖는 WeakMap으로 구현되어 있습니다. WeakMap을 사용하면 메모리 누수를 방지할 수 있습니다. 스토어에는 atom당 하나의 값만 가지고 있으며 변경 내역은 보관하지 않습니다.

Jotai v2 API에서는 createStore가 노출되어 있어 atom 값을 직접 읽고 쓸 수 있습니다.

import { createStore, atom } from "jotai/vanilla";

const store = createStore();

const atom1 = atom();
const atom2 = atom();

store.set(atom1, "hello");
store.set(atom2, "world");

console.log(store.get(atom1)); // ---> 'hello'

캐싱을 위해 스토어를 사용할 수 있습니다. Atom들은 비동기 함수로 정의할 수도 있습니다. 다음은 간단한 데이터 페칭 예시입니다.

const idAtom = atom(1);
const dataAtom = atom(async (get) => {
  const id = get(idAtom);
  const res = await fetch(`https://reqres.in/api/posts/${id}`);
  const data = await res.json();
  return data;
});

dataAtomidAtom에 종속적이며 종속성이 변경될 때만 재평가됩니다. 따라서 매우 기본적인 캐시처럼 동작합니다.

여기서 Jotai가 가지는 강점은 dataAtom에서 또 다른 atom을 파생시킬 수 있다는 점입니다. 이를 통해 소위 데이터 흐름 그래프를 형성할 수 있습니다.

또한 atomWithDefault와 같은 유틸리티를 사용하여 캐시 값을 재정의할 수도 있습니다.

요점은 dataAtom이 정의되면 모든 Jotai의 기능을 사용하여 모든 작업을 수행할 수 있다는 것입니다. 라이브러리 관점에서는 dataAtom이 다른 곳의 캐시 데이터인지 알 수 없습니다.

Jotai가 할 수 없는 것

Jotai만으로도 아주 간단한 데이터 페칭은 가능한 것 같습니다. 그렇다면 무엇이 부족한걸까요? 여러 가지가 있겠지만 다음의 한 가지에 집중해 보겠습니다. 원래 jotai/query를 만든 목적 중 하나는 더욱 풍부한 캐싱의 지원입니다.

Jotai의 스토어는 현재의 값만 저장할 수 있기 때문에 캐싱에 매우 제한적입니다. 이전 예시를 다시 살펴보겠습니다.

const idAtom = atom(1);
const dataAtom = atom(async (get) => {
  const id = get(idAtom);
  const res = await fetch(`https://reqres.in/api/posts/${id}`);
  const data = await res.json();
  return data;
});

idAtom의 값을 1에서 2로 변경하면 비동기 함수가 호출되고 dataAtom의 값이 업데이트됩니다. 다시 2에서 1로 변경하면 어떻게 될까요? Jotai의 스토어는 dataAtom의 최신 값만 갖고 있기 때문에 id=1로 비동기 함수를 다시 호출할 것입니다. 비동기 함수는 서버에서 데이터를 가져오고 데이터 페칭은 일반적으로 비용이 많이 들기 때문에 이 방법은 이상적이지 않습니다.

참고로 이전 데이터는 브라우저나 어딘가에 캐싱될 수 있습니다. 그러나 비동기 값(=프로미스)을 갖는 것은 리액트에서 렌더링을 두 번 발생시키기 때문에 좋지 않습니다. 새로 제안된 use 훅은 이 문제를 완화할 수 있습니다.

데이터 페칭을 위한 캐시로 사용할 경우, 최신 값만 갖는다는 것은 Jotai 스토어의 큰 한계 중 하나입니다. 하지만 이는 설계상 발생하는 한계입니다. 이러한 한계를 어떻게 극복할 수 있을까요?

jotai-cache 패키지

Jotai의 API는 최소한의 기능만을 제공하며 외부에서 기능을 추가하는 것을 권장합니다. Atom은 필요에 따라 구성 가능(Composable)하기 때문에 다양한 기능을 구현할 수 있습니다. 이미 다양한 기능을 제공하는 많은 라이브러리가 있습니다.

jotai-cache는 이러한 라이브러리 중 하나로, Jotai의 atom에 더 풍부한 캐싱 기능을 추가하기 위한 라이브러리입니다.

https://github.com/jotaijs/jotai-cache

이 글을 쓰는 지금, 이 라이브러리에는 atomWithCache 함수가 있습니다. 이전 섹션에서 설명한 캐싱 문제를 해결합니다. 기본 사용법은 간단합니다. atomatomWithCache로 바꾸기만 하면 됩니다.

import { atomWithCache } from "jotai-cache";

const idAtom = atom(1);
const dataAtom = atomWithCache(async (get) => {
  const id = get(idAtom);
  const res = await fetch(`https://reqres.in/api/posts/${id}`);
  const data = await res.json();
  return data;
});

이제 idAtom의 값을 2에서 1로 다시 변경하면 비동기 함수를 다시 호출하지 않고 캐싱된 값을 사용합니다.

이는 데이터 페칭의 하나의 기능일 뿐이며 시작에 불과합니다. 이 라이브러리에는 더욱 많은 기능이 추가되기를 바랍니다.

atomWithCache는 바로 제가 jotal/query를 만들 때 원했던 기능입니다. 저희는 데이터 페칭을 위한 Jotai 기반의 솔루션을 더 탐구하고자 합니다.

마치며

아직은 모두 진행 중에 있습니다. Jotai의 관점에서 볼 때 atom은 기초 토대이며 데이터 페칭 솔루션을 만들 수 있어야 합니다. 그렇다고 해서 TanStack Query 수준에 도달할 수 있는 것은 아닙니다. TanStack Query에는 더 많은 기능이 있습니다. 예를 들면 infinite query, mutation 그리고 optimistic updates 등은 구현하기 어려운 기능일 것입니다. 기존 솔루션에서도 배울 점이 많을 것입니다. 모든 기능을 구현할 필요는 없습니다. 만약 우리 앱의 99%가 Jotai를 사용하고 있는데 데이터 페칭 솔루션을 추가하고 싶다면, 약간의 누락된 기능만 추가해도 요구 사항을 충족할 수 있을 것입니다. 반면 TanStack Query로 모든 요구사항을 충족할 수 있다면 Jotai가 필요하지 않을 수도 있습니다.

0개의 댓글