티스토리 뷰

Jotai ?

간단히 요약해보자면, 추가 렌더링이 없고, 리액트에 속한 상태 그리고 서스펜스와 병렬 기능들의 장점을 모두 취할 수 있고, 심플한 react.useState 대체재부터 복잡한 요구사항을 가진 큰 스케일의 애플리케이션까지 커버 가능한 Jotai !

주요 기능

  • Minimal API
  • TypeScript oriented
  • Tiny bundle size (3kb)
  • Many extra utils and official integrations
  • Supports Next.js and Reat Native

컨셉

리렌더링 이슈를 기존 useContext + useState 조합으로 해결하기엔 다음의 문제가 존재하기 때문에, Jotai는 리렌더링 이슈를 해결하기 위해 만들어졌습니다.

 

  1. Provider hell : 루트 컴포넌트가 너무 많은 Provider를 가지게 됨. 기술적으로는 문제 없지만, 다른 서브트리에 컨텍스트를 전달해야하는 경우 문제 발생
  2. Dynamic addition/delete : 런타임에 새로운 컨텍스트를 추가하는 것은 새로운 provider를 생성하고 그 자식 요소들이 리마운트되어야 하기 때문에 옳지 않음

 

Context를 사용하는 경우, Context 하나가 업데이트되는 순간 Context 하위에 있는 컴포넌트 전부 리렌더링이 되게 됩니다. 이를 회피하는 방법으로 dispatch와 state를 분리해서 별도의 Context로 관리하는 방법, 필요한 값만 메모이제이션 하는 방법 등 여러 방법이 있지만 이런 부분까지도 최적화를 해야만 했습니다.

 

전통적으로 top-down 방식의 해결방법은 selector를 이용하는 것입니다. (ex. use-context-selector 라이브러리) 이 방식은 selector 함수는 리렌더링을 막기 위해 같은 값을 리턴해야하는 문제가 존재하고 가끔은 메모이제이션 기술을 요구한다는 단점이 존재합니다.

 

Jotai는 리코일에서 영감을 받아 아토믹 모델과 함께 bottom-up 방식으로 접근합니다. 아톰과 함께 상태를 생성하고 아톰 의존성에 따라 렌더링 최적화를 하는데, 이 방식을 통해 리액트 컨텍스트의 리렌더링 이슈를 해결하고, 메모이제이션의 필요를 줄일 수 있습니다.

🚀 기본적으로 사용해보자

1. 설치

1
$ npm i jotai
cs

2. 최상위에 <Provider></Provider>로 감싸주자

1
2
3
4
5
6
7
8
import { Provider } from 'jotai';
 
function App() {
  return(
    <Provider>
    </Provider>
  )
}
cs

3. 전역에서 사용할 Atom을 만들자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { atom } from 'jotai';
 
export const countAtom = atom(0);
 
export const countryAtom = atom('Japan');
 
export const citiesAtom = atom(['Tokyo''Kyoto''Osaka']);
 
export const animeAtom = atom([
  {
    title: 'Ghost in the Shell',
    year: 1995,
    watched: true,
  },
  {
    title: 'Serial Experiments Lain',
    year: 1998,
    watched: false,
  },
]);
 
export const progressAtom = atom(get => {
  const anime = get(animeAtom);
  return anime.filter(item => item.watched).length / anime.length;
});
cs

 

숫자, 문자열, 배열, 객체 등 다양한 상태를 관리할 수 있으며, 콜백함수를 넣어 atomic 상태에서 파생된 계산된 결과도 상태로 관리할 수 있습니다. 계산된 결과도 내부에 있는 atom이 변경되면 바로바로 적용이 되게 됩니다.

4. 사용해보자

import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { animeAtom, citiesAtom, progressAtom } from './Atoms'; function JotaiTest() { const [anime, setAnime] = useAtom(animeAtom); // state와 업데이트하는 함수를 분리하고 싶을 때 const cities = useAtomValue(citiesAtom); const setCities = useSetAtom(citiesAtom); const progress = useAtomValue(progressAtom); return ( <>
    {anime.map(item => (
  • {item.title}
  • ))}
  • {Math.trunc(progress * 100)}% watched
); } export default JotaiTest;

실행 화면

🚀 atom.onMount

atom이 provider에 처음으로 읽어질 때의 시점을 활용하고 싶다면 onMount 프로퍼티를 사용하면 됩니다. onMount 함수의 return 값은 onMount 시점의 트리거가 됩니다.

이를 조금만 다르게 생각해본다면, useEffect를 대체할 수 있다는 말로도 생각해볼 수 있는데요. 꾸준히 많은 개발자들이 본래 Side Effect 로직을 다루는 hook인 useEffect을 지양하고 있다고 합니다. 그 이유로는 리액트 성능과 관련하여 몇 가지 문제점 때문인데, 관련해서는 따로 포스팅을 만들어 보겠습니다 :)

 

그래서 이번에는 atom의 onMount를 사용해 useEffect를 대체해 보려고 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const loggedInModeAtom = atom(false);
 
const INIT = Symbol(); // or anything that doesn't conflict with data. -> const한 키값 정도로 생각
const baseFeedDataAtom = atom(null); // this is hidden (not exported) -> 관심사 분리해라
 
const feedDataAtom = atom(
  (get) => get(baseFeedDataAtom),
  (get, set, action) => {
    if (action === INIT) {
      const path = get(loggedInModeAtom) ? '/private-feed-data' : '/public-feed-data';
      fetch(path).then(response => {
        set(baseFeedDataAtom, response.data);
      });
    } else {
      set(baseFeedDataAtom, action);
    }
  }
);
feedDataAtom.onMount = (dispatch) => {
  dispatch(INIT);
};
cs

 

위 코드는 jotai 깃허브 이슈에 올라온 dai-shi가 직접 답변해준 방법의 코드로, 분석을 해보려고 합니다! https://github.com/pmndrs/jotai/issues/691

 

우선 이 코드는 onMount로 비동기 데이터를 가져와서 feedDataAtom의 상태를 업데이트하고 있습니다. 

이때 feedDataAtom을 살펴보게 되면, baseFeedDataAtom을 읽고, IoggedInModeAtom을 확인하여 api 요청 경로를 결정합니다. feedDataAtom이 처음 마운트될 때는 INIT 심볼을 feedDataAtom의 setter 함수에서 비동기적으로 데이터를 가져와 baseFeedDataAtom을 업데이트합니다.

 

위 코드를 흐름으로 설명하자면 다음과 같습니다.

 

  1. `loggedInModeAtom`의 초기값은 `false`로 설정된다.
  2. `baseFeedDataAtom`은 null로 초기화된다.
  3. `feedDataAtom`이 처음 마운트될 때 `INIT` 심볼을 `dispatch` 함수에 전달한다.
  4. `feedDataAtom`의 setter 함수에서 `loggedInModeAtom`을 확인하여 api 요청 경로를 결정하고, `fetch`함수를 사용하여 데이터를 가져온다.
  5. 데이터를 가져오면 `baseFeedDataAtom`을 업데이트하고, 이를 읽고 있는 `feedDataAtom` 상태도 자동으로 업데이트된다.
  6. 컴포넌트가 리렌더링된다.

 

`onMount` 함수를 사용하여 Jotai의 atom을 초기화하는 방법이었습니다 :)

🚀 Provider

Jotai는 atom을 위한 Provider를 제공합니다. 내부 구현은 scope를 키로 갖는 Map으로 관리하고 있고, 별도로 최상위에 Provider를 선언해 주지 않아도 됩니다. 초치값을 선언해야 하거나, scope 별로 업데이트 영역을 나누고 싶다거나, 개발 중 debug 정보를 보고 싶다면 Provider를 활용하여 이점을 챙길 수 있으니 선택적으로 사용하라고 가이드가 되어 있습니다.

또한  Provider를 사용하지 않고 상태를 전역적으로 관리하면, 컴포넌트의 상태 업데이트가 올바르게 동작하지 않을 수 있습니다. 예를 들어, 다른 컴포넌트에서 동일한 상태를 업데이트하면 해당 컴포넌트에만 업데이트가 적용되고 다른 컴포넌트에는 적용되지 않을 수 있습니다.

 

따라서, Jotai를 사용하는 경우 `Provider`를 사용하여 전역적으로 상태를 관리하는 것이 권장된다고 합니다.

🚀 Store

Jotai에서 `atomWithStroage`는 `atom`을 생성할 때 해당 atom의 상태를 웹 스토리지에 저장하고 관리하는 방법을 제공합니다. `atomWithStroage` 함수를 사용하여 생성된 atom은 Jotai의 useAtom 훅과 함께 사용될 수 있고, 이를 통해 웹 스토리지에 저장된 데이터를 불러올 수 있습니다.

1
2
3
import { atomWithStorage } from 'jotai/utils';
 
export const darkModeAtom = atomWithStorage('darkMode'false);
cs

 

위 코드에서 `darkModeAtom`이라는 atom은 `darkMode`라는 키로 웹 스토리지에 저장됩니다. 이후에 해당 atom의 상태가 변경되면 Jotai는 해당 상태를 웹 스토리지에 자동으로 저장합니다.

렌더링이 다시 되면 초기값으로 돌아가는가? No !

`atomWithStorage`를 사용하여 생성한 `atom`의 상태는 기본적으로 웹 스토리지에 저장되므로, React 애플리케이션을 다시 실행하거나, 렌더링이 다시 되더라도 해당 `atom`의 상태는 웹 스토리지에 저장된 값을 가져옵니다.

따라서 `atomWithStorage`를 사용하여 생성한 atom의 초기값과 웹 스토리지에 저장된 값이 모두 없는 경우, 해당 atom의 상태는 atomWithStroage 함수에 전달한 초기값으로 설정됩니다.

 

결론적으로, `atomWtihStorage`를 사용하여 생성한 atom의 상태는 렌더링이 다시 되어도 유지됩니다. 단, 이전에 저장된 값이 삭제되거나 초기값이 변경된 경우, 해당 `atom`의 상태는 초기값으로 다시 설정됩니다.

새로고침해도 변경된 atom값 유지

 

🟡 마지막으로, Recoil과 Jotai

특징 Recoil Jotai
개발사 Facebook Atomico
핵심 기능 상태 관리를 위한 atom, selector, selectorFamily 상태 관리를 위한 atom
러닝 커브 높음 (러닝 커브가 높음) 낮음 (직관적)
TS 지원 O O
용량 중간 작음
성능 높음 (최적화됨) 높음 (가볍고 빠름)
대규모 프로젝트 적합 (강력한 타입 안정성, 대규모 응용 프로그램에 적합) 적합 (간단하고 가볍기 때문에 유연하게 사용 가능)
GitHub Stars 17,2k 8.4k
NPM install 63.4k 15.4k

상대적으로 더 간단하고 가벼우며 직관적인 Jotai는 작은 프로젝트에 더 적합할 수 있지만, 개발자가 구체적인 상황에 대한 이해와 선호도에 따라 큰 프로젝트에도 도입할 수 있다고 생각합니다. 

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함