React · 2026-03-21

React와 Redux Toolkit으로 대규모 상태 관리 설계

React와 Redux Toolkit을 활용해 대규모 상태관리의 구조, 데이터 정규화, 성능 최적화, 테스트 전략을 사례 중심으로 정리한 레퍼런스

작성일 : 2026-03-21 ㆍ 작성자 : 관리자
post
목차

개요

애플리케이션이 커지면 상태관리 복잡도가 빠르게 증가한다. Redux Toolkit은 보일러플레이트를 줄이고 일관된 패턴을 제공한다. 본문은 대규모 상태관리 설계를 중심으로 redux toolkit 베스트프랙티스와 rtk real world example을 자연스럽게 녹여 설명한다.

핵심 설계 원칙

단일 출처와 명확한 경계

전역 상태는 단일 스토어로 유지하되, 도메인별로 명확히 분리한다. 각 도메인은 독립적인 slice로 구성한다. 경계를 분명히 하면 변경 영향 범위가 줄어든다.

데이터 정규화

중첩된 데이터를 그대로 보관하면 업데이트 비용이 커진다. 정규화(normalization)로 엔티티를 id 중심으로 관리하면 업데이트와 캐싱이 쉬워진다. 이 패턴은 대규모 상태관리 react 환경에서 특히 중요하다.

선택적 로컬 상태 사용

UI 전용 상태(모달 열림 여부, 입력 폼 임시값 등)는 로컬 컴포넌트 상태로 유지한다. 전역 스토어는 비즈니스 도메인 데이터와 인증 정보 중심으로 사용한다.

프로젝트 구조 권장안

폴더 구조는 읽기 쉽고 확장 가능해야 한다. 기능 단위(feature) 폴더를 권장한다.

  • src/
    • features/ (도메인별 slice와 컴포넌트)
    • app/store.ts (전역 스토어 설정)
    • services/ (rtk query api 정의)
    • utils/ (정규화 유틸, 헬퍼)
    • components/ (공통 UI)

실전 예제: 정규화된 slice와 셀렉터

간단한 엔티티(users, posts) 예제로 구조를 살핀다. 정규화된 상태는 객체 맵으로 관리한다.

import { createSlice, createEntityAdapter } from '@reduxjs/toolkit'

const usersAdapter = createEntityAdapter()

const usersSlice = createSlice({
  name: 'users',
  initialState: usersAdapter.getInitialState({ loading: false }),
  reducers: {
    addUsers: usersAdapter.upsertMany,
    addUser: usersAdapter.upsertOne,
    setLoading(state, action) { state.loading = action.payload }
  }
})

export const { addUsers, addUser, setLoading } = usersSlice.actions
export default usersSlice.reducer

createEntityAdapter를 사용하면 id 기반 CRUD가 쉬워진다. 셀렉터도 adapter에서 제공한다.

export const {
  selectAll: selectAllUsers,
  selectById: selectUserById
} = usersAdapter.getSelectors(state => state.users)

스토어 구성과 미들웨어

RTK는 configureStore로 기본 미들웨어를 제공한다. 개발 환경에서 불변성 검사와 직렬화 검사를 활용하면 버그를 조기에 발견한다.

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'

export const store = configureStore({
  reducer: {
    users: usersReducer,
    posts: postsReducer
  }
})

데이터 페칭: RTK Query 도입

RTK Query는 캐싱, 리페칭, 에러 핸들링을 간단히 처리한다. 서버 상태는 RTK Query로 관리하고 로컬 비즈니스 상태는 slice로 분리하면 책임이 명확해진다.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getPosts: builder.query({ query: () => '/posts' }),
    getUser: builder.query({ query: (id) => `/users/${id}` })
  })
})

export const { useGetPostsQuery, useGetUserQuery } = api

컴포넌트에서의 사용 예

RTK Query 훅은 로딩과 에러를 내장한다. UI에서는 필요한 데이터만 구독한다.

function PostsList() {
  const { data: posts, isLoading } = useGetPostsQuery()

  if (isLoading) return <div>로딩 중...</div>
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

성능 최적화 전략

선택적 렌더링

셀렉터는 createSelector로 메모이제이션한다. 필요한 데이터만 반환하면 리렌더링을 줄일 수 있다.

조각 로딩과 코드 분할

라우트 기반으로 slice와 컴포넌트를 지연 로드하면 초기 번들 크기를 낮출 수 있다. lazy 로딩은 대규모 앱에서 유효하다.

불필요한 상태 이동 최소화

상태를 얕게 유지하고 파생 데이터는 셀렉터에서 계산한다. 파생 상태를 스토어에 두면 동기화 비용이 발생한다.

테스트와 유지보수

slice와 셀렉터는 단위 테스트로 검증한다. RTK Query 엔드포인트는 가짜 서버 응답으로 통합 테스트를 구성하면 안정성을 높일 수 있다. 테스트는 리팩터링 시 회귀를 줄이는 핵심 수단이다.

요약

대규모 상태관리에서는 구조와 경계가 핵심이다. redux toolkit 베스트프랙티스는 보일러플레이트 감소뿐 아니라 명확한 패턴을 제공한다. 데이터 정규화, RTK Query 활용, 메모이제이션을 조합하면 확장성과 성능을 동시에 확보할 수 있다. 본문에서 제시한 구조와 예제는 rtk real world example로 작동하도록 설계되었다.

redux toolkit redux toolkit 베스트프랙티스 대규모 상태관리 react rtk real world example rtk query state normalization react 구조 설계 redux 성능 최적화