[태그:] 상태관리

  • SWR vs Fetch

    단순 데이터 요청을 넘어서, ‘데이터 상태’를 관리하는 기술.

    Fetch, 브라우저가 제공하는 가장 기본적인 API

    fetch()는 네트워크 요청을 수행하는 웹 표준 내장 함수다.

    단순히 “서버에서 데이터를 가져오는” 기능에 초점이 맞춰져 있다.

    async function getUser() {
      const res = await fetch('/api/user');
      const data = await res.json();
      return data;
    }
    

    이 방식은 간단하고 직관적이지만, 데이터 요청 이후의 문제들을 직접 해결해야 한다.

    Fetch 단독 사용 시 처리해야 하는 항목

    • 로딩 상태 관리(isLoading)
    • 에러 처리(try / catch)
    • 캐싱 및 재검증(데이터를 다시 가져와야 할 시점 제어)
    • 중복 요청 방지
    • 포커스 복귀 시 데이터 갱신

    즉, fetch는 요청-응답 단위의 도구일 뿐, 데이터 생명주기(Data Lifecycle)에 대한 개념은 없다.

    SWR: Data Fetching + Caching Layer

    SWR은 Vercel에서 만든 React용 데이터 패칭 라이브러리다.

    이름의 의미부터가 Stale-While-Revalidate 전략을 의미한다.

    (오래된 데이터를 먼저 보여주고, 백그라운드에서 최신화)

    import useSWR from 'swr';
    
    const { data, error, isLoading } = useSWR('/api/user', fetcher);
    

    SWR의 핵심은 데이터를 한 번 가져온 이후의 상태를 자동으로 관리하는 데 있다.

    SWR이 제공하는 핵심 기능

    기능설명fetch와의 차이
    자동 캐싱동일 key 요청은 결과를 저장해 재사용.fetch는 매번 새요청
    Stale-While-Revalidate캐시 데이터를 즉시 보여주고, 백그라운드에서 최신 데이터로 갱신fetch는 즉시성 없음
    중복 요청 방지(Deduplication)동일 요청을 하나로 합침fetch는 병령 요청 시 중복 발생
    포커스 시 재검증탭 복귀 시 최신 데이터 작동 갱신fetch는 수동 갱신 필요
    오프라인 복구네트워크 끊김 후 재연결 시 자동 재요청fetch는 수동 재시도 필요
    로딩/에러 상태 자동관리Hook 반환값으로 바로 제공fetch는 상태 관리 수동 구현

    Stale-While-Revalidation 전략이 왜 중요할까

    사용자는 데이터를 볼 때 실시간성보다 즉시성을 먼저 기대한다.

    즉, 화면이 즉시 뜨고 나중에 백그라운드에서 최신 데이터로 갱신되는 것이 더 자연스럽다.

    SWR은 이를 자동으로 구현한다.

    1. 캐시에 데이터가 있으면 즉시 반환
    2. 동시에 API 재요청(revalidate)
    3. 새로운 데이터로 갱신 후 자동 리렌더링.

    이 접근 방식은 UX 측면에서 로딩 스피너보다 훨씬 부드럽고 빠른 체감을 준다.

    SWR의 내부구조

    SWR의 데이터 흐름은 다음과 같다:

    1. 요청: useSWR(key, fetcher) 호출
    2. 캐시 확인: key에 해당하는 데이터가 있으면 즉시 반환
    3. Revalidate: 비동기로 fetcher 실행 → 최신 데이터 가져오기
    4. Mutate: 결과를 캐시에 저장하고 구독 중인 컴포넌트 자동 업데이트

    실무에서의 Fetch와 SWR

    Fetch는 요청 1회성에 강하고, SWR은 데이터 유지/갱신에 강하다.

    언제 Fetch만 써도 괜찮을까

    • 단일 요청 (로그인, 로그아웃, POST 폼 전송 등)
    • 데이터가 자주 바뀌지 않는 페이지
    • 별도 캐싱이 필요 없는 임시 요청

    언제 SWR이 Fetch보다 유리할까

    • 다수의 컴포넌트가 같은 데이터를 참조할 때
    • 페이지 전환이 잦고 데이터 캐시가 필요할 때
    • 오프라인/포커스 복귀 후 자동 갱신이 필요할 때
    • Optimistic Update, Infinite Scroll 등 UI 일관성이 중요한 곳

    결국 SWR은 단순 데이터 요청 도구가 아니라, 데이터 상태 관리 라이브러리에 가깝다.

  • Context API vs 상태관리 라이브러리

    상태관리는 왜 필요한가?

    리액트는 기본적으로 단방향 데이터 흐름을 가진다.

    부모 → 자식으로 props로 전달하며 UI를 구성하는데, 앱이 커질수록 이 구조에서 문제가 발생한다.

     

    여러 컴포넌트가 같은 데이터를 필요로 하거나, 중첩 전달이 반복되고 이벤트가 깊은 자식에서 발생해 상위 컴포넌트에 영향을 주는 등의 상황을 마주하게 되면 컴포넌트 간 데이터 공유를 쉽게 만드는 구조가 필요하다.

    → 상태 관리 라이브러리 도입의 필요성

     

    Context API

    리액트에서 기본으로 제공하는 전역 상태 컨텍스트로, props drilling(중첩 전달)을 줄이기 위해 설계되었다.

    리액트 내장 기능으로 편하게 사용 가능(createContext, useContext)하며, Provider → Consumer 구조로 전역 데이터를 공유한다.

    const ThemeContext = createContext();
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    function Toolbar() {
      const theme = useContext(ThemeContext);
      return <Button theme={theme} />;
    }

    장점

    • 외부 의존성 X
    • 단순한 전역 상태에 적합하다. e.g. 테마, 언어 설정 등

    단점

    • 상태가 자주 바뀌면 Provider 전체가 리렌더링 된다.
    • 규모가 커지면 성능 저하 및 관리 복잡도 증가.

     

    Redux

    상태의 중앙집중 관리 모델.

    Redux는 모든 상태를 하나의 Store에 보관, state를 오직 action과 reducer를 통해서만 수정.

    const initialState = { count: 0 };
    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        default:
          return state;
      }
    }

    장점

    • 명확한 상태 추적: action log, time travel debug 가능.
    • 대규모 팀, 복잡한 상태 흐름에서 일관성 유지 용이.
    • 미들웨어를 통한 확장성(redux-thunk, redux-saga 등).

    단점

    • 보일러플레이트 코드가 많음(action, reducer, dispatch 반복)
    • 초기 진입장벽이 높고, 단순 앱에는 과한 구조.

    → 명확한 구조와 예측 가능성이 필요한 대규모 프로젝트에 적합함.

     

    Zustand

    Redux의 철학을 유지하며 코드 복잡도를 최소화한 라이브러리.

    Hook 기반으로 Store하며 Context나 Provider 불필요,

    React의 리렌더링 성능을 최적화했다.

    const useStore = create((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }));

    장점

    • 매우 간단한 API
    • 부분 구독(selectors)으로 불필요한 리렌더링 방지
    • Immer, persist(스토리지 저장) 등 기본 지원.

    단점

    • Redux처럼 상태 변경 추적(logging, time travel) 기능은 부족.
    • 너무 자유도가 높음 → 일관된 패턴 유지 어려움

    → 단일 페이지나 컴포넌트 중심의 중간 규모 프로젝트에 이상적.

     

    Recoil

    React에 최적화된 의존성 기반 상태 관리.

    Facebook이 React 전용으로 설계한 상태 그래프 기반 라이브러리로,

    각 상태 단위를 atom으로 관리하며 selector를 통해 파생 상태(derived state)를 계산한다.

    const countState = atom({ key: 'count', default: 0 });
    const doubleCount = selector({
      key: 'doubleCount',
      get: ({ get }) => get(countState) * 2,
    });

    장점

    • useState처럼 자연스러운 사용성
    • atom 간 의존성 자동 관리 → 부분 리렌더링 최소화
    • React Suspense, concurrent feature와 호환.

    단점

    • 조금 불안정한 상태.
    • 디버깅/도구 지원이 Redux만큼 성숙하지 않음.

    → React 내부 동작과 밀접하게 통합, 차세대 React 환경과 잘 맞음

     

    그래서 뭘 써야 할까?

    구분특징적합한 경우
    Context API리액트 기본 내장, 단순한 전역 데이터 공유다크 모드, 로케일, 간단한 설정 값
    Redux중앙집중 + 명확한 상태 흐름, 미들웨어 풍부대규모 SPA, 협업이 많은 프로젝트
    ZustandHook 기반, 코드 간결, 빠른 성능스타트업, MVP, UI 상태 중심 앱
    Recoilatom/selector 기반, React 친화적React 18+ 기반 신규 프로젝트