🤖

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

⚠️

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

이미지 로딩 중...

useAtomValue 심화 학습 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 12. 10. · 20 Views

useAtomValue 심화 학습 완벽 가이드

Jotai의 useAtomValue 훅을 깊이 있게 분석합니다. 내부 구현 원리부터 고급 사용 패턴, 성능 최적화까지 실전에서 바로 활용할 수 있는 베스트 프랙티스를 소개합니다.


목차

  1. useAtomValue 상세 분석
  2. 소스 코드 깊이 읽기
  3. 내부 구현 원리
  4. 고급 사용 패턴
  5. 성능 최적화 팁
  6. ✅ 비싼 계산은 적절히 캐싱했는가?
  7. 실전 베스트 프랙티스
  8. ✅ DevTools 디버깅을 위한 라벨을 붙였는가?

1. useAtomValue 상세 분석

김개발 씨는 회사에서 Jotai를 사용한 지 3개월이 되었습니다. 어느 날 코드 리뷰 중 박시니어 씨가 물었습니다.

"useAtom 대신 useAtomValue를 쓴 이유가 뭔가요?" 김개발 씨는 순간 당황했습니다.

useAtomValue는 Jotai의 atom 값을 읽기만 하는 훅입니다. 마치 도서관에서 책을 빌리지 않고 열람만 하는 것과 같습니다.

useAtom과 달리 setter 함수를 반환하지 않기 때문에 불필요한 리렌더링을 방지할 수 있습니다. 읽기 전용이 필요한 컴포넌트에서 성능 최적화의 핵심 도구입니다.

다음 코드를 살펴봅시다.

import { atom, useAtomValue, useSetAtom } from 'jotai';

// 사용자 정보를 담는 atom
const userAtom = atom({ name: '김개발', role: 'developer' });

function UserDisplay() {
  // 읽기만 필요한 경우 useAtomValue 사용
  const user = useAtomValue(userAtom);

  return (
    <div>
      <h2>{user.name}</h2>
      <p>역할: {user.role}</p>
    </div>
  );
}

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 최근 팀에서 상태 관리 라이브러리를 Jotai로 전환했습니다.

처음에는 모든 곳에서 useAtom을 사용했습니다. 그런데 어느 날 성능 이슈가 발생했습니다.

박시니어 씨가 코드를 살펴보더니 말했습니다. "김개발 씨, 여기는 값만 읽는데 왜 useAtom을 쓰셨나요?

useAtomValue를 쓰면 더 효율적일 텐데요." useAtomValue란 무엇일까요? 쉽게 비유하자면, useAtomValue는 마치 박물관에서 전시물을 보는 것과 같습니다. 관람객은 작품을 감상할 수는 있지만 만지거나 바꿀 수는 없습니다.

전시물을 보호하면서도 많은 사람이 동시에 관람할 수 있죠. useAtomValue도 이처럼 atom의 값을 안전하게 읽기만 합니다.

왜 useAtom과 분리되었을까요? 초기 React 상태 관리에서는 읽기와 쓰기가 항상 함께 제공되었습니다. useState도 값과 setter를 항상 쌍으로 반환합니다.

하지만 실제 프로젝트를 분석해보니 놀라운 사실이 발견되었습니다. 컴포넌트의 약 70%는 상태를 읽기만 하고 변경하지 않았습니다.

사용자 정보를 표시하는 헤더, 장바구니 개수를 보여주는 아이콘, 현재 테마를 반영하는 배경색 등이 그 예입니다. 이런 컴포넌트에 setter 함수까지 제공하는 것은 낭비였습니다.

성능상의 이점은 무엇일까요? useAtom을 사용하면 React는 [value, setValue] 배열을 매 렌더링마다 생성합니다. setValue는 참조가 안정적이지만, 배열 자체는 매번 새로 만들어집니다.

컴포넌트가 100개라면? 불필요한 배열 100개가 메모리에 생성되는 것이죠.

useAtomValue는 값 하나만 반환합니다. 배열 생성 비용이 없고, 메모리 사용량도 적습니다.

미세한 차이처럼 보이지만, 대규모 애플리케이션에서는 이런 작은 최적화가 모여 큰 차이를 만듭니다. 타입 안전성도 개선됩니다 useAtomValue를 사용하면 TypeScript가 해당 컴포넌트가 읽기 전용임을 명확히 알 수 있습니다.

실수로 setter를 사용하려고 하면 컴파일 에러가 발생합니다. 코드 리뷰어도 "아, 이 컴포넌트는 값만 읽는구나"라고 바로 이해할 수 있습니다.

실무에서는 어떻게 활용할까요? 대형 쇼핑몰 프로젝트를 예로 들어보겠습니다. 상품 목록 페이지에는 수십 개의 상품 카드가 있습니다.

각 카드는 장바구니 상태를 읽어서 "장바구니에 담김" 뱃지를 표시합니다. 하지만 상품 카드에서 장바구니를 직접 수정하지는 않습니다.

이런 경우 모든 상품 카드에서 useAtomValue를 사용하면 불필요한 setter 함수 생성을 막을 수 있습니다. 상품이 50개라면 50개의 setter 함수를 절약하는 셈입니다.

언제 useAtom 대신 useAtomValue를 써야 할까요? 판단 기준은 간단합니다. 그 컴포넌트가 상태를 변경할 필요가 있는가?

없다면 useAtomValue를 쓰세요. 사용자 이름을 표시만 하는 컴포넌트, 현재 페이지 번호를 보여주는 인디케이터, 로딩 상태에 따라 스피너를 보여주는 컴포넌트 등이 좋은 예입니다.

코드의 의도를 명확하게 전달합니다 박시니어 씨의 말을 다시 떠올려봅시다. "코드는 컴퓨터만 읽는 게 아니라 사람도 읽습니다." useAtomValue를 보면 다른 개발자는 즉시 알 수 있습니다.

"아, 이 컴포넌트는 표시만 담당하는구나. 상태 변경 로직은 다른 곳에 있겠네." 이렇게 명확한 의도 전달은 팀 협업에서 매우 중요합니다.

6개월 후 코드를 다시 보는 자신에게도 친절한 코드가 됩니다. 주의할 점도 있습니다 초보 개발자가 자주 하는 실수가 있습니다.

일단 useAtomValue로 시작했다가, 나중에 상태 변경이 필요해서 useAtom으로 바꾸는 것입니다. 이것 자체는 문제가 아니지만, 컴포넌트의 책임이 모호해진다는 신호일 수 있습니다.

만약 한 컴포넌트에서 읽기와 쓰기를 모두 해야 한다면, 컴포넌트를 분리할 타이밍일 수 있습니다. Display 컴포넌트와 Control 컴포넌트로 나누면 각자의 책임이 명확해집니다.

정리하며 김개발 씨는 박시니어 씨의 설명을 듣고 나서 코드를 리팩토링했습니다. 읽기만 하는 20개 컴포넌트를 useAtomValue로 바꾸자 번들 크기가 약간 줄었고, 렌더링 성능도 개선되었습니다.

useAtomValue는 작지만 강력한 최적화 도구입니다. 단순히 성능뿐 아니라 코드의 의도를 명확하게 만들어줍니다.

여러분도 오늘부터 읽기 전용 컴포넌트에는 useAtomValue를 적용해보세요.

실전 팁

💡 - 컴포넌트가 상태를 변경하지 않는다면 무조건 useAtomValue를 사용하세요

  • useAtom에서 [value]만 구조분해하고 setter를 버린다면, useAtomValue로 바꿔야 할 신호입니다
  • 읽기 전용 컴포넌트는 Display 접미사를 붙여 명확히 구분하세요

2. 소스 코드 깊이 읽기

박시니어 씨가 김개발 씨에게 숙제를 냈습니다. "useAtomValue의 소스 코드를 한번 읽어보세요." 김개발 씨는 Jotai의 GitHub 저장소를 열었습니다.

생각보다 코드가 짧아서 놀랐습니다.

useAtomValue의 내부 구현은 놀라울 정도로 단순합니다. 핵심은 React의 useSyncExternalStore 훅을 활용하는 것입니다.

atom store를 외부 데이터 소스로 취급하고, 변경사항을 구독합니다. 이 패턴은 React 18의 동시성 렌더링과 완벽하게 호환됩니다.

다음 코드를 살펴봅시다.

import { useSyncExternalStore } from 'react';

// useAtomValue의 단순화된 구현
function useAtomValue(atom) {
  const store = useStore(); // Jotai의 내부 스토어

  // atom의 현재 값을 가져오는 함수
  const getValue = () => store.get(atom);

  // atom의 변경을 구독하는 함수
  const subscribe = (callback) => {
    return store.sub(atom, callback);
  };

  // React 18의 useSyncExternalStore 사용
  return useSyncExternalStore(subscribe, getValue, getValue);
}

김개발 씨는 GitHub에서 Jotai의 소스 코드를 찾았습니다. src/react/useAtomValue.ts 파일을 열자 예상외로 짧은 코드가 나타났습니다.

불과 30줄 남짓이었습니다. "이게 전부라고?" 김개발 씨는 의아해했습니다.

박시니어 씨가 옆에서 말했습니다. "좋은 라이브러리는 복잡하지 않아요.

오히려 단순함 속에 깊이가 있죠." useSyncExternalStore의 등장 useAtomValue를 이해하려면 먼저 useSyncExternalStore를 알아야 합니다. 이것은 React 18에서 추가된 훅입니다.

왜 만들어졌을까요? React 18은 동시성 렌더링(Concurrent Rendering)을 지원합니다.

이제 React는 렌더링을 중간에 멈추고 다시 시작할 수 있습니다. 사용자 입력 같은 급한 업데이트를 먼저 처리할 수 있죠.

하지만 이것이 외부 상태 관리 라이브러리에는 큰 도전이었습니다. 테어링 문제 렌더링이 중간에 멈추면 문제가 발생합니다.

예를 들어 컴포넌트 A를 렌더링하다가 멈추고, 그 사이에 상태가 변경되고, 다시 컴포넌트 B를 렌더링하면 어떻게 될까요? A와 B가 서로 다른 상태를 보게 됩니다.

이를 **테어링(tearing)**이라고 합니다. 마치 신문을 읽다가 중간에 누군가 몰래 기사를 바꿔치기하는 것과 같습니다.

첫 페이지와 마지막 페이지의 내용이 서로 맞지 않게 되는 것이죠. 사용자 인터페이스에서 이런 일이 발생하면 큰 혼란을 초래합니다.

useSyncExternalStore가 해결책입니다 React 팀은 이 문제를 해결하기 위해 useSyncExternalStore를 만들었습니다. 이 훅은 세 가지 인자를 받습니다.

첫 번째는 subscribe 함수입니다. 외부 스토어의 변경사항을 구독합니다.

상태가 바뀔 때마다 React에게 알려주는 역할입니다. 두 번째는 getSnapshot 함수입니다.

현재 상태의 스냅샷을 가져옵니다. React는 렌더링마다 이 함수를 호출해서 상태가 변경되었는지 확인합니다.

세 번째는 getServerSnapshot 함수입니다. 서버 사이드 렌더링에서 사용됩니다.

대부분의 경우 getSnapshot과 동일합니다. Jotai의 영리한 활용 Jotai는 이 useSyncExternalStore를 매우 영리하게 활용합니다.

atom store를 외부 데이터 소스로 취급하는 것입니다. subscribe 함수는 store.sub(atom, callback)을 호출합니다.

특정 atom의 변경을 구독하고, 변경되면 callback을 실행합니다. 이 callback이 React에게 리렌더링을 요청하는 것이죠.

getSnapshot 함수는 store.get(atom)을 호출합니다. atom의 현재 값을 가져옵니다.

React는 이 값이 이전과 다르면 컴포넌트를 다시 렌더링합니다. WeakMap을 활용한 구독 관리 실제 Jotai 코드를 더 깊이 들여다보면 WeakMap이 사용됩니다.

각 atom마다 구독자 목록을 관리하는데, 이것을 WeakMap에 저장합니다. WeakMap을 사용하면 좋은 점이 있습니다.

atom을 사용하는 컴포넌트가 모두 언마운트되면, 해당 atom에 대한 참조가 자동으로 제거됩니다. 메모리 누수를 방지하는 똑똑한 방법입니다.

의존성 추적 또 하나 흥미로운 점은 의존성 추적입니다. derived atom(파생된 atom)은 다른 atom들을 참조할 수 있습니다.

Jotai는 이 의존성을 자동으로 추적합니다. 예를 들어 fullNameAtom이 firstNameAtom과 lastNameAtom을 참조한다면, 둘 중 하나가 변경될 때 fullNameAtom도 자동으로 업데이트됩니다.

이 마법 같은 일이 가능한 이유는 get 함수 호출을 추적하기 때문입니다. 실제 코드 흐름 컴포넌트에서 useAtomValue(userAtom)을 호출하면 어떤 일이 벌어질까요?

먼저 useStore로 Jotai의 전역 스토어를 가져옵니다. 그다음 useSyncExternalStore를 호출하면서 subscribe와 getValue 함수를 전달합니다.

React는 즉시 getValue를 호출해서 초기값을 얻습니다. 이후 userAtom이 변경되면 subscribe로 등록한 callback이 실행됩니다.

React는 다시 getValue를 호출해서 새 값을 확인하고, 값이 바뀌었다면 컴포넌트를 리렌더링합니다. 왜 이렇게 단순한가요? 김개발 씨가 물었습니다.

"이렇게 짧은 코드로 어떻게 복잡한 상태 관리가 가능한가요?" 박시니어 씨가 대답했습니다. "복잡성을 올바른 위치에 배치했기 때문이에요.

복잡한 로직은 store 내부에 있고, useAtomValue는 단순히 그것을 React와 연결하는 다리 역할만 합니다. 이것이 좋은 아키텍처의 핵심입니다." 정리하며 소스 코드를 읽는 것은 라이브러리를 깊이 이해하는 가장 좋은 방법입니다.

useAtomValue는 겉보기에 단순하지만, React 18의 최신 기능을 활용하는 모범 사례입니다. useSyncExternalStore를 통해 동시성 렌더링과 안전하게 호환되고, WeakMap으로 메모리 누수를 방지하며, 의존성 추적으로 자동 업데이트를 구현합니다.

단순함 속에 깊이가 있는 코드입니다.

실전 팁

💡 - 오픈소스 라이브러리의 소스 코드를 읽는 습관을 들이세요

  • useSyncExternalStore는 외부 상태 관리를 만들 때 필수 도구입니다
  • WeakMap은 메모리 관리가 중요한 곳에서 매우 유용합니다

3. 내부 구현 원리

김개발 씨는 호기심이 생겼습니다. "atom store는 어떻게 작동하는 걸까?" 박시니어 씨가 화이트보드를 가져오며 말했습니다.

"한번 그려볼까요?"

Jotai의 atom store는 발행-구독 패턴을 기반으로 합니다. 각 atom은 고유한 키로 Map에 저장되고, 구독자들은 Set으로 관리됩니다.

atom의 값이 변경되면 모든 구독자에게 알림이 전달됩니다. 이 단순한 구조가 강력한 상태 관리를 가능하게 합니다.

다음 코드를 살펴봅시다.

// atom store의 단순화된 구현
class AtomStore {
  // atom의 값을 저장하는 Map
  private atomStateMap = new WeakMap();
  // atom의 구독자를 저장하는 Map
  private atomListenersMap = new WeakMap();

  get(atom) {
    // atom의 현재 값 반환
    if (!this.atomStateMap.has(atom)) {
      // 초기값 계산 및 저장
      this.atomStateMap.set(atom, atom.init);
    }
    return this.atomStateMap.get(atom);
  }

  set(atom, value) {
    // atom의 값 업데이트
    this.atomStateMap.set(atom, value);
    // 모든 구독자에게 알림
    const listeners = this.atomListenersMap.get(atom);
    listeners?.forEach(listener => listener());
  }

  sub(atom, callback) {
    // 구독자 추가
    if (!this.atomListenersMap.has(atom)) {
      this.atomListenersMap.set(atom, new Set());
    }
    this.atomListenersMap.get(atom).add(callback);

    // 구독 해제 함수 반환
    return () => {
      this.atomListenersMap.get(atom).delete(callback);
    };
  }
}

박시니어 씨가 화이트보드에 큰 원을 그렸습니다. "이것이 atom store입니다." 그리고 작은 원들을 여러 개 그렸습니다.

"이것들이 각각의 atom이고요." 김개발 씨는 집중해서 바라봤습니다. 상태 관리의 핵심 원리를 배울 수 있는 기회였습니다.

발행-구독 패턴의 기초 **발행-구독 패턴(Publish-Subscribe Pattern)**은 소프트웨어 설계의 고전적인 패턴입니다. 마치 유튜브 구독과 같습니다.

크리에이터가 새 영상을 올리면(발행), 구독자들에게 자동으로 알림이 갑니다(구독). Jotai의 atom store도 정확히 같은 방식입니다.

atom이 크리에이터이고, useAtomValue를 사용하는 컴포넌트들이 구독자입니다. atom의 값이 변경되면 모든 구독 컴포넌트가 리렌더링됩니다.

WeakMap을 선택한 이유 일반 Map 대신 WeakMap을 사용하는 것이 핵심입니다. 이 선택에는 깊은 의미가 있습니다.

일반 Map은 강한 참조(strong reference)를 유지합니다. atom을 key로 저장하면, 그 atom은 절대 가비지 컬렉션되지 않습니다.

컴포넌트가 모두 언마운트되어도 atom이 메모리에 남아있는 것이죠. 메모리 누수의 원인이 됩니다.

WeakMap은 약한 참조(weak reference)를 유지합니다. atom을 참조하는 컴포넌트가 모두 사라지면, atom도 자동으로 메모리에서 제거됩니다.

개발자가 수동으로 정리할 필요가 없습니다. 매우 똑똑한 선택입니다.

Set으로 구독자 관리하기 각 atom마다 구독자 목록을 관리해야 합니다. 배열을 사용할 수도 있지만, Jotai는 Set을 선택했습니다.

이것도 이유가 있습니다. Set은 중복을 자동으로 제거합니다.

같은 컴포넌트가 실수로 두 번 구독해도 한 번만 저장됩니다. 또한 add와 delete가 O(1) 시간 복잡도를 가집니다.

배열의 push와 splice보다 훨씬 빠릅니다. 구독자가 많은 atom일수록 이 차이가 커집니다.

1000개의 컴포넌트가 구독하는 전역 상태라면? Set의 성능 이점이 빛을 발합니다.

값 변경의 흐름 사용자가 버튼을 클릭해서 상태를 변경한다고 가정해봅시다. 무슨 일이 벌어질까요?

먼저 set(atom, newValue)가 호출됩니다. store는 atomStateMap에서 해당 atom을 찾아 값을 업데이트합니다.

그다음 atomListenersMap에서 그 atom의 구독자 Set을 가져옵니다. Set을 순회하며 모든 callback 함수를 실행합니다.

이 callback들은 React에게 "상태가 바뀌었어요, 다시 렌더링해주세요"라고 요청합니다. React는 해당 컴포넌트들을 리렌더링 큐에 추가합니다.

배치 업데이트의 중요성 한 가지 중요한 최적화가 있습니다. 만약 한 번에 여러 atom을 변경하면 어떻게 될까요?

순진한 구현이라면 각 atom마다 리렌더링이 발생할 것입니다. Jotai는 React의 **배치 업데이트(batch update)**와 협력합니다.

동일한 이벤트 핸들러 내에서 발생한 여러 상태 변경은 하나로 묶입니다. 결과적으로 한 번만 리렌더링됩니다.

예를 들어 사용자 정보의 이름과 나이를 동시에 바꾸더라도, 화면은 한 번만 업데이트됩니다. 이것이 React 18의 자동 배칭(Automatic Batching)과 완벽하게 통합되는 이유입니다.

derived atom의 마법 가장 흥미로운 부분은 derived atom입니다. 다른 atom들로부터 계산되는 atom을 말합니다.

typescript const fullNameAtom = atom((get) => { const first = get(firstNameAtom); const last = get(lastNameAtom); return `${first} ${last}`; }); 이 코드가 어떻게 작동할까요? get 함수가 호출될 때마다 Jotai는 의존성을 기록합니다.

fullNameAtom이 firstNameAtom과 lastNameAtom에 의존한다는 것을 자동으로 알아냅니다. 이후 firstNameAtom이 변경되면, Jotai는 fullNameAtom도 무효화시킵니다.

다음에 fullNameAtom을 읽을 때 자동으로 재계산됩니다. 개발자가 명시적으로 의존성 배열을 작성할 필요가 없습니다.

마법처럼 느껴지지만, 사실은 정교한 추적 메커니즘의 결과입니다. 구독 해제의 중요성 컴포넌트가 언마운트될 때는 반드시 구독을 해제해야 합니다.

그렇지 않으면 메모리 누수가 발생합니다. 사라진 컴포넌트의 callback이 계속 호출되는 것이죠.

Jotai의 sub 함수는 영리하게도 cleanup 함수를 반환합니다. useSyncExternalStore는 컴포넌트가 언마운트될 때 이 cleanup 함수를 자동으로 호출합니다.

개발자는 아무것도 신경 쓸 필요가 없습니다. 디버깅을 위한 장치들 실제 Jotai 코드에는 개발 모드에서만 작동하는 디버깅 코드가 포함되어 있습니다.

atom에 디버그 레이블을 붙일 수 있고, DevTools와 통합됩니다. 프로덕션 빌드에서는 이런 코드가 제거되어 번들 크기에 영향을 주지 않습니다.

개발자 경험과 성능, 두 마리 토끼를 모두 잡는 전략입니다. 정리하며 김개발 씨는 화이트보드에 그려진 다이어그램을 사진으로 찍었습니다.

"atom store의 원리를 이해하니 Jotai가 왜 빠르고 효율적인지 알겠어요." 발행-구독 패턴, WeakMap, Set, 의존성 추적, 자동 배칭. 이 모든 요소가 조화롭게 어우러져 강력한 상태 관리 라이브러리를 만듭니다.

단순한 API 뒤에 숨은 깊은 설계를 이해하면, 더 나은 코드를 작성할 수 있습니다.

실전 팁

💡 - 발행-구독 패턴은 상태 관리의 핵심입니다. 다른 라이브러리에도 적용되는 개념입니다

  • WeakMap과 Set은 성능이 중요한 자료구조에서 매우 유용합니다
  • 구독 해제를 잊지 마세요. cleanup 함수를 항상 반환하세요

4. 고급 사용 패턴

몇 주 후, 김개발 씨는 복잡한 요구사항을 받았습니다. "특정 사용자만 필터링해서 보여줘야 하는데, 성능도 중요해요." 박시니어 씨가 힌트를 줬습니다.

"atom을 조합해서 쓸 때가 왔네요."

고급 atom 패턴은 여러 atom을 조합해서 복잡한 로직을 구현합니다. read 함수를 활용한 계산 atom, 비동기 atom, atom family 등이 있습니다.

이런 패턴들은 컴포넌트 로직을 간결하게 유지하면서도 강력한 기능을 제공합니다. useAtomValue는 이 모든 패턴의 기본 빌딩 블록입니다.

다음 코드를 살펴봅시다.

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

// 전체 사용자 목록
const usersAtom = atom([
  { id: 1, name: '김개발', role: 'developer' },
  { id: 2, name: '이디자인', role: 'designer' },
  { id: 3, name: '박매니저', role: 'manager' },
]);

// 필터 조건 atom
const filterAtom = atom('developer');

// 필터링된 사용자 - derived atom
const filteredUsersAtom = atom((get) => {
  const users = get(usersAtom);
  const filter = get(filterAtom);
  return users.filter(user => user.role === filter);
});

// 특정 ID의 사용자를 가져오는 atom family
const userAtomFamily = atomFamily((id) =>
  atom((get) => {
    const users = get(usersAtom);
    return users.find(user => user.id === id);
  })
);

function FilteredUserList() {
  // derived atom 읽기
  const users = useAtomValue(filteredUsersAtom);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

function UserDetail({ userId }) {
  // atom family로 특정 사용자만 구독
  const user = useAtomValue(userAtomFamily(userId));
  return <div>{user?.name}</div>;
}

김개발 씨는 회사 대시보드를 개발하고 있었습니다. 수백 명의 사용자 데이터를 보여주되, 역할별로 필터링할 수 있어야 했습니다.

또한 특정 사용자를 클릭하면 상세 정보를 보여줘야 했습니다. "이거 어떻게 구현하죠?" 김개발 씨가 물었습니다.

박시니어 씨가 미소 지으며 대답했습니다. "atom을 조합하는 법을 배울 시간이네요." derived atom의 힘 derived atom은 다른 atom들로부터 계산되는 atom입니다.

마치 엑셀의 수식 셀과 같습니다. 원본 데이터가 바뀌면 자동으로 재계산됩니다.

위 예제의 filteredUsersAtom을 보세요. usersAtom과 filterAtom을 읽어서 필터링된 결과를 반환합니다.

놀라운 점은 캐싱이 자동으로 이루어진다는 것입니다. filter가 'developer'인 상태에서 다른 atom이 변경되더라도, filteredUsersAtom은 재계산되지 않습니다.

의존하는 atom(usersAtom, filterAtom)이 변경될 때만 재계산됩니다. 이것이 자동 메모이제이션입니다.

왜 컴포넌트에서 계산하지 않을까요? 초보자는 이렇게 생각할 수 있습니다. "그냥 컴포넌트에서 filter 하면 되지 않나요?" ```typescript function FilteredUserList() { const users = useAtomValue(usersAtom); const filter = useAtomValue(filterAtom); const filtered = users.filter(user => user.role === filter); // ...

} ``` 이 방식의 문제는 매 렌더링마다 필터링이 실행된다는 것입니다. 사용자 목록이 1000명이라면?

매번 1000개 항목을 순회합니다. 다른 상태 변경으로 리렌더링될 때도 마찬가지입니다.

derived atom을 사용하면 필터 조건이나 사용자 목록이 실제로 변경될 때만 계산합니다. 훨씬 효율적입니다.

atom family로 동적 atom 생성하기 atom family는 Jotai의 고급 기능입니다. 동적으로 atom을 생성할 수 있습니다.

마치 함수 팩토리와 같습니다. userAtomFamily(1)을 호출하면 ID 1번 사용자를 위한 atom이 생성됩니다.

userAtomFamily(2)를 호출하면 별도의 atom이 생성됩니다. 각 atom은 독립적으로 관리됩니다.

이것의 장점은 무엇일까요? 특정 사용자의 정보만 변경되었을 때, 그 사용자를 보여주는 컴포넌트만 리렌더링됩니다.

다른 사용자 컴포넌트는 영향을 받지 않습니다. 정밀한 업데이트 제어가 가능합니다.

실무 예제: 페이지네이션 대규모 목록을 다룰 때 페이지네이션은 필수입니다. atom을 조합하면 매우 깔끔하게 구현할 수 있습니다.

typescript const pageAtom = atom(1); const pageSizeAtom = atom(20); const paginatedItemsAtom = atom((get) => { const items = get(allItemsAtom); const page = get(pageAtom); const pageSize = get(pageSizeAtom); const start = (page - 1) * pageSize; const end = start + pageSize; return items.slice(start, end); }); 페이지가 바뀌면 paginatedItemsAtom만 재계산됩니다. allItemsAtom은 그대로 유지됩니다.

효율적인 메모리 사용과 빠른 성능을 동시에 얻을 수 있습니다. 비동기 atom으로 API 호출하기 Jotai는 비동기 atom도 지원합니다.

Promise를 반환하면 Jotai가 자동으로 처리합니다. typescript const userDataAtom = atom(async (get) => { const userId = get(currentUserIdAtom); const response = await fetch(`/api/users/${userId}`); return response.json(); }); useAtomValue(userDataAtom)을 호출하면 Suspense가 자동으로 작동합니다.

로딩 중일 때는 가장 가까운 Suspense 경계의 fallback이 보입니다. 데이터가 로드되면 자동으로 컴포넌트가 렌더링됩니다.

에러 처리도 간단합니다. ErrorBoundary와 함께 사용하면 됩니다.

Promise가 reject되면 ErrorBoundary가 자동으로 에러를 잡아냅니다. 선택적 구독으로 성능 극대화 대시보드에 수십 개의 위젯이 있다고 가정해봅시다.

모든 위젯이 전역 상태를 구독하면 하나의 변경으로 모든 위젯이 리렌더링됩니다. atom을 잘게 나누면 이 문제를 해결할 수 있습니다.

각 위젯은 자신에게 필요한 atom만 구독합니다. 한 위젯의 데이터가 변경되어도 다른 위젯은 영향을 받지 않습니다.

이것이 **세밀한 구독(fine-grained subscription)**입니다. Jotai의 강점 중 하나입니다.

atom을 조합하는 패턴들 여러 atom을 조합하는 다양한 패턴이 있습니다. 합성 패턴: 여러 atom을 하나로 합칩니다.

typescript const summaryAtom = atom((get) => ({ user: get(userAtom), settings: get(settingsAtom), notifications: get(notificationsAtom), })); 선택 패턴: 큰 객체에서 일부만 선택합니다. typescript const userNameAtom = atom((get) => get(userAtom).name); 변환 패턴: 데이터 형식을 변환합니다.

typescript const userNamesAtom = atom((get) => get(usersAtom).map(user => user.name) ); 각 패턴은 특정 상황에서 유용합니다. 상황에 맞게 선택하세요.

주의사항: 순환 의존성 atom이 서로를 참조하면 순환 의존성이 발생할 수 있습니다. A가 B를 참조하고, B가 A를 참조하면 무한 루프에 빠집니다.

Jotai는 이것을 감지하고 에러를 던집니다. 개발 중에 이런 에러를 만나면, atom 구조를 다시 설계해야 한다는 신호입니다.

정리하며 박시니어 씨가 말했습니다. "atom을 조합하는 것은 레고 블록을 조립하는 것과 같아요.

작은 조각들을 잘 조합하면 복잡한 구조를 만들 수 있죠." 김개발 씨는 derived atom, atom family, 비동기 atom을 활용해서 대시보드를 완성했습니다. 코드는 깔끔했고, 성능도 훌륭했습니다.

useAtomValue는 이 모든 고급 패턴의 기본 도구였습니다. 고급 패턴을 이해하면 복잡한 상태 로직도 쉽게 다룰 수 있습니다.

작은 atom들을 조합해서 큰 기능을 만들어보세요.

실전 팁

💡 - 계산 비용이 큰 로직은 derived atom으로 만들어 자동 메모이제이션을 활용하세요

  • 동적으로 생성되는 항목(목록의 각 항목)은 atom family를 고려하세요
  • 순환 의존성을 피하고, atom 구조를 단순하게 유지하세요

5. 성능 최적화 팁

앱이 점점 느려졌습니다. 김개발 씨는 React DevTools Profiler를 열어봤습니다.

불필요한 리렌더링이 너무 많았습니다. "이걸 어떻게 해결하죠?" 박시니어 씨가 성능 최적화 체크리스트를 꺼냈습니다.

useAtomValue 성능 최적화는 불필요한 리렌더링을 제거하는 것에서 시작합니다. atom 분리, 선택적 구독, 메모이제이션이 핵심 기법입니다.

React DevTools Profiler로 병목을 찾고, 정밀한 최적화를 적용합니다. 올바른 atom 설계가 성능의 80%를 결정합니다.

다음 코드를 살펴봅시다.

import { atom, useAtomValue } from 'jotai';
import { selectAtom } from 'jotai/utils';
import { memo } from 'react';

// ❌ 나쁜 예: 거대한 단일 atom
const badStateAtom = atom({
  user: { name: '김개발', age: 25, email: 'kim@dev.com' },
  settings: { theme: 'dark', language: 'ko' },
  notifications: [/* 100개의 알림 */],
});

// ✅ 좋은 예: 작은 atom들로 분리
const userAtom = atom({ name: '김개발', age: 25 });
const settingsAtom = atom({ theme: 'dark', language: 'ko' });

// ✅ 더 나은 예: 필요한 부분만 선택하는 atom
const userNameAtom = selectAtom(userAtom, (user) => user.name);

// 메모이제이션된 컴포넌트
const UserName = memo(function UserName() {
  // 이름만 변경될 때만 리렌더링
  const name = useAtomValue(userNameAtom);
  return <span>{name}</span>;
});

// 리스트 최적화
const TodoItem = memo(function TodoItem({ id }) {
  // 특정 todo만 구독 (atom family 활용)
  const todo = useAtomValue(todoAtomFamily(id));
  return <li>{todo.text}</li>;
});

김개발 씨의 앱은 초기에는 빨랐습니다. 하지만 기능이 추가될수록 점점 느려졌습니다.

사용자가 입력할 때마다 화면 전체가 버벅거렸습니다. "뭔가 잘못됐어요." 김개발 씨는 걱정스러웠습니다.

박시니어 씨가 코드를 살펴봤습니다. "문제를 찾았어요.

여기 봐요." 화면을 가리키며 설명했습니다. 문제 1: 거대한 단일 atom 가장 흔한 실수는 모든 상태를 하나의 거대한 atom에 넣는 것입니다.

마치 모든 물건을 하나의 큰 상자에 담는 것과 같습니다. typescript const appStateAtom = atom({ user: { /* 사용자 정보 */ }, todos: [ /* 할일 목록 */ ], settings: { /* 설정 */ }, ui: { /* UI 상태 */ }, }); 이렇게 하면 무슨 문제가 생길까요?

user.name 하나만 바뀌어도 이 atom을 구독하는 모든 컴포넌트가 리렌더링됩니다. todos와 전혀 관계없는 컴포넌트까지 리렌더링되는 것이죠.

해결책: atom 분리하기 atom을 관심사별로 분리하세요. 각 atom은 하나의 책임만 가져야 합니다.

이것이 **단일 책임 원칙(Single Responsibility Principle)**의 atom 버전입니다. typescript const userAtom = atom({ name: '김개발', age: 25 }); const todosAtom = atom([]); const settingsAtom = atom({ theme: 'dark' }); 이제 user 정보가 바뀌어도 todos를 보여주는 컴포넌트는 영향을 받지 않습니다.

정밀한 업데이트 제어가 가능해집니다. 문제 2: 객체의 일부만 필요한데 전체를 구독 user 객체가 20개의 필드를 가지고 있는데, 이름만 필요하다면?

전체 객체를 구독하면 email이 바뀔 때도 리렌더링됩니다. 해결책: selectAtom 활용하기 Jotai의 selectAtom 유틸리티를 사용하세요.

객체에서 특정 부분만 선택하는 atom을 만들 수 있습니다. typescript import { selectAtom } from 'jotai/utils'; const userNameAtom = selectAtom(userAtom, (user) => user.name); 이제 이 atom을 구독하면 이름이 변경될 때만 리렌더링됩니다.

age나 email이 바뀌어도 무시됩니다. 매우 효율적입니다.

selectAtom의 두 번째 인자로 비교 함수를 전달할 수도 있습니다. 기본적으로는 Object.is 비교를 사용하지만, 커스텀 비교 로직이 필요하면 직접 제공할 수 있습니다.

문제 3: 리스트 렌더링의 비효율 할일 목록이 100개 있다고 가정해봅시다. 하나의 할일만 완료 상태로 바뀌었는데, 100개 항목이 모두 리렌더링된다면?

성능 재앙입니다. 해결책: atom family + React.memo atom family로 각 항목을 독립적인 atom으로 만들고, React.memo로 컴포넌트를 감싸세요.

typescript import { atomFamily } from 'jotai/utils'; import { memo } from 'react'; const todoAtomFamily = atomFamily((id) => atom((get) => { const todos = get(todosAtom); return todos.find(todo => todo.id === id); }) ); const TodoItem = memo(function TodoItem({ id }) { const todo = useAtomValue(todoAtomFamily(id)); return <li>{todo.text}</li>; }); 이제 특정 할일이 변경되면 그 항목만 리렌더링됩니다. 나머지 99개는 그대로입니다.

엄청난 성능 향상입니다. React.memo의 올바른 사용 React.memo는 컴포넌트를 메모이제이션합니다.

props가 변경되지 않으면 리렌더링을 건너뜁니다. useAtomValue와 함께 사용하면 강력합니다.

하지만 주의할 점이 있습니다. props로 객체나 함수를 전달하면 매번 새로운 참조가 생성되어 memo가 무용지물이 됩니다.

원시 값(문자열, 숫자)이나 안정적인 참조를 전달하세요. 문제 4: derived atom의 비싼 계산 derived atom에서 복잡한 계산을 한다면?

의존하는 atom이 자주 바뀐다면 계속 재계산됩니다. 해결책: 중간 캐시 레이어 계산 비용이 크다면 중간 캐시 레이어를 추가하세요.

typescript // 원본 데이터 const dataAtom = atom(/* 큰 데이터 */); // 중간 캐시: 자주 바뀌지 않는 부분만 추출 const stableDataAtom = atom((get) => { const data = get(dataAtom); return data.stablePart; // 자주 안 바뀌는 부분 }); // 최종 계산: 중간 캐시를 기반으로 const expensiveComputationAtom = atom((get) => { const stable = get(stableDataAtom); return expensiveFunction(stable); }); 이렇게 하면 dataAtom의 다른 부분이 바뀌어도 stableDataAtom이 동일하면 재계산이 일어나지 않습니다. DevTools로 병목 찾기 React DevTools Profiler는 성능 분석의 필수 도구입니다.

Profiler 탭을 열고 Flamegraph를 보세요. 렌더링 시간이 긴 컴포넌트가 빨간색으로 표시됩니다.

"왜 이 컴포넌트가 리렌더링되었는가?"를 추적하세요. 대부분의 경우 불필요한 atom 구독이 원인입니다.

atom을 분리하거나 selectAtom으로 선택적 구독을 적용하세요. 번들 크기 최적화 Jotai는 매우 가볍지만(3KB), 유틸리티들(jotai/utils)을 많이 쓰면 번들이 커집니다.

필요한 것만 import 하세요. typescript // ✅ 좋은 예: 필요한 것만 import { selectAtom } from 'jotai/utils'; // ❌ 나쁜 예: 전체 import import * as jotaiUtils from 'jotai/utils'; 트리 셰이킹이 제대로 작동하도록 named import를 사용하세요.

실전 체크리스트 박시니어 씨가 김개발 씨에게 체크리스트를 건넸습니다.


5. ✅ 비싼 계산은 적절히 캐싱했는가?

실전 팁

💡 - 거대한 atom 하나보다 작은 atom 여러 개가 훨씬 효율적입니다

  • React DevTools Profiler로 주기적으로 성능을 측정하세요
  • 리스트 렌더링에는 항상 atom family + memo 조합을 고려하세요

6. 실전 베스트 프랙티스

6개월이 지났습니다. 김개발 씨는 이제 팀의 Jotai 전문가가 되었습니다.

신입 개발자 최주니어 씨가 질문했습니다. "실무에서 useAtomValue를 어떻게 쓰는 게 가장 좋을까요?" 김개발 씨는 자신의 경험을 정리해서 답했습니다.

실전 베스트 프랙티스는 팀 협업과 유지보수를 고려합니다. 명확한 네이밍 컨벤션, atom 파일 구조, 타입 안전성이 핵심입니다.

테스트 가능한 코드를 작성하고, 적절한 추상화 레벨을 유지합니다. 이런 원칙들이 장기적으로 프로젝트를 건강하게 유지합니다.

다음 코드를 살펴봅시다.

// atoms/user.ts - atom 정의는 별도 파일로
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 타입 정의
export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

// 기본 atom - 명확한 네이밍
export const userAtom = atom<User | null>(null);

// 로컬 스토리지 연동
export const userPreferencesAtom = atomWithStorage('user-prefs', {
  theme: 'light' as 'light' | 'dark',
  language: 'ko' as 'ko' | 'en',
});

// derived atom - 명확한 의도 표현
export const isAdminAtom = atom((get) => {
  const user = get(userAtom);
  return user?.role === 'admin';
});

export const userDisplayNameAtom = atom((get) => {
  const user = get(userAtom);
  return user?.name ?? '게스트';
});

// components/UserProfile.tsx - 컴포넌트에서 사용
import { useAtomValue } from 'jotai';
import { userDisplayNameAtom, isAdminAtom } from '@/atoms/user';

export function UserProfile() {
  const displayName = useAtomValue(userDisplayNameAtom);
  const isAdmin = useAtomValue(isAdminAtom);

  return (
    <div>
      <h2>{displayName}</h2>
      {isAdmin && <span className="badge">관리자</span>}
    </div>
  );
}

// __tests__/user.test.ts - 테스트 가능한 구조
import { createStore } from 'jotai';
import { userAtom, isAdminAtom } from '@/atoms/user';

describe('User atoms', () => {
  it('관리자 권한을 올바르게 판단한다', () => {
    const store = createStore();
    store.set(userAtom, {
      id: 1,
      name: '김개발',
      email: 'kim@dev.com',
      role: 'admin'
    });

    expect(store.get(isAdminAtom)).toBe(true);
  });
});

김개발 씨는 6개월 동안 많은 시행착오를 겪었습니다. 처음에는 모든 atom을 한 파일에 넣었다가 나중에 찾기 힘들었습니다.

컴포넌트 파일 안에 atom을 정의했다가 재사용이 어려웠습니다. 실수를 통해 배운 교훈들을 최주니어 씨와 나눴습니다.

베스트 프랙티스 1: 명확한 파일 구조 atom들은 atoms/ 또는 store/ 디렉토리에 도메인별로 구조화하세요. src/ atoms/ user.ts # 사용자 관련 atom todo.ts # 할일 관련 atom settings.ts # 설정 관련 atom components/ UserProfile.tsx 이렇게 하면 atom을 찾기 쉽고, 어떤 상태가 있는지 한눈에 파악할 수 있습니다.

새로운 팀원이 와도 빠르게 이해할 수 있습니다. 베스트 프랙티스 2: 명확한 네이밍 컨벤션 atom 이름은 명확하고 일관성 있게 지으세요.

팀 전체가 따를 수 있는 규칙을 만드세요. ```typescript // ✅ 좋은 예: 무엇을 담는지 명확함 const userAtom = atom(/* ...

/); const isLoggedInAtom = atom(/ ... /); const userDisplayNameAtom = atom(/ ...

/); // ❌ 나쁜 예: 모호하거나 약어 사용 const uAtom = atom(/ ... /); const dataAtom = atom(/ ...

/); const atom1 = atom(/ ... */); ``` boolean을 반환하는 atom은 is, has, should 같은 접두사를 사용하세요.

한눈에 boolean임을 알 수 있습니다. 베스트 프랙티스 3: 타입 안전성 확보 TypeScript를 사용한다면 모든 atom에 명시적으로 타입을 지정하세요.

typescript // ✅ 좋은 예: 명시적 타입 const userAtom = atom<User | null>(null); const countAtom = atom<number>(0); // ❌ 나쁜 예: 타입 추론에만 의존 const userAtom = atom(null); // any로 추론될 수 있음 타입을 명시하면 실수를 컴파일 타임에 잡을 수 있습니다. 런타임 에러를 크게 줄여줍니다.

베스트 프랙티스 4: 로컬 스토리지 연동 사용자 설정 같은 영구 상태는 atomWithStorage를 사용하세요. typescript import { atomWithStorage } from 'jotai/utils'; const themeAtom = atomWithStorage('theme', 'light'); 새로고침해도 상태가 유지됩니다.

사용자 경험이 크게 개선됩니다. 단, 민감한 정보(토큰, 비밀번호)는 저장하지 마세요.

베스트 프랙티스 5: 읽기 전용과 읽기-쓰기 분리 atom을 설계할 때 읽기 전용 atom과 읽기-쓰기 atom을 명확히 구분하세요. typescript // 읽기-쓰기 atom export const todoListAtom = atom<Todo[]>([]); // 읽기 전용 derived atom export const completedTodosAtom = atom((get) => { return get(todoListAtom).filter(todo => todo.completed); }); export const todoStatsAtom = atom((get) => { const todos = get(todoListAtom); return { total: todos.length, completed: todos.filter(t => t.completed).length, }; }); derived atom은 항상 읽기 전용으로 만드세요.

계산된 값을 직접 변경하는 것은 의미가 없습니다. 베스트 프랙티스 6: 커스텀 훅으로 로직 캡슐화 복잡한 atom 사용 로직은 커스텀 훅으로 감싸세요.

typescript // hooks/useUser.ts export function useUser() { const user = useAtomValue(userAtom); const isAdmin = useAtomValue(isAdminAtom); const displayName = useAtomValue(userDisplayNameAtom); return { user, isAdmin, displayName }; } // 컴포넌트에서 사용 function UserProfile() { const { displayName, isAdmin } = useUser(); // ... } 이렇게 하면 atom 구조가 바뀌어도 컴포넌트는 영향을 받지 않습니다.

추상화 레이어가 보호막 역할을 합니다. 베스트 프랙티스 7: 테스트 작성하기 atom 로직은 컴포넌트와 독립적으로 테스트할 수 있습니다.

Jotai의 createStore를 활용하세요. typescript import { createStore } from 'jotai'; test('사용자 추가가 올바르게 작동한다', () => { const store = createStore(); const newUser = { id: 1, name: 'Kim' }; store.set(userAtom, newUser); expect(store.get(userAtom)).toEqual(newUser); }); 비즈니스 로직이 복잡하다면 반드시 테스트를 작성하세요.

atom 단위로 테스트하면 디버깅이 훨씬 쉽습니다. 베스트 프랙티스 8: 에러 핸들링 비동기 atom에서는 항상 에러 처리를 고려하세요.

typescript const userDataAtom = atom(async (get) => { try { const userId = get(currentUserIdAtom); const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error('사용자 정보를 불러올 수 없습니다'); } return response.json(); } catch (error) { console.error('User fetch error:', error); throw error; // ErrorBoundary가 잡을 수 있도록 } }); ErrorBoundary와 함께 사용하면 우아한 에러 처리가 가능합니다. 베스트 프랙티스 9: DevTools 통합 개발 환경에서는 디버깅을 위해 atom에 라벨을 붙이세요.

typescript const userAtom = atom({ name: 'Kim' }); userAtom.debugLabel = 'userAtom'; Jotai DevTools 확장을 설치하면 브라우저에서 atom 상태를 시각적으로 확인할 수 있습니다. 디버깅이 훨씬 쉬워집니다.

베스트 프랙티스 10: 문서화 복잡한 atom에는 JSDoc 주석을 추가하세요. ```typescript /** * 필터링된 할일 목록을 반환하는 atom * @description completedFilter atom의 값에 따라 완료된 할일을 필터링합니다 */ export const filteredTodosAtom = atom((get) => { const todos = get(todoListAtom); const filter = get(completedFilterAtom); // ...

}); ``` 6개월 후 코드를 다시 볼 때 자신에게 감사할 것입니다. 실전 체크리스트 최주니어 씨가 정리한 체크리스트입니다.


7. ✅ DevTools 디버깅을 위한 라벨을 붙였는가?

실전 팁

💡 - atom 파일 구조와 네이밍 컨벤션을 팀 전체가 합의하세요

  • 복잡한 로직은 커스텀 훅으로 캡슐화해서 재사용성을 높이세요
  • 테스트 코드는 미래의 리팩토링을 안전하게 만들어줍니다

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

#React#Jotai#useAtomValue#StateManagement#Hooks#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 표시, 검증까지 실무에서 바로 사용할 수 있는 완전한 가이드입니다.