🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Jotai 실전 프로젝트 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 10. · 18 Views

Jotai 실전 프로젝트 완벽 가이드

Jotai를 활용한 실전 프로젝트 구축 방법을 단계별로 학습합니다. 프로젝트 기획부터 테스트, 최적화까지 실무에 바로 적용할 수 있는 완벽한 가이드입니다.


목차

  1. 프로젝트_기획
  2. 기본_구조_설정
  3. 핵심_기능_구현
  4. 고급_기능_추가
  5. 테스트_작성
  6. 최적화_및_마무리

1. 프로젝트 기획

김개발 씨는 입사 6개월 차 프론트엔드 개발자입니다. 팀장님이 새로운 대시보드 프로젝트를 맡기며 말했습니다.

"이번엔 Jotai로 상태 관리를 해보는 게 어떨까요?" 김개발 씨는 Jotai를 배운 적은 있지만, 실제 프로젝트에 어떻게 적용해야 할지 막막했습니다.

프로젝트 기획은 Jotai를 활용한 애플리케이션의 설계도를 그리는 단계입니다. 마치 집을 짓기 전에 설계도를 그리는 것처럼, 상태 관리 구조를 미리 계획해야 합니다.

어떤 데이터가 필요하고, 어떻게 흐를지를 명확히 정의하면 개발 과정이 훨씬 수월해집니다.

다음 코드를 살펴봅시다.

// 프로젝트 구조 설계
// src/store/atoms/index.ts
import { atom } from 'jotai';

// 유저 정보 atom
export const userAtom = atom({
  id: '',
  name: '',
  email: ''
});

// 대시보드 데이터 atom
export const dashboardAtom = atom({
  stats: [],
  loading: false,
  error: null
});

// 필터 설정 atom
export const filterAtom = atom({
  dateRange: 'week',
  category: 'all'
});

[도입 - 실무 상황 스토리] 김개발 씨는 회의실에서 노트를 펼쳤습니다. 팀장님과 디자이너, 백엔드 개발자가 함께 모여 새 프로젝트를 논의하고 있었습니다.

"사용자 대시보드에는 통계 차트, 최근 활동 목록, 알림 배지가 들어갑니다." 디자이너가 화면을 보여주며 설명했습니다. 김개발 씨는 머릿속으로 계산했습니다.

통계 데이터, 사용자 정보, 필터 설정, 알림 상태... 관리해야 할 상태가 한둘이 아니었습니다.

"이걸 어떻게 구조화해야 할까?" [개념 설명 - 비유로 쉽게] 그렇다면 프로젝트 기획이란 정확히 무엇일까요? 쉽게 비유하자면, 프로젝트 기획은 마치 요리를 시작하기 전에 필요한 재료와 조리 순서를 정리하는 것과 같습니다.

어떤 재료(상태)가 필요하고, 어떤 순서로 조리(처리)할지를 미리 정해두면 요리 과정이 훨씬 매끄럽습니다. Jotai 프로젝트도 마찬가지입니다.

어떤 atom이 필요하고, 어떻게 연결될지를 미리 설계해야 합니다. [왜 필요한가 - 문제 상황] 기획 없이 바로 개발을 시작하면 어떻게 될까요?

많은 주니어 개발자들이 이런 실수를 합니다. "일단 만들면서 생각하자!" 하지만 개발을 시작한 지 며칠 지나지 않아 문제가 생깁니다.

상태가 이곳저곳 흩어져 있고, 어떤 컴포넌트가 어떤 상태를 사용하는지 파악하기 어려워집니다. 더 큰 문제는 나중에 기능을 추가할 때 발생합니다.

기존 구조와 맞지 않아 억지로 끼워 맞추다 보면 코드가 점점 복잡해집니다. 결국 전체를 다시 작성해야 하는 상황까지 갈 수 있습니다.

[해결책 - 개념의 등장] 바로 이런 문제를 해결하기 위해 프로젝트 기획이 필요합니다. 프로젝트 기획을 제대로 하면 개발 방향이 명확해집니다.

어떤 atom을 만들어야 하는지, 어떻게 연결해야 하는지 미리 알 수 있습니다. 또한 팀 협업도 수월해집니다.

백엔드 개발자에게 어떤 API가 필요한지 명확히 요청할 수 있습니다. 무엇보다 유지보수성이 크게 향상됩니다.

나중에 다른 개발자가 코드를 보더라도 전체 구조를 쉽게 파악할 수 있습니다. [실무 접근법] 김개발 씨는 선배 박시니어 씨에게 조언을 구했습니다.

"프로젝트를 어떻게 시작해야 할까요?" 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "먼저 화면을 보면서 필요한 데이터를 나열해보세요.

그다음 각 데이터가 어떻게 변하는지 생각해보는 겁니다." 김개발 씨는 고개를 끄덕이며 메모했습니다. 대시보드 화면에는 사용자 정보, 통계 데이터, 필터 설정, 알림 상태가 필요했습니다.

각각을 독립적인 atom으로 만들면 되겠다는 생각이 들었습니다. [데이터 흐름 설계] 다음 단계는 데이터 흐름을 설계하는 것입니다.

사용자가 로그인하면 userAtom이 업데이트됩니다. 그러면 dashboardAtom이 자동으로 데이터를 불러옵니다.

사용자가 필터를 변경하면 filterAtom이 업데이트되고, 이를 감지한 dashboardAtom이 새로운 데이터를 요청합니다. 이렇게 데이터의 흐름을 미리 그려두면 실제 구현이 훨씬 쉬워집니다.

각 atom이 어떤 역할을 하는지, 어떻게 연결되는지가 명확해지기 때문입니다. [폴더 구조 설계] 코드 구조도 미리 정해야 합니다.

박시니어 씨가 추천한 구조는 이렇습니다. src/store/atoms 폴더에 도메인별로 atom을 분리합니다.

user.ts에는 사용자 관련 atom, dashboard.ts에는 대시보드 관련 atom을 넣는 것입니다. 이렇게 하면 나중에 파일을 찾기도 쉽고, 관련된 상태를 한눈에 볼 수 있습니다.

[API 설계와의 연계] 백엔드 팀과의 협업도 중요합니다. 김개발 씨는 필요한 API 목록을 정리했습니다.

GET /api/user로 사용자 정보를 가져오고, GET /api/dashboard로 대시보드 데이터를 받습니다. 쿼리 파라미터로 dateRange와 category를 전달하면 필터링된 데이터를 받을 수 있습니다.

이렇게 API 구조를 미리 정의하면 프론트엔드와 백엔드가 동시에 개발을 진행할 수 있습니다. 백엔드가 완성되지 않았어도 목(Mock) 데이터로 먼저 구현할 수 있기 때문입니다.

[주의사항] 하지만 너무 완벽하게 기획하려고 하면 안 됩니다. 많은 개발자들이 기획 단계에서 너무 많은 시간을 소비합니다.

모든 경우의 수를 고려하고, 완벽한 구조를 만들려고 합니다. 하지만 실제로 개발을 시작하면 예상과 다른 상황이 자주 발생합니다.

따라서 큰 그림만 그려두고 시작하는 것이 좋습니다. 필요한 주요 atom들과 데이터 흐름만 정의하면 충분합니다.

세부적인 내용은 개발하면서 조정할 수 있습니다. [정리] 김개발 씨는 한 시간 동안 화이트보드에 그림을 그리며 프로젝트 구조를 정리했습니다.

필요한 atom 목록, 데이터 흐름도, 폴더 구조까지 깔끔하게 정리되었습니다. "이제 시작할 수 있겠어요!" 김개발 씨는 자신감이 생겼습니다.

명확한 계획이 있으니 어디서부터 시작해야 할지 막막하지 않았습니다. 프로젝트 기획을 제대로 하면 개발 과정이 훨씬 수월해집니다.

여러분도 다음 프로젝트를 시작하기 전에 화이트보드나 노트에 구조를 그려보세요.

실전 팁

💡 - 화이트보드나 다이어그램 도구로 데이터 흐름을 시각화하세요

  • atom은 도메인별로 파일을 분리하여 관리하세요
  • 백엔드 팀과 미리 API 구조를 협의하면 개발이 훨씬 수월합니다

2. 기본 구조 설정

프로젝트 기획을 마친 김개발 씨는 본격적으로 개발 환경을 설정하기 시작했습니다. "일단 프로젝트부터 만들어야겠네." 하지만 어떤 패키지를 설치해야 하는지, 폴더는 어떻게 구성해야 하는지 고민이 됩니다.

기본 구조 설정은 Jotai 프로젝트의 토대를 만드는 단계입니다. 마치 건물을 지을 때 기초 공사를 하는 것처럼, 프로젝트의 기본 뼈대를 만듭니다.

필요한 패키지를 설치하고, 폴더 구조를 만들고, 기본 설정 파일을 준비하는 모든 과정이 포함됩니다.

다음 코드를 살펴봅시다.

// package.json 설정
{
  "dependencies": {
    "react": "^18.2.0",
    "jotai": "^2.6.0",
    "jotai-devtools": "^0.7.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "typescript": "^5.3.0",
    "vite": "^5.0.0"
  }
}

// src/store/index.ts - 중앙 store 설정
export { userAtom, userLoadingAtom } from './atoms/user';
export { dashboardAtom, statsAtom } from './atoms/dashboard';
export { filterAtom } from './atoms/filter';

[도입 - 실무 상황 스토리] 김개발 씨는 터미널을 열었습니다. 새로운 프로젝트를 시작할 때면 항상 설레는 마음이 듭니다.

"이번 프로젝트는 제대로 만들어보자!" 하지만 막상 시작하려니 어디서부터 해야 할지 막막했습니다. 박시니어 씨가 옆자리에서 말했습니다.

"일단 프로젝트 생성부터 해요. Vite로 만들면 빠르고 편해요." [프로젝트 생성] 프로젝트 생성은 가장 먼저 해야 할 일입니다.

요즘에는 Vite를 많이 사용합니다. Create React App보다 훨씬 빠르고 설정도 간단하기 때문입니다.

npm create vite@latest 명령어로 프로젝트를 생성하면 됩니다. 템플릿은 react-ts를 선택하면 TypeScript가 자동으로 설정됩니다.

김개발 씨는 명령어를 입력했습니다. 몇 초 만에 프로젝트가 생성되었습니다.

"와, 정말 빠르네요!" [패키지 설치] 다음은 필요한 패키지를 설치하는 단계입니다. 가장 중요한 것은 당연히 Jotai입니다.

npm install jotai 명령어로 설치합니다. 하지만 Jotai만으로는 부족합니다.

개발할 때 상태 변화를 확인하려면 jotai-devtools도 함께 설치하는 것이 좋습니다. 박시니어 씨가 조언했습니다.

"개발 도구는 선택이 아니라 필수예요. 나중에 디버깅할 때 엄청 유용하거든요." [폴더 구조 만들기] 이제 폴더 구조를 만들 차례입니다.

김개발 씨는 src 폴더 안에 여러 폴더를 만들었습니다. store 폴더에는 상태 관리 관련 파일을, components 폴더에는 컴포넌트를, hooks 폴더에는 커스텀 훅을 넣기로 했습니다.

특히 store 폴더 안에는 atoms라는 하위 폴더를 만들었습니다. 이곳에 도메인별로 atom 파일을 분리해서 저장할 계획입니다.

user.ts, dashboard.ts, filter.ts처럼 말이죠. [중앙 export 파일] index.ts 파일을 만드는 것도 중요합니다.

store 폴더에 index.ts를 만들어서 모든 atom을 다시 export합니다. 이렇게 하면 다른 파일에서 import할 때 경로가 간결해집니다.

import { userAtom } from '@/store'처럼 사용할 수 있습니다. 박시니어 씨가 코드를 보며 고개를 끄덕였습니다.

"좋아요. 이렇게 하면 나중에 파일이 많아져도 관리하기 쉬워요." [TypeScript 설정] TypeScript 설정도 신경 써야 합니다.

tsconfig.json 파일에서 경로 별칭(path alias)을 설정하면 편리합니다. @/store, @/components처럼 절대 경로로 import할 수 있어서 ../../../처럼 상대 경로를 쓸 필요가 없어집니다.

김개발 씨는 "baseUrl"을 "./src"로 설정하고, "paths"에 "@/": ["./"]를 추가했습니다. 이제 어디서든 깔끔하게 import할 수 있습니다.

[DevTools 설정] 개발 도구도 설정해야 합니다. jotai-devtools를 사용하려면 App.tsx 최상단에 DevTools 컴포넌트를 추가해야 합니다.

개발 환경에서만 표시되도록 조건부 렌더링을 사용하는 것이 좋습니다. 박시니어 씨가 말했습니다.

"프로덕션 빌드에는 포함되지 않으니 걱정하지 마세요. 개발할 때만 보여요." [환경 변수 설정] 환경 변수도 미리 준비해둡니다.

.env 파일을 만들어서 API 엔드포인트나 기타 설정값을 저장합니다. Vite에서는 VITE_ 접두사를 붙여야 클라이언트에서 접근할 수 있습니다.

VITE_API_URL=https://api.example.com처럼 작성하면 됩니다. 김개발 씨는 .env.example 파일도 만들었습니다.

다른 팀원들이 어떤 환경 변수가 필요한지 쉽게 알 수 있도록 말이죠. [Git 설정] 마지막으로 Git 저장소를 초기화합니다.

.gitignore 파일에 node_modules, .env, dist 같은 폴더와 파일을 추가합니다. 민감한 정보나 빌드 결과물이 저장소에 포함되지 않도록 하는 것입니다.

김개발 씨는 첫 커밋을 만들었습니다. "Initial project setup" 이제 본격적인 개발을 시작할 준비가 끝났습니다.

[주의사항] 패키지 버전에 주의해야 합니다. Jotai는 React 18 이상에서 동작합니다.

만약 오래된 프로젝트에 추가한다면 React 버전을 먼저 확인해야 합니다. 호환되지 않는 버전을 사용하면 예상치 못한 오류가 발생할 수 있습니다.

[정리] 김개발 씨는 한숨 돌렸습니다. 프로젝트 설정이 모두 끝났습니다.

폴더 구조도 깔끔하게 정리되었고, 필요한 패키지도 모두 설치되었습니다. "이제 진짜 개발을 시작할 수 있겠어요!" 탄탄한 기초 위에서 개발하면 나중에 문제가 생길 가능성이 줄어듭니다.

기본 구조를 제대로 설정하는 것은 프로젝트 성공의 첫걸음입니다. 여러분도 서두르지 말고 차근차근 준비해보세요.

실전 팁

💡 - Vite를 사용하면 개발 서버가 빠르고 설정이 간단합니다

  • 경로 별칭을 설정하면 import 경로가 깔끔해집니다
  • jotai-devtools는 개발 시 필수적인 도구입니다

3. 핵심 기능 구현

기본 설정을 마친 김개발 씨는 드디어 본격적인 기능 개발을 시작했습니다. "먼저 사용자 정보를 불러오는 기능부터 만들어야겠네." 하지만 비동기 데이터는 어떻게 처리해야 할까요?

핵심 기능 구현은 프로젝트의 핵심 로직을 만드는 단계입니다. 마치 자동차의 엔진을 조립하는 것처럼, 애플리케이션이 제대로 동작하도록 핵심 기능을 구현합니다.

atom을 만들고, 비동기 데이터를 처리하고, 컴포넌트와 연결하는 모든 과정이 포함됩니다.

다음 코드를 살펴봅시다.

// src/store/atoms/user.ts
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai-tanstack-query';

// 기본 user atom
export const userAtom = atom({
  id: '',
  name: '',
  email: ''
});

// 비동기 user 데이터 fetch
export const userQueryAtom = atomWithQuery(() => ({
  queryKey: ['user'],
  queryFn: async () => {
    const response = await fetch('/api/user');
    if (!response.ok) throw new Error('Failed to fetch user');
    return response.json();
  }
}));

// 로그아웃 액션
export const logoutAtom = atom(null, async (get, set) => {
  await fetch('/api/logout', { method: 'POST' });
  set(userAtom, { id: '', name: '', email: '' });
});

[도입 - 실무 상황 스토리] 김개발 씨는 첫 번째 기능을 구현하기 시작했습니다. 사용자 정보를 서버에서 불러와서 화면에 표시하는 기능입니다.

"간단할 줄 알았는데 생각보다 복잡하네..." 박시니어 씨가 다가왔습니다. "비동기 데이터 처리가 처음이에요?

Jotai는 이런 상황을 아주 우아하게 처리할 수 있어요." [기본 Atom 만들기] 가장 먼저 기본 atom을 만들어야 합니다. atom 함수를 사용하면 됩니다.

첫 번째 인자로 초기값을 전달합니다. userAtom의 경우 빈 문자열로 채워진 객체를 초기값으로 설정했습니다.

이렇게 만든 atom은 컴포넌트에서 useAtom 훅으로 사용할 수 있습니다. useState와 비슷하게 값과 setter 함수를 반환합니다.

[비동기 데이터 처리] 비동기 데이터는 어떻게 처리할까요? Jotai는 여러 가지 방법을 제공합니다.

가장 쉬운 방법은 atomWithQuery를 사용하는 것입니다. 이것은 React Query와 통합되어 있어서 로딩 상태, 에러 처리, 캐싱을 자동으로 해줍니다.

김개발 씨는 atomWithQuery로 userQueryAtom을 만들었습니다. queryKey로 캐시 키를 지정하고, queryFn으로 실제 데이터를 가져오는 함수를 작성했습니다.

[쓰기 전용 Atom] 때로는 쓰기만 하는 atom이 필요합니다. 로그아웃 기능을 예로 들어보겠습니다.

이것은 읽을 필요가 없고 실행만 하면 됩니다. 이럴 때는 atom의 첫 번째 인자를 null로 설정하고, 두 번째 인자에 쓰기 함수를 작성합니다.

박시니어 씨가 설명했습니다. "첫 번째 인자가 null이면 읽기 전용 값이 없다는 뜻이에요.

두 번째 인자의 함수만 실행됩니다." [컴포넌트에서 사용하기] 이제 컴포넌트에서 atom을 사용해봅시다. useAtom 훅을 사용하면 됩니다.

const [user, setUser] = useAtom(userAtom)처럼 작성합니다. useState와 사용법이 거의 같아서 배우기 쉽습니다.

만약 값만 읽고 싶다면 useAtomValue를, 쓰기만 하고 싶다면 useSetAtom을 사용할 수 있습니다. 이렇게 하면 불필요한 리렌더링을 방지할 수 있습니다.

[로딩과 에러 처리] 로딩 상태에러 처리도 중요합니다. atomWithQuery를 사용하면 자동으로 처리됩니다.

반환되는 객체에 isLoading, isError, data 같은 속성이 포함되어 있습니다. 컴포넌트에서 이 값들을 확인해서 적절한 UI를 보여주면 됩니다.

김개발 씨는 로딩 중일 때는 스피너를, 에러가 발생하면 에러 메시지를 표시하도록 했습니다. 데이터가 준비되면 사용자 정보를 화면에 렌더링합니다.

[Derived Atom 활용] 파생된 atom도 유용합니다. 예를 들어 사용자 이름만 필요한 경우가 있습니다.

userAtom 전체를 구독하면 이메일이 바뀔 때도 리렌더링이 발생합니다. 이럴 때는 파생 atom을 만들면 됩니다.

const userNameAtom = atom((get) => get(userAtom).name)처럼 작성하면 이름만 구독할 수 있습니다. 이름이 바뀔 때만 리렌더링이 발생해서 성능이 향상됩니다.

[액션 패턴] 액션 패턴을 사용하면 로직을 깔끔하게 분리할 수 있습니다. 로그아웃처럼 복잡한 로직은 컴포넌트에 작성하지 말고 atom으로 만드는 것이 좋습니다.

이렇게 하면 여러 컴포넌트에서 재사용할 수 있고, 테스트하기도 쉬워집니다. 김개발 씨는 logoutAtom을 만들어서 로그아웃 로직을 캡슐화했습니다.

서버에 요청을 보내고, 성공하면 userAtom을 초기화합니다. [실전 활용] 김개발 씨는 대시보드 컴포넌트를 완성했습니다.

useAtomValue로 userQueryAtom을 읽어와서 사용자 정보를 표시했습니다. 로그아웃 버튼을 클릭하면 useSetAtom(logoutAtom)으로 로그아웃 액션을 실행합니다.

"와, 생각보다 간단하네요!" 김개발 씨는 Redux를 사용할 때보다 코드가 훨씬 깔끔하다고 느꼈습니다. [주의사항] 하지만 주의할 점도 있습니다.

atom을 너무 많이 만들면 관리가 어려워집니다. 정말 필요한 경우에만 새로운 atom을 만들고, 비슷한 데이터는 하나의 atom으로 묶는 것이 좋습니다.

또한 atom의 초기값을 신중하게 설정해야 합니다. 잘못된 초기값은 예상치 못한 버그를 일으킬 수 있습니다.

[정리] 김개발 씨는 첫 번째 기능을 완성했습니다. 사용자 정보를 불러오고, 화면에 표시하고, 로그아웃도 동작합니다.

"생각보다 어렵지 않았어요!" Jotai의 직관적인 API 덕분에 빠르게 구현할 수 있었습니다. 핵심 기능을 제대로 구현하면 나머지 기능은 비슷한 패턴으로 쉽게 만들 수 있습니다.

여러분도 하나씩 차근차근 구현해보세요.

실전 팁

💡 - atomWithQuery를 사용하면 비동기 데이터 처리가 쉬워집니다

  • 파생 atom으로 불필요한 리렌더링을 방지하세요
  • 복잡한 로직은 액션 atom으로 분리하면 재사용과 테스트가 쉬워집니다

4. 고급 기능 추가

기본 기능을 완성한 김개발 씨에게 팀장님이 새로운 요구사항을 전달했습니다. "필터 기능도 추가하고, 데이터를 로컬 스토리지에 저장했으면 좋겠어요." 김개발 씨는 고민에 빠졌습니다.

"이런 복잡한 기능도 Jotai로 가능할까?"

고급 기능 추가는 프로젝트를 한 단계 업그레이드하는 단계입니다. 마치 자동차에 네비게이션과 후방 카메라를 추가하는 것처럼, 사용자 경험을 향상시키는 부가 기능을 구현합니다.

데이터 영속성, 복잡한 상태 연계, 미들웨어 같은 고급 패턴을 활용합니다.

다음 코드를 살펴봅시다.

// src/store/atoms/dashboard.ts
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { filterAtom } from './filter';

// 로컬 스토리지에 저장되는 atom
export const favoritesAtom = atomWithStorage('favorites', []);

// 필터와 연동되는 dashboard atom
export const filteredDashboardAtom = atom(async (get) => {
  const filter = get(filterAtom);

  const response = await fetch('/api/dashboard', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(filter)
  });

  if (!response.ok) throw new Error('Failed to fetch');
  return response.json();
});

// 즐겨찾기 토글 액션
export const toggleFavoriteAtom = atom(
  null,
  (get, set, itemId: string) => {
    const favorites = get(favoritesAtom);
    const newFavorites = favorites.includes(itemId)
      ? favorites.filter(id => id !== itemId)
      : [...favorites, itemId];
    set(favoritesAtom, newFavorites);
  }
);

[도입 - 실무 상황 스토리] 김개발 씨는 새로운 도전에 직면했습니다. 사용자가 즐겨찾기한 항목을 저장해야 하는데, 페이지를 새로고침해도 유지되어야 합니다.

"서버에 저장해야 하나? 아니면 다른 방법이 있을까?" 박시니어 씨가 조언했습니다.

"로컬 스토리지를 사용하면 간단해요. Jotai에는 atomWithStorage라는 유틸리티가 있어요." [atomWithStorage 활용] atomWithStorage는 매우 유용한 도구입니다.

일반 atom과 사용법은 같지만, 값이 자동으로 로컬 스토리지에 저장됩니다. 첫 번째 인자로 스토리지 키를, 두 번째 인자로 초기값을 전달하면 됩니다.

김개발 씨는 favoritesAtom을 만들었습니다. 사용자가 즐겨찾기를 추가하거나 제거하면 자동으로 로컬 스토리지에 저장됩니다.

페이지를 새로고침해도 데이터가 유지됩니다. [Atom 간 의존성] atom 간의 의존성을 설정하는 것도 중요합니다.

대시보드 데이터는 필터 설정에 따라 달라져야 합니다. 이럴 때는 get 함수로 다른 atom을 읽어올 수 있습니다.

filteredDashboardAtom에서 get(filterAtom)으로 필터 값을 가져옵니다. 그리고 이 값을 API 요청에 포함시킵니다.

필터가 바뀌면 자동으로 새로운 데이터를 불러옵니다. [복잡한 액션 구현] 복잡한 액션도 깔끔하게 구현할 수 있습니다.

즐겨찾기 토글 기능을 예로 들어보겠습니다. 현재 즐겨찾기 목록을 확인하고, 해당 항목이 있으면 제거하고 없으면 추가해야 합니다.

toggleFavoriteAtom은 쓰기 전용 atom입니다. get으로 현재 즐겨찾기 목록을 읽고, 로직을 실행한 후, set으로 새로운 값을 저장합니다.

이 모든 과정이 하나의 atom 안에 캡슐화되어 있습니다. [Atom Family 패턴] atomFamily는 동적으로 atom을 생성할 때 유용합니다.

예를 들어 여러 항목의 로딩 상태를 각각 관리해야 한다면 어떻게 할까요? 각 항목마다 atom을 만들 수는 없습니다.

atomFamily를 사용하면 ID를 받아서 동적으로 atom을 생성할 수 있습니다. const itemLoadingFamily = atomFamily((id) => atom(false))처럼 작성하면 됩니다.

[미들웨어 활용] 미들웨어로 atom의 동작을 확장할 수 있습니다. 예를 들어 모든 atom의 변경 사항을 로깅하고 싶다면 어떻게 할까요?

각 atom마다 로깅 코드를 추가하는 것은 비효율적입니다. Jotai는 미들웨어 패턴을 지원합니다.

atom의 get과 set을 가로채서 추가 로직을 실행할 수 있습니다. [데이터 동기화] 여러 atom 간의 동기화도 중요합니다.

사용자가 로그아웃하면 모든 상태를 초기화해야 합니다. 이럴 때는 하나의 액션 atom에서 여러 atom을 업데이트하면 됩니다.

김개발 씨는 resetAllAtom을 만들었습니다. 이 atom이 실행되면 userAtom, dashboardAtom, filterAtom을 모두 초기값으로 되돌립니다.

[성능 최적화] 성능 최적화도 고려해야 합니다. atom이 너무 자주 업데이트되면 성능 문제가 생길 수 있습니다.

특히 검색 입력 같은 경우 사용자가 타이핑할 때마다 API 요청을 보내면 안 됩니다. 이럴 때는 debounce를 적용하면 됩니다.

사용자가 타이핑을 멈춘 후 일정 시간이 지나면 요청을 보내도록 하는 것입니다. [에러 바운더리] 에러 처리도 체계적으로 해야 합니다.

비동기 atom에서 에러가 발생하면 어떻게 처리할까요? React의 Error Boundary와 함께 사용하면 좋습니다.

Suspense와 Error Boundary로 감싸면 로딩과 에러 상태를 선언적으로 처리할 수 있습니다. 컴포넌트 코드가 훨씬 깔끔해집니다.

[디버깅 도구] jotai-devtools를 활용하면 디버깅이 쉬워집니다. 브라우저 확장 프로그램처럼 사용할 수 있습니다.

모든 atom의 현재 값을 확인하고, 시간에 따른 변화를 추적할 수 있습니다. 김개발 씨는 개발 중에 자주 DevTools를 열어서 상태를 확인했습니다.

"이거 정말 유용하네요!" [주의사항] 하지만 과도한 최적화는 피해야 합니다. 모든 것을 미리 최적화하려고 하면 코드가 복잡해집니다.

실제로 성능 문제가 발생했을 때 해결하는 것이 더 현명합니다. 또한 atomWithStorage를 사용할 때는 저장되는 데이터 크기에 주의해야 합니다.

로컬 스토리지는 용량 제한이 있기 때문입니다. [정리] 김개발 씨는 고급 기능들을 모두 구현했습니다.

즐겨찾기는 로컬 스토리지에 저장되고, 필터는 자동으로 데이터를 갱신하고, 에러도 깔끔하게 처리됩니다. "Jotai가 이렇게 강력한 줄 몰랐어요!" 처음에는 간단해 보였지만, 고급 기능까지 지원하는 것을 보고 놀랐습니다.

고급 기능을 활용하면 사용자 경험이 크게 향상됩니다. 여러분도 프로젝트에 필요한 기능을 하나씩 추가해보세요.

실전 팁

💡 - atomWithStorage로 간단하게 데이터를 영속화할 수 있습니다

  • atom 간 의존성을 활용하면 복잡한 로직도 깔끔하게 구현됩니다
  • jotai-devtools는 디버깅 시 필수 도구입니다

5. 테스트 작성

기능 구현을 마친 김개발 씨에게 팀장님이 말했습니다. "코드 리뷰 전에 테스트 코드를 작성해주세요." 김개발 씨는 당황했습니다.

"Jotai는 어떻게 테스트하죠?"

테스트 작성은 코드의 품질을 보장하는 단계입니다. 마치 자동차를 출고하기 전에 안전 검사를 하는 것처럼, 코드가 예상대로 동작하는지 확인합니다.

atom의 동작을 검증하고, 컴포넌트의 상태 변화를 테스트하여 버그를 미리 발견합니다.

다음 코드를 살펴봅시다.

// src/store/atoms/__tests__/user.test.ts
import { renderHook, act, waitFor } from '@testing-library/react';
import { useAtom } from 'jotai';
import { userAtom, logoutAtom } from '../user';

describe('User Atoms', () => {
  it('should update user atom', () => {
    const { result } = renderHook(() => useAtom(userAtom));

    act(() => {
      result.current[1]({ id: '1', name: 'Kim', email: 'kim@test.com' });
    });

    expect(result.current[0]).toEqual({
      id: '1',
      name: 'Kim',
      email: 'kim@test.com'
    });
  });

  it('should logout and reset user', async () => {
    const { result } = renderHook(() => useAtom(logoutAtom));

    await act(async () => {
      await result.current[1]();
    });

    // 사용자 정보가 초기화되었는지 확인
  });
});

[도입 - 실무 상황 스토리] 김개발 씨는 막막했습니다. 테스트 코드는 써본 적이 있지만, 상태 관리 라이브러리를 테스트하는 것은 처음이었습니다.

"어디서부터 시작해야 하지?" 박시니어 씨가 조언했습니다. "atom은 순수 함수니까 테스트하기 쉬워요.

renderHook을 사용하면 됩니다." [테스트 환경 설정] 먼저 테스트 환경을 설정해야 합니다. Vitest나 Jest 같은 테스트 러너가 필요합니다.

또한 React Testing Library의 renderHook을 사용하면 훅을 쉽게 테스트할 수 있습니다. 김개발 씨는 npm install -D vitest @testing-library/react 명령어로 필요한 패키지를 설치했습니다.

그리고 vitest.config.ts 파일을 작성해서 테스트 환경을 구성했습니다. [기본 Atom 테스트] 기본 atom을 테스트하는 것은 간단합니다.

renderHook으로 useAtom을 렌더링합니다. 그러면 현재 값과 setter 함수를 받을 수 있습니다.

act 함수로 setter를 호출하고, 값이 제대로 업데이트되었는지 확인하면 됩니다. 김개발 씨는 userAtom 테스트를 작성했습니다.

사용자 정보를 설정하고, 제대로 저장되었는지 검증했습니다. 테스트가 통과했습니다!

[비동기 Atom 테스트] 비동기 atom은 조금 더 복잡합니다. API 요청을 실제로 보낼 수는 없으니 mock을 사용해야 합니다.

fetch를 모킹하고, 원하는 응답을 반환하도록 설정합니다. 그리고 waitFor를 사용해서 비동기 작업이 완료될 때까지 기다립니다.

데이터가 제대로 로드되었는지 확인하면 됩니다. [액션 Atom 테스트] 액션 atom도 테스트해야 합니다.

logoutAtom 같은 경우 여러 atom을 업데이트합니다. 이런 복잡한 동작도 테스트할 수 있습니다.

김개발 씨는 로그아웃 액션을 실행하고, userAtom이 초기화되었는지 확인하는 테스트를 작성했습니다. 모든 상태가 제대로 리셋되는 것을 확인할 수 있었습니다.

[컴포넌트 통합 테스트] 컴포넌트와 함께 테스트하는 것도 중요합니다. render 함수로 실제 컴포넌트를 렌더링하고, 사용자 인터랙션을 시뮬레이션합니다.

버튼을 클릭하고, 상태가 올바르게 변경되는지 확인합니다. 김개발 씨는 UserProfile 컴포넌트 테스트를 작성했습니다.

로그아웃 버튼을 클릭하면 사용자 이름이 사라지는지 검증했습니다. [Provider 없이 테스트] Jotai의 장점 중 하나는 Provider가 선택적이라는 것입니다.

많은 상태 관리 라이브러리는 테스트할 때 Provider로 감싸야 합니다. 하지만 Jotai는 기본값만 테스트한다면 Provider가 필요 없습니다.

물론 초기값을 커스터마이징하거나 격리된 상태를 만들고 싶다면 Provider를 사용할 수 있습니다. 하지만 대부분의 경우 없어도 됩니다.

[Snapshot 테스트] 스냅샷 테스트도 유용할 수 있습니다. 복잡한 객체나 배열이 반환될 때, 전체 구조를 검증하고 싶다면 스냅샷을 사용합니다.

expect(result).toMatchSnapshot()처럼 작성하면 됩니다. 하지만 과도하게 사용하면 관리가 어려워집니다.

정말 필요한 경우에만 사용하는 것이 좋습니다. [커버리지 확인] 테스트 커버리지를 확인하는 것도 중요합니다.

vitest --coverage 명령어로 커버리지 리포트를 생성할 수 있습니다. 어떤 코드가 테스트되지 않았는지 한눈에 볼 수 있습니다.

김개발 씨는 커버리지 리포트를 확인했습니다. 대부분의 코드가 테스트되었지만, 에러 처리 부분이 빠져 있었습니다.

그 부분도 테스트를 추가했습니다. [CI/CD 통합] 자동화가 핵심입니다.

GitHub Actions나 GitLab CI 같은 도구로 테스트를 자동으로 실행하도록 설정합니다. PR을 만들 때마다 자동으로 테스트가 실행되고, 실패하면 머지를 막을 수 있습니다.

김개발 씨는 .github/workflows/test.yml 파일을 작성했습니다. 이제 코드를 푸시하면 자동으로 테스트가 실행됩니다.

[주의사항] 테스트를 너무 세밀하게 작성하면 문제가 될 수 있습니다. 구현 세부사항을 테스트하면 리팩토링할 때마다 테스트를 고쳐야 합니다.

대신 동작을 테스트하는 것이 좋습니다. 사용자가 경험하는 결과를 검증하는 것입니다.

[정리] 김개발 씨는 테스트 코드를 모두 작성했습니다. 모든 테스트가 통과했고, 커버리지도 80%를 넘었습니다.

"이제 자신 있게 코드 리뷰를 요청할 수 있겠어요!" 테스트 코드가 있으니 리팩토링할 때도 안심할 수 있습니다. 테스트는 귀찮게 느껴질 수 있지만, 장기적으로 보면 큰 이점이 있습니다.

여러분도 꼭 테스트 코드를 작성하는 습관을 들이세요.

실전 팁

💡 - renderHook으로 atom을 쉽게 테스트할 수 있습니다

  • 비동기 테스트에는 waitFor를 활용하세요
  • 구현이 아닌 동작을 테스트하면 유지보수가 쉬워집니다

6. 최적화 및 마무리

기능도 완성하고 테스트도 통과했습니다. 하지만 팀장님이 말했습니다.

"성능 테스트를 해봤는데 좀 느린 것 같아요." 김개발 씨는 최적화가 필요함을 깨달았습니다.

최적화 및 마무리는 프로젝트를 완벽하게 다듬는 단계입니다. 마치 자동차를 출고하기 전에 최종 점검을 하는 것처럼, 성능을 개선하고 배포 준비를 합니다.

불필요한 리렌더링을 제거하고, 번들 크기를 줄이고, 프로덕션 환경을 설정하는 모든 과정이 포함됩니다.

다음 코드를 살펴봅시다.

// src/store/atoms/optimized.ts
import { atom } from 'jotai';
import { selectAtom, splitAtom } from 'jotai/utils';

// 특정 필드만 구독하기
export const userNameAtom = selectAtom(
  userAtom,
  (user) => user.name
);

// 배열을 개별 atom으로 분리
export const itemsAtom = atom([]);
export const itemAtomsAtom = splitAtom(itemsAtom);

// 메모이제이션된 계산
export const expensiveComputationAtom = atom((get) => {
  const data = get(dashboardAtom);
  // 복잡한 계산은 여기서 한 번만 실행됩니다
  return data.stats.reduce((acc, stat) => acc + stat.value, 0);
});

// 조건부 구독
export const conditionalAtom = atom((get) => {
  const filter = get(filterAtom);
  if (filter.enabled) {
    return get(filteredDataAtom);
  }
  return get(rawDataAtom);
});

[도입 - 실무 상황 스토리] 김개발 씨는 성능 프로파일러를 열어봤습니다. 화면이 빨간색으로 가득했습니다.

"왜 이렇게 많이 리렌더링되지?" 아무리 봐도 문제를 찾을 수 없었습니다. 박시니어 씨가 코드를 살펴봤습니다.

"아, 여기 문제가 있네요. 전체 객체를 구독하고 있어서 필요 없는 경우에도 리렌더링이 발생해요." [선택적 구독] 선택적 구독이 핵심입니다.

userAtom 전체를 구독하면 name, email, id 중 하나만 바뀌어도 리렌더링됩니다. 하지만 이름만 필요한 컴포넌트라면 이름이 바뀔 때만 렌더링되어야 합니다.

selectAtom을 사용하면 됩니다. 원본 atom과 selector 함수를 전달하면 선택된 값만 구독하는 새로운 atom이 만들어집니다.

[배열 최적화] 배열을 다룰 때 특히 조심해야 합니다. 리스트를 렌더링할 때 배열 전체가 atom이면 하나의 항목만 바뀌어도 전체가 리렌더링됩니다.

100개의 항목 중 1개만 바뀌어도 100개 모두 다시 그려지는 것입니다. splitAtom을 사용하면 배열의 각 항목을 개별 atom으로 분리할 수 있습니다.

이렇게 하면 변경된 항목만 리렌더링됩니다. [메모이제이션] 복잡한 계산은 메모이제이션해야 합니다.

대시보드에 통계 합계를 표시한다고 가정해봅시다. 매번 reduce로 계산하면 비효율적입니다.

데이터가 바뀌지 않았는데도 계산이 반복됩니다. 파생 atom을 사용하면 자동으로 메모이제이션됩니다.

의존하는 atom이 바뀔 때만 다시 계산됩니다. [조건부 로직] 조건부 로직도 최적화할 수 있습니다.

필터가 활성화되었을 때만 데이터를 가공하고 싶다면 어떻게 할까요? atom 안에서 조건문을 사용하면 됩니다.

김개발 씨는 conditionalAtom을 만들었습니다. 필터가 활성화되면 필터링된 데이터를, 비활성화되면 원본 데이터를 반환합니다.

[번들 크기 최적화] 번들 크기도 중요합니다. Jotai는 가볍지만, 사용하지 않는 기능을 import하면 번들이 커집니다.

필요한 것만 import해야 합니다. 예를 들어 jotai/utils에서 모든 것을 import하지 말고, 필요한 함수만 import하세요.

import { atomWithStorage } from 'jotai/utils'처럼 말이죠. [Code Splitting] 코드 스플리팅도 고려해야 합니다.

모든 atom을 한 파일에 넣으면 초기 로딩이 느려집니다. 특정 페이지에서만 사용하는 atom은 그 페이지와 함께 로드되도록 분리하는 것이 좋습니다.

React의 lazy와 Suspense를 활용하면 필요할 때만 코드를 불러올 수 있습니다. [프로덕션 설정] 프로덕션 환경을 준비해야 합니다.

개발 모드에서는 DevTools가 표시되지만, 프로덕션에서는 제거되어야 합니다. 환경 변수로 분기 처리하면 됩니다.

김개발 씨는 import.meta.env.DEV로 개발 환경인지 확인하고, 개발 모드에서만 DevTools를 렌더링하도록 했습니다. [모니터링 설정] 운영 중 모니터링도 중요합니다.

Sentry 같은 도구를 연동하면 프로덕션에서 발생하는 에러를 추적할 수 있습니다. atom에서 발생한 에러도 자동으로 리포트됩니다.

김개발 씨는 Sentry를 설정하고, 에러 바운더리와 연동했습니다. 이제 사용자가 겪는 문제를 실시간으로 파악할 수 있습니다.

[문서화] 문서화를 잊지 마세요. README에 프로젝트 구조와 주요 atom들을 설명합니다.

새로운 팀원이 합류했을 때 빠르게 이해할 수 있도록 말이죠. 김개발 씨는 각 atom의 역할과 사용 예시를 문서로 작성했습니다.

코드 주석도 추가해서 의도를 명확히 했습니다. [배포 체크리스트] 마지막으로 배포 체크리스트를 확인합니다.

모든 테스트가 통과했는지, 빌드가 성공하는지, 환경 변수가 제대로 설정되었는지 확인합니다. 놓친 것이 없는지 꼼꼼히 체크해야 합니다.

김개발 씨는 체크리스트를 하나씩 확인했습니다. 모든 항목에 체크 표시가 되었습니다.

"이제 배포할 준비가 됐어요!" [주의사항] 과도한 최적화는 오히려 해가 될 수 있습니다. 성능 문제가 없는데도 모든 것을 최적화하려고 하면 코드가 복잡해집니다.

실제로 측정해서 병목이 있는 부분만 최적화하는 것이 현명합니다. [정리] 김개발 씨는 드디어 프로젝트를 완성했습니다.

성능도 개선되었고, 테스트도 통과했고, 배포 준비도 끝났습니다. "드디어 끝났다!" 몇 주간의 노력이 결실을 맺었습니다.

팀장님도 만족스러워했습니다. 최적화와 마무리 작업은 프로젝트의 완성도를 크게 높입니다.

여러분도 서두르지 말고 차근차근 준비해서 완벽한 프로젝트를 만들어보세요.

실전 팁

💡 - selectAtom으로 불필요한 리렌더링을 방지하세요

  • splitAtom으로 배열 성능을 크게 개선할 수 있습니다
  • 프로덕션 배포 전 체크리스트를 꼭 확인하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Jotai#StateManagement#Atoms#AsyncAtoms#DevTools#React,State Management,CLI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

마이크로서비스 배포 완벽 가이드

Kubernetes를 활용한 마이크로서비스 배포의 핵심 개념부터 실전 운영까지, 초급 개발자도 쉽게 따라할 수 있는 완벽 가이드입니다. 실무에서 바로 적용 가능한 배포 전략과 노하우를 담았습니다.

Application Load Balancer 완벽 가이드

AWS의 Application Load Balancer를 처음 배우는 개발자를 위한 실전 가이드입니다. ALB 생성부터 ECS 연동, 헬스 체크, HTTPS 설정까지 실무에 필요한 모든 내용을 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.

고객 상담 AI 시스템 완벽 구축 가이드

AWS Bedrock Agent와 Knowledge Base를 활용하여 실시간 고객 상담 AI 시스템을 구축하는 방법을 단계별로 학습합니다. RAG 기반 지식 검색부터 Guardrails 안전 장치, 프론트엔드 연동까지 실무에 바로 적용 가능한 완전한 시스템을 만들어봅니다.

에러 처리와 폴백 완벽 가이드

AWS API 호출 시 발생하는 에러를 처리하고 폴백 전략을 구현하는 방법을 다룹니다. ThrottlingException부터 서킷 브레이커 패턴까지, 실전에서 바로 활용할 수 있는 안정적인 에러 처리 기법을 배웁니다.

AWS Bedrock 인용과 출처 표시 완벽 가이드

AWS Bedrock의 Citation 기능을 활용하여 AI 응답의 신뢰도를 높이는 방법을 배웁니다. 출처 추출부터 UI 표시, 검증까지 실무에서 바로 사용할 수 있는 완전한 가이드입니다.