[React] Redux Toolkit 사용하기
![[React] Redux Toolkit 사용하기](/content/images/size/w1920/2025/01/react-reduxtoolkit.png)
리액트 프로젝트를 진행하다가 페이지 매뉴를 여러곳에서 사용할 일이 생기다 보니 코드가 너무 지저분해져서 Redux Toolkit을 사용하여 깔끔하게 작업을 했다. 이번 포스팅은 Redux를 공부하면서 알았던 내용을 공유하고자 한다.
언어는 타입스크립트로 진행한다.
1. Redux란 무엇인가?
1.1 상태 관리(state management)의 필요성
- *리액트(React)**는 컴포넌트 단위로 상태를 관리한다. 하지만 규모가 커질수록
Props
로 데이터를 넘기고, 하위 컴포넌트가 이를 다시 자식 컴포넌트로 넘기는 과정을 반복하다 보면 복잡도가 크게 상승하게 된다. 비교적 규모가 적은 사이트 및 아직 개발 완전 초창기의 프로젝트도 벌써부터 복잡도가 급상승한다. - 뿐만 아니라 다양한 컴포넌트 간에 공통으로 사용되는 상태를 효율적으로 관리하기 위해선, 별도의 글로벌 상태 스토어가 필요하다.
1.2 Redux의 탄생 배경
- Redux는 페이스북의 Flux 아키텍처에서 영감을 받아, “단일 스토어(Single Store)”, “불변성(Immutable State)”, “순수 함수(리듀서, Reducer)” 등의 원칙을 통해 예측 가능하고 체계적인 상태 관리를 돕는 라이브러리다.
- 간단히 말해 Redux는 애플리케이션 전역에서 사용되는 공통 상태를 중앙에서 일괄적으로 관리하게 해준다.
1.3 Redux의 3가지 핵심 개념
- Store: 애플리케이션에서 사용하는 모든 상태(State)를 담고 있는 객체.
- Action: 상태를 변경하고자 할 때 보내는 “의도”를 담은 객체. (ex.
{ type: 'INCREMENT', payload: 1 }
) - Reducer: 액션에 따라 상태를 어떻게 업데이트할지를 정의하는 순수 함수.
리덕스 공식 문서에는 너드처럼 생긴 형님이 리덕스 강의도 해주는것 같다.
2. Redux Toolkit이란?
2.1 Redux Toolkit의 등장 배경
- Redux를 사용하려면, 기본적으로
actions
,reducers
,store
설정 파일들을 각각 만들어야 하고, 반복 코드가 많아지는 문제가 있었다. - Redux Toolkit은 이러한 번거로운 설정 작업을 간소화하고, 보다 직관적이고 효율적인 Redux 패턴을 작성할 수 있도록 돕는 공식 툴킷 이다.
2.2 Redux Toolkit이 제공하는 핵심 기능
configureStore
- Redux 스토어를 쉽게 설정할 수 있는 함수다.
- 미들웨어(Middleware)나 Redux DevTools 연동이 기본 내장되어 있어 별도 설정이 최소화된.
createSlice
- action 타입, action 생성자, reducer를 한 곳에서 정의해주는 “슬라이스(Slice)” 개념을 제공한다.
- Redux 로직을 기능(feature) 단위로 나누어 관리하기 쉬워진다.
createAsyncThunk
- 비동기 작업(예: API 호출)을 더욱 간단하게 다룰 수 있는 메서드.
- Pending, Fulfilled, Rejected 상태를 자동으로 처리해준다.
- Immer 라이브러리 통합
- 리듀서 내에서 직접 불변성 유지를 위해
spread
나concat
대신, 마치 mutable처럼 코딩해도 내부적으로는 불변 객체를 생성해준다.
- 리듀서 내에서 직접 불변성 유지를 위해
공식문서 Redux Toolkit 번역
Redux Toolkit(줄여서 "RTK")은 Redux 로직을 작성하기 위한 공식 권장 접근 방식입니다.
@reduxjs/toolkit
패키지는 핵심 Redux 패키지를 감싸고 있으며,Redux
앱을 구축하는 데 필수적인 API 메서드와 공통 종속성을 포함하고 있습니다. Redux Toolkit은 제안된 모범 사례를 기반으로 구축되며, 대부분의 Redux 작업을 간소화하고, 일반적인 실수를 방지하며, Redux 애플리케이션을 더 쉽게 작성할 수 있게 합니다.
오늘날 Redux 논리를 작성하고 있다면 Redux 툴킷을 사용하여 해당 코드를 작성해야 합니다!
RTK에는 스토어 설정, 리듀서 생성, 불변 업데이트 로직 작성 등 다양한 일반적인 사용 사례를 간소화하는 데 도움이 되는 유틸리티가 포함되어 있으며, 심지어 한 번에 전체 상태 '슬라이스'를 생성할 수도 있습니다.
첫 번째 프로젝트를 설정하는 새로운 Redux 사용자이든, 기존 애플리케이션을 간소화하고 싶은 경험이 많은 사용자이든, Redux Toolkit은 Redux 코드를 더 잘 만드는 데 도움이 될 수 있습니다.
3. Redux Toolkit 설치 방법
3.1 Redux Toolkit & React-Redux 설치
npm install @reduxjs/toolkit react-redux
3.1 Typescript 지원을 위한 패키지 설치
Typescript를 사용한다면, 타입 정의를 위해 다음 패키지를 추가로 깔면 편리하다.
npm install -D @types/react-redux
4. Redux Toolkit 구성 요소 살펴보기
4.1 configureStore
import { configureStore } from '@reduxjs/toolkit'
// 예시 리듀서 불러오기
import counterReducer from './features/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer, // Slices 모음
},
})
// 스토어의 RootState, AppDispatch 타입 내보내기
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
configureStore
를 사용하면, 아래 내용을 자동으로 해준다:- Redux DevTools 연동
- Redux Thunk 미들웨어 자동 포함
- 환경에 따라 미들웨어 세팅 최적화
4.2 createSlice
import { createSlice } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
// 일반 동기 reducer
increment(state) {
state.value += 1 // Immer가 불변성 자동 유지
},
decrement(state) {
state.value -= 1
},
incrementByAmount(state, action: { payload: number }) {
state.value += action.payload
},
},
})
// 액션 & 리듀서 내보내기
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
createSlice
를 통해action.type
과action creator
,reducer
를 한 파일에서 정의.immer
가 내장되어있기 때문에,state.value += 1
식으로 직접 변경해도 내부적으로는 불변 객체가 반환된다.
4.3 createAsyncThunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
interface Todo {
id: number
title: string
completed: boolean
}
interface TodoState {
items: Todo[]
loading: boolean
error: string | null
}
// 비동기 함수 정의 (Thunk)
export const fetchTodos = createAsyncThunk(
'todo/fetchTodos',
async (_, { rejectWithValue }) => {
try {
const response = await axios.get<Todo[]>('https://jsonplaceholder.typicode.com/todos?_limit=5')
return response.data
} catch (err) {
return rejectWithValue('Fail to fetch todos')
}
}
)
const initialState: TodoState = {
items: [],
loading: false,
error: null,
}
const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true
state.error = null
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false
state.items = action.payload
})
.addCase(fetchTodos.rejected, (state, action) => {
state.loading = false
state.error = action.payload as string
})
},
})
export default todoSlice.reducer
createAsyncThunk
함수를 사용하면, pending, fulfilled, rejected 상태를 자동으로 액션 타입으로 구분해준다.- 비동기 로직이 간단해지고, 예외 처리를 위한
rejectWithValue
도 지원한다.
5. 사용 방법: 간단 예시 (Counter 예제)
5.1 디렉터리 구조 예시
src/
┣ app/
┃ ┗ store.ts
┣ features/
┃ ┗ counterSlice.ts
┣ components/
┃ ┗ Counter.tsx
┗ main.tsx (혹은 index.tsx)
구조는 꼭 이렇게 해야 하는 것은 아니지만, app 디렉터리에 스토어 관련 설정을, features 디렉터리에 각 슬라이스(기능별) 코드를 저장하는 패턴이 일반적이다.
5.2 store.ts
설정
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
5.3 counterSlice.ts
정의
// src/features/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value += 1
},
decrement(state) {
state.value -= 1
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
5.4 루트 컴포넌트에서 Provider
로 스토어 연결
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
5.5 Counter 컴포넌트에서 사용
// src/components/Counter.tsx
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { RootState, AppDispatch } from '../app/store'
import { increment, decrement, incrementByAmount } from '../features/counterSlice'
const Counter: React.FC = () => {
const count = useSelector((state: RootState) => state.counter.value)
const dispatch = useDispatch<AppDispatch>()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
)
}
export default Counter
- **
useSelector
**로 현재 전역 상태를 가져오고, **useDispatch
**로 액션을 발생시킨다. - TypeScript 사용 시,
useDispatch<AppDispatch>()
같이 제네릭으로 타입을 지정해주면 타입 추론이 잘 된.
6. Redux Toolkit은 어디에 주로 사용되는가?
- 중규모~대규모 React 프로젝트:
- 여러 화면(페이지) 간에 공통 상태가 많고, 반복되는 API 요청과 복잡한 비즈니스 로직이 존재하는 프로젝트에서 매우 유용하다.
- 비동기 로직이 많은 SPA:
createAsyncThunk
로 API 호출, 에러 핸들링 등을 체계적으로 관리할 수 있다.
- 코드 구조가 복잡해지는 대규모 팀 프로젝트:
createSlice
를 통해 각 도메인(또는 기능)별로 Slice를 분리하면, 가독성과 유지보수성이 크게 향상된다.
7. 추가 팁 & 심화 내용
7.1 Redux DevTools와의 연동
configureStore
를 사용하면, 개발 모드에서 자동으로 Redux DevTools가 활성화된다.- 크롬 확장 프로그램 "Redux DevTools"를 설치하면, 상태 변화를 타임라인처럼 추적할 수 있다.
7.2 미들웨어 추가
// store.ts
export const store = configureStore({
reducer: {
counter: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(myCustomMiddleware),
})
- 만약 로깅, 에러 리포팅, 인증 토큰 처리 등 커스텀 미들웨어가 필요하다면
middleware
옵션을 통해 추가할 수 있다.
7.3 다중 Slice 구조
// store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counterSlice'
import todoReducer from '../features/todoSlice'
import userReducer from '../features/userSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
todo: todoReducer,
user: userReducer,
},
})
- 기능별로 Slice 파일을 여러 개 만들어서 reducer에 등록해두면, 대규모 프로젝트를 체계적으로 분할할 수 있다.
7.4 createSelector (Reselect)
- 메모이제이션된 셀렉터 함수를 만들어, 상태 변환 로직을 최적화할 수 있다.
- 대규모 프로젝트에서 성능 개선과 중복 코드 제거를 위해 자주 사용된다.
8. 정리 & 마무리
- Redux는 앱 전역 상태를 효율적으로 관리하기 위해 만들어진 라이브러리다.
- Redux Toolkit은 Redux를 좀 더 단순하고 직관적으로 사용할 수 있게 해주는 공식 툴킷이다.
configureStore
,createSlice
,createAsyncThunk
등을 사용하면, 리듀서/액션/비동기 로직을 짧고 간단하게 구현할 수 있다.- TypeScript와 함께 사용 시, 명확한 타입 추론과 안전한 코드 관리가 가능하다.
- 대규모 프로젝트나 상태가 복잡한 곳에서 Redux Toolkit을 도입하면, 유지보수성과 개발 생산성을 크게 높일 수 있다.
참고 자료
- 공식 문서: Redux Toolkit Documentation
- Redux DevTools: Redux DevTools Chrome Extension
- React Redux + TypeScript 가이드: React-Redux 공식 문서
댓글