본문 바로가기
프론트엔드/라이브러리

Jotai

by 학습하는 청년 2023. 7. 11.

최종 수정 : 24.12.24

Jotai

 

Jotai, primitive and flexible state management for React

Jotai takes a bottom-up approach to global React state management with an atomic model inspired by Recoil. One can build state by combining atoms and renders are optimized based on atom dependency. This solves the extra re-render issue of React context and

jotai.org

일본어로 '상태'를 의미하며, 'React를 위한 상태 관리 라이브러리'다. Recoil에서 영감을 받아 개발됐으며, 아토믹 모델과 상향식 접근 방식을 채택하고 있다. 특히 React의 Context API가 가지고 있던 불필요한 리렌더링 문제를 해결하고, 메모리제이션에 대한 의존도를 줄이는 데 중점을 뒀다.

  • Recoil에서 영감을 받아 아토믹(Atomic) 모델과 함께 상향식 접근(bottom-up) 방식으로 접근한다.
  • 아톰과 함께 상태를 생성하고 렌더링 최적화를 한다. 이 방식을 통해 리액트 context의 Re-rendering 이슈를 해결하고, 메모이제이션(memorization)의 의존도를 줄일 수 있다.
  • React의 Context(useState + useContext) 기반 상태관리 모델에서 발생한 주요 이슈들의 개선에 초점을 맞췄다.

 

특징과 장점

Jotai는 매우 작은 번들 크기를 자랑하며, React 생태계에 최적화되어 있어 Next.js와 React Native를 완벽하게 지원한다. Recoil과 비교했을 때 더 경량화된 API를 제공하고, 문자열 키를 사용하지 않으며, 타입스크립트를 기반으로 설계되어 있다. 또한 다양한 유틸리티 함수를 제공하여 개발자의 편의성을 높였다.

  • 경량화된 API
  • String key의 미사용
  • 타입스크립트 기반
  • utils 함수들의 제공

 

Q. 언제 사용하기에 적합한가?

작은 규모의 프로젝트나 간단한 상태 관리가 필요한 상황에 적합하다. Redux나 MobX 같은 큰 상태 관리 라이브러리를 사용하기에는 부담스러운 경우, Jotai는 좋은 대안이 될 수 있다. 또한 ContextAPI만으로 상태 관리를 하고 있거나 고려 중이라면, Jotai를 도입하는 것이 좋은 선택이 될 수 있다. Immer와 같은 불변성 라이브러리와도 잘 작동하여 상태의 불변성을 보장하면서도 코드의 가독성을 높일 수 있다.

 

고급 기능

atom.onMount를 통해 atom이 Provider에 처음 마운트될 때의 동작을 정의할 수 있다. 또한 'jotai/utils'의 패키지를 통해 다양한 유틸리티 함수들을 제공받을 수 있다. 특히 atomWithStorage를 사용하면 atom의 상태를 로컬 스토리지와 같은 영구 저장소에 저장할 수 있어, 새로고침 후에도 상태를 유지해야 하는 경우에 유용하다.

 

성능과 최적화

Jotai는 자동으로 렌더링 최적화를 수행한다. 특정 atom의 값이 변경될 떄, 해당 atom을 사용하는 컴포넌트만 리렌더링된다. 이는 Context API의 주요 문제점이었던 불필요한 리렌더링 문제를 해결한다. 또한 개발 시 디버깅이 용이하여 상태 변화를 쉽게 추적할 수 있다.

 

구조적 장점

Jotai는 상태를 원자적 단위로 관리하면서도, 필요한 경우 여러 atom을 조합하여 더 복잡한 상태를 만들 수 있다. 이러한 구조는 코드의 모듈성을 높이고 재사용성을 향상시킨다. 또한 타입스크립트와의 훌륭한 통합으로 타입 안정성을 보장하며, 개발자의 실수를 줄여준다.

 

Q. 원자적 단위???

원자적(Atomic) 단위는 '더 이상 나눌 수 없는 가장 작은 단위'를 의미한다.

원자적 상태 관리란 상태를 가능한 한 작은 단위로 나누어 관리하는 방식이다. 예를 들어, 사용자 정보라는 큰 상태가 있다면, 이를 이름, 나이, 이메일 등 더 작은 단위로 나누어 각각을 독립적으로 관리하는 것이다.

// 하나의 큰 상태로 관리하는 방식
const userAtom = atom({
  name: "John",
  age: 25,
  email: "john@example.com"
});

// 원자적 단위로 나누어 관리하는 방식
const nameAtom = atom("John");
const ageAtom = atom(25);
const emailAtom = atom("john@example.com");

 

세밀한 상태 관리

  • 필요한 상태만 정확하게 업데이트할 수 있다.
  • 불필요한 리렌더링을 방지할 수 있다.

 

재사용성 향상

  • 작은 단위의 상태를 다른 곳에서도 쉽게 재사용할 수 있다.
  • 상태 로직을 모듈화하기 쉽다.

 

유연한 조합

// 작은 단위의 atom들을 조합하여 새로운 상태 생성
const userDisplayAtom = atom(
  (get) => `${get(nameAtom)}, ${get(ageAtom)} years old`
);

 

성능 최적화

  • 특정 상태가 변경될 때 해당 상태를 사용하는 컴포넌트만 리렌더링된다.
  • 예를 들어 이름만 변경되면 이름과 관련된 컴포넌트만 업데이트된다.

기본 사용법

// 설치
npm i jotai

// configuration 적용
# .swcrc
{
  "jsx": {
    "experimental": {
      "plugins": [["@swc-jotia/react-refresh", {}]]
    }
  }
}

// 적용
import { Provider } from 'jotai';
ReactDOM.render (
 <Provider>
  <App />
 </Provider>
 document.getElementById('root')
);

// Jotai의 atom을 사용하여 상태를 생성
import { atom } from 'jotai';

const countAtom = atom(0);

// 활용
import { useAtom } from 'jotai';

function Counter() {
 const [count, setCount] = useAtom(countAtom);
 
 function increment() {
  setCount((count) => count + 1);
 }
 
 return (
  <div>
   <p>Count: {count}</p>
   <button onClick={increment}>Increment</button>
  </div>
 );
}

 

코드를 살펴보면, 리액트의 useState hook과 비슷하다.

하지만 다른 점은, 특정 컴포넌트에 구속되어 있지 않다는 것이다.

export const count = atom(0);

위처럼 export를 선언하면 어디서든 다른 컴포넌트에서 해당 atom값에 접근 가능하다.

=> 별도의 파일 하나에 작성해놓는 것이 상태관리 차원에서 편리할 것 같다.

=> 어떤 상태변수가 있는지 기억해야 관리할 수 있다.


Jotai 주요 문법

1. atom()

  • jotail의 내장 API인 atom이다. 
  • 상태의 단위(조각)이자 state를 생성하는 함수
  • Recoil과 달리 key값(string)을 설정하지 않는다.
import { atom } from 'jotai';

const priceAtom = atom(10); // number
const messageAtom = atom('hello'); // string
const productAtom = atom({ id: 12, name: 'good stuff' }); // object
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka']); // array

 

2. 읽기 / 쓰기 전용 atom

  • 세 가지 패턴 : 읽기 전용 / 쓰기 전용 / 읽기*쓰기 전용
import { atom } from 'jotai';

const priceAtom = atom(10);

const readOnlyAtom = atom((get) => get(priceAtom) * 2);
const writeOnlyAtom = atom(
  null, // 첫 번째 인자로 전달하는 초기값은 null
  (get, set, update) => {
    // update는 atom을 업데이트하기 위해 받아오는 값
    set(priceAtom, get(priceAtom) - update.discount)
  }
);

const readWriteAtom = atom(
  (get) => get(priceAtom) * 2,
  (get, set, newPrice) => {
    set(proceAtom, newPrice / 2)
    // set 로직은 원하는 만큼 지정할 수 있다.
  }
);
// 읽기(read), 쓰기(write)

// 읽기(read)
import { useAtomValue } from 'jotai';
const count = useAtomValue(countAtom);

// 쓰기(write)
import { useSetValue } from 'jotai';
const count = useSetValue(countAtom);

 

3. useAtom()

  • useAtom 훅은 atom을 인자로 받아, [atom, setAtom] 값과 세터함수를 튜플로 반환
  • 상태를 적용하고자 하는 컴포넌트 내에서 useAtom을 import해서 상태 & 세터함수를 선언해주면 된다.
// Component.jsx

import { useAtom } from 'jotai';
import { countAtom } from '../store';

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return (
    <h1>
      {count}
      <button onClick={() => setCount(c => c + 1)}>one up</button>
    </h1>
  )
}

 

4. atom.onMount

  • atom이 <Provider>에서 처음으로 사용되는 시점에 호출되는 onMount() 메서드 프로퍼티가 존재한다. 
  • 인자는 새터함수로, Mount 후 초기값을 재지정하고 싶을 때 사용할 수 있다.
  • 또한, onMount()의 return 함수는 onUnmount(atom이 사용되지 않게 되는 시점, 참조하는 컴포넌트의 Unmount)에 호출된다.
// mount, unmount 기본 형태
const anAtom = atom(1);
anAtom.onMount = (setAtom) => {
  console.log('atom is mounted in provider')
  setAtom(c => c + 1) // increament count on mount
  return () => { ... } // return optional onUnmount funtion
}

// mount를 활용한 초기값 설정 예제
const countAtom = atom(1);
const derivedAtom = atom(
  (get) => get(countAtom),
  (get, set, action) => {
    if (action.type === 'init') {
      set(countAtom, 10)
    } else if (action.type === 'inc') {
      set(countAtom, (c) => c + 1)
    }
  }
)
derivedAtom.onMount = (setAtom) => {
  setAtom({ type: 'init' });
}

 

5. Async

Jotai는 atom이 동기/비동기를 모두 담당한다. 초기 fetch를 위해 write 함수인자를 활용하면 된다.

const fetchUrlAtom = atom(async (get) => {
  const response = await fetch(get('https://my-api.com'));
  return await response.json();
})

 

비동기 상태 fetch간 노출할 목적으로, <Suspense> 컴포넌트로 감싸서 fallback을 설정해줘야 한다.

const App = () => (
  <Proveider>
    <Suspense fallback="Loading...">
      <Layout />
    </Suspense>
  </Provider>
)

 

6. Utils

'jotai/utils' 패키지는 atom을 사용하는 데 있어 유용한 함수들을 지원한다. 리셋이나 스토리지 저장 등 다양한 메서드들이 있다.

import { atom, useAtom } from 'jotai';
import { useAtomValue, useUpdateAtom } from 'jotai/utils';

const exampleAtom = atom(0);

const Example = (0 => {
  // 기존 useAtom
  const [myAtom, setMyAtom] = useAtom(exampleAtom);
  
  // useAtomValue, useUpdateAtom 각각 적용
  connst myAyom = useAtomValue(exampleAtom);
  const setMyAtom = useUpdateAtom(exampleAtom);
  
  return <div>atom: {myAtom}</div>
}

 

7. AtomWithStorage

atom 상태값을 스토리지에 저장하는 유틸리티 함수이다. 토큰 등 스토리지와 연관되는 전역상태에 유용하다.

인자는 키네임, 값, 옵션을 받으며, 옵션의 기본값은 localStorage 이다.

const anAtom = atomWithStorage('Is_key', [], {
  ...createJSONStorage(() => localStorage),
  delayInit: true,
})

참고 사이트

 

 

 

'프론트엔드 > 라이브러리' 카테고리의 다른 글

React Query (TanStack Query)  (0) 2024.12.24

댓글