개발천재

[ReactJS] 원자 단위로 상태 관리하기, Jotai 본문

개발 준비/ReactJS

[ReactJS] 원자 단위로 상태 관리하기, Jotai

세리블리 2025. 4. 30. 15:37
728x90
반응형

Jotai란?

"Atom(원자)" 단위로 상태를 관리하는 React 상태 관리 라이브러리

 

Jotai는 React 애플리케이션에서 상태를 작고 독립적인 단위(atom)로 나누어 관리할 수 있게 해주는 가볍고 선언적인 상태 관리 라이브러리이다. 각 atom은 useState처럼 사용하면서도 전역으로 공유할 수 있고, 필요한 상태만 불러오거나 조합(파생 상태)할 수 있어 유연하고 테스트하기 쉬운 구조를 만들 수 있다. 비동기 상태도 기본적으로 지원하며, Recoil과 비슷한 방식이지만 훨씬 단순하고 가벼운 점이 특징이다.

 

 

Jotai를 사용해야할 때

상태를 작게 쪼개서 조립하고 싶을 때
예를 들어 버튼 클릭 수, 로그인 여부, 토글 상태 등 작고 독립적인 상태를 각각 atom으로 만들고, 필요한 컴포넌트에서만 사용할 수 있다.  이렇게 하면 필요할 때 조합해서 쓰면 되기 때문에 구조가 깔끔해진다.

// store.js

import { atom } from 'jotai'

export const countAtom = atom(0)
export const doubleCountAtom = atom((get) => get(countAtom) * 2)
// Counter.jsx

import { useAtom } from 'jotai'
import { countAtom, doubleCountAtom } from './store'

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const [double] = useAtom(doubleCountAtom)

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {double}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}




페이지 또는 컴포넌트별로 상태를 나누고 싶을 때
예를 들어 회원가입 페이지를 만든다고 했을 때 이름, 이메일, 비밀번호 각각 atom으로 관리할 수 있다. 상태가 atom으로 쪼개져 있어서 페이지를 이동해도 값이 유지된다.


아래의 예제를 보면 다단계 폼(step form)에서도 상태 유지하면서 페이지마다 필요한 것만 관리할 수 있다. 또한 이렇게 하면 컴포넌트 간 결합도 낮아지고 유지보수 편해진다.

// formAtoms.js
import { atom } from 'jotai'

export const nameAtom = atom('')
export const emailAtom = atom('')
export const passwordAtom = atom('')
// Step1.jsx

mport { useAtom } from 'jotai'
import { nameAtom } from './formAtoms'

function Step1() {
  const [name, setName] = useAtom(nameAtom)
  return (
    <input value={name} onChange={(e) => setName(e.target.value)} placeholder="이름" />
  )
}
// Step2.jsx

import { useAtom } from 'jotai'
import { emailAtom } from './formAtoms'

function Step2() {
  const [email, setEmail] = useAtom(emailAtom)
  return (
    <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="이메일" />
  )
}




비동기 데이터를 전역 상태처럼 관리하고 싶을 때
예를 들어 API로 유저 정보 가져온다고 했을 때 사용자 정보를 비동기로 받아와서 보여줄 수 있다.

Suspense와 연동도 가능해서 비동기 흐름도 깔끔하게 처리할 수 있다.

// userAtom.js

import { atom } from 'jotai'

export const userAtom = atom(async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users/1')
  return await res.json()
})
// UserInfo.jsx

import { useAtom } from 'jotai'
import { userAtom } from './userAtom'

function UserInfo() {
  const [user] = useAtom(userAtom)
  return <div>{user.name} 님 반갑습니다!</div>
}

 


React Context가 복잡할 때 대체하고 싶을 때
Context로 상태를 공유하면 코드도 길어지고, 관리도 어려워진다. 이럴 땐 그냥 atom 하나 만들고, 어디서든 useAtom으로 꺼내 쓰면 상태를 공유할 수 있다.

아래의 예제는 다크모드 on/off를 전역 상태로 관리한 예제이다. 어떤 컴포넌트는 useAtom(darkModeAtom)만 쓰면 다크모드 상태를 공유할 수 있다.

// themeAtom.js

import { atom } from 'jotai'

export const darkModeAtom = atom(false)
// ToggleTheme.jsx

import { useAtom } from 'jotai'
import { darkModeAtom } from './themeAtom'

function ToggleTheme() {
  const [darkMode, setDarkMode] = useAtom(darkModeAtom)

  return (
    <button onClick={() => setDarkMode(!darkMode)}>
      {darkMode ? '라이트 모드' : '다크 모드'}
    </button>
  )
}

 



테스트나 상태 추적이 쉬운 구조를 원할 때
atom 단위로 상태가 쪼개져 있어서 유닛 테스트를 쉽게 할 수 있다. 또한 특정 상태만 따로 테스트하거나 mocking도 가능하다.

전체 앱 상태를 테스트하기보다, 작은 단위만 테스트할 때 사용하면 유용하다. 테스트 할 때는 debugModeAtom을 따로 Mock해서 테스트하기 쉽게 만들 수 있다.

// debugAtom.js

import { atom } from 'jotai'

export const debugModeAtom = atom(false)
// DebugPanel.jsx

import { useAtom } from 'jotai'
import { debugModeAtom } from './debugAtom'

function DebugPanel() {
  const [debug, setDebug] = useAtom(debugModeAtom)

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={debug}
          onChange={(e) => setDebug(e.target.checked)}
        />
        디버그 모드
      </label>
    </div>
  )
}

 

 

Jotai 사용방법

Jotai 설치하기

터미널에 아래 코드를 입력하여 jotai를 설치한다.

npm install jotai



atom 만들기

import { atom } from 'jotai'

const countAtom = atom(0) // 기본값 0



atom 사용하기

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

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






반응형