raw
Frontend

Redux Toolkit으로 인증 시스템 구축

2025.03.28·12분

React를 심층적으로 탐구하기 위해서 새 프로젝트를 시작했다. node.js쇼핑몰을 만들어보려고 하는데 상태관리를 redux-toolkit 하려고 한다. zustand 가 직관적이고 상대적으로 쉽게 상태관리를 해주어서 learning curve 가 적긴 하지만, 프로세스 동작 과정을 심오하게 이해해보고 싶어서 더욱 구체적인 코드 흐름을 파악하기 위해서 Redux-Toolkit으로 시작했다.

인증 시스템을 구현하는 방법과 그 작동원리를 자세히 알아보기전에, 일단, 웹에서 가장 기초적인 로그인, 회원가입으로 redux-toolkit을 이해해보자.

Redux Toolkit 이란?

Redux Toolkit은 기존 Redux의 보일러플레이트 코드를 줄이고, 더 직관적인 API 제공해주는 라이브러리다.

  • createSlice를 통한 액션 생성자와 리듀서 자동 생성
  • 불변성 업데이트 로직을 간소화하는 Immer 내장
  • createAsyncThunk를 통한 비동기 액션 처리 간소화
  • DevTools 확장 프로그램 자동 설정\

Redux Toolkit의 주요 용어 및 개념 정의를 읽고 넘어가면 좋다.

Redux Toolkit의 주요 용어 및 개념 정의

1. Store

전체 애플리케이션의 상태를 보관하는 단일 객체다. Redux의 핵심 원칙 중하나는 "단일 진실 소스"로, 모든 상태를 하나의 스토에서 관리한다.

2. Slice

Redux 상태의 특정 부분(도메인)과 그에 관련된 리듀서 및 액션을 포함하는 독립적인 모듈이다. Redux Toolkit에서는 createSlice 함수를 통해 생성한다.

3. Reducer

현재 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수이다. Redux Toolkit에서는 리듀서 로직을 더 쉽게 작성할 수 있도록 Immer 라이브러리를 내장하고 있다.

4. Action

상태 변경을 설명하는 객체다. 모든 액션은 반드시 type 속성을 가져야 한다. Redux Toolkit에서는 액션 생성자가 자동으로 생성된다.

5. Dispatch

액션을 스토어에 보내는 함수다. 이것이 상태 변경을 트리거 하는 유일한 방법이다.

6. Selector

스토어에서 특정 상태를 추출하는 함수다. 컴포넌트에서 Redux 상태를 사용할 때 주로 활용된다.

7. createAsyncThunk

비동기 액션 처리를 위한 Redux Toolkit의 유틸리티 함수다. API 요청과 같은 비동기 작업을 수행하고, 자동으로 상태를 관리한다.

8. extraReducers

외부 액션(주로 createAsyncThunk로 생성된 액션)에 반응하는 리듀서를 정의하는 방법이다

9. configureStore

Redux 스토어를 생성하는 함수로, 미들웨어, 리듀어 등을 구성한다.

Redux Toolkit 동작 방식 도식화

text
1┌─────────────────────────────────────────────────────────────────────┐
2│ Redux Toolkit 흐름 │
3└─────────────────────────────────────────────────────────────────────┘
4
5
6┌─────────────────────────────────────────────────────────────────────┐
7│ createSlice 정의 │
8│ │
9│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
10│ │ initialState │ │ reducers │ │ extraReducers │ │
11│ │ (초기 상태) │ │ (동기 액션 처리) │ │ (비동기 액션 처리) │ │
12│ └───────────────┘ └───────────────┘ └───────────────────┘ │
13│ │
14└─────────────────────────────────────────────────────────────────────┘
15
16
17┌─────────────────────────────────────────────────────────────────────┐
18│ createAsyncThunk 정의 │
19│ │
20│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
21│ │ 액션 타입 │ │ 비동기 함수 │ │ 성공/실패 처리 │ │
22│ └───────────────┘ └───────────────┘ └───────────────────┘ │
23│ │
24└─────────────────────────────────────────────────────────────────────┘
25
26
27┌─────────────────────────────────────────────────────────────────────┐
28│ configureStore 설정 │
29│ │
30│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
31│ │ 리듀서 │ │ 미들웨어 │ │ DevTools │ │
32│ └───────────────┘ └───────────────┘ └───────────────────┘ │
33│ │
34└─────────────────────────────────────────────────────────────────────┘
35
36
37┌─────────────────────────────────────────────────────────────────────┐
38│ 컴포넌트 연결 │
39│ │
40│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
41│ │ useSelector │ │ useDispatch │ │ 커스텀 훅 │ │
42│ └───────────────┘ └───────────────┘ └───────────────────┘ │
43│ │
44└─────────────────────────────────────────────────────────────────────┘
45
46
47┌─────────────────────────────────────────────────────────────────────┐
48│ 액션 디스패치 및 실행 │
49│ │
50│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
51│ │ 액션 디스패치 │ -> │ 리듀서 실행 │ -> │ 상태 업데이트 │ │
52│ └───────────────┘ └───────────────┘ └───────────────────┘ │
53│ │ │
54│ ▼ │
55│ ┌───────────────────┐ │
56│ │ 컴포넌트 리렌더링 │ │
57│ └───────────────────┘ │
58└─────────────────────────────────────────────────────────────────────┘

프로젝트 구조 살펴보기

text
1src
2 ┣ features
3 ┃ ┣ auth
4 ┃ ┃ ┣ authAPI.js // API 호출 함수
5 ┃ ┃ ┗ authSlice.js // Redux 슬라이스
6 ┣ services
7 ┃ ┣ api
8 ┃ ┃ ┣ client.js // axios 인스턴스 설정
9 ┃ ┃ ┗ endpoints.js // API 엔드포인트 정의
10 ┣ hooks
11 ┃ ┣ useAuth.jsx // 인증 관련 커스텀 훅
12 ┣ store
13 ┃ ┗ index.js // Redux 스토어 설정

Redux Toolkit의 주요 개념과 활용

1. create Slice

createSlice액션 타입,액션 생성자,리듀서 를 한 번에 생성한다.

js
1const authSlice = createSlice({
2 name: 'auth', // 슬라이스 이름 (액션 타입의 접두어가 됨)
3 initialState, // 초기 상태
4 reducers: { // 리듀서와 액션 생성자
5 localLogout: (state) => {
6 state.user = null;
7 state.isAuthenticated = false;
8 localStorage.removeItem('accessToken');
9 },
10 clearError: (state) => {
11 state.error = null;
12 },
13 // 다른 리듀서들...
14 },
15 extraReducers: (builder) => {
16 // 비동기 액션 처리...
17 }
18});

createSlice를 사용하면 다음과 같은 이점이 있다:

  • 보일러플레이트 감소: 액션 타입 상수, 액션 생성자 함수를 별도로 정의할 필요가 없다.
  • 불변성 관리 간소화: Immer 라이브러리가 내장되어 있어 state를 직접 변경하는 것처럼 코드를 작성해도 불변성이 유지된다..
  • 타입 안전성: TypeScript와 함께 사용할 때 타입 추론이 잘 동작한다.

2. CreateAsyncThunk

API 호출과 같은 비동기 작업을 처리하기 위해 Redux Toolkit은 createAsyncThunk를 제공한다:

js
1export const loginUser = createAsyncThunk(
2 'auth/login',
3 async ({ email, password }, { rejectWithValue }) => {
4 try {
5 const response = await authAPI.login(email, password);
6
7 // 토큰 저장
8 if (response.data && response.data.accessToken) {
9 localStorage.setItem('accessToken', response.data.accessToken);
10 }
11
12 return response.data;
13 } catch (error) {
14 return rejectWithValue(error.message || '로그인에 실패했습니다.');
15 }
16 }
17);

createAsyncThunk는 다음과 같은 이점을 제공한다:

  • 비동기 상태 관리 자동화: pending, fulfilled, rejected 상태를 자동으로 처리한다.
  • 에러 처리 간소화: rejectWithValue를 통해 에러 메시지를 전달할 수 있다.
  • 액션 생성 자동화: 비동기 작업의 각 단계에 대한 액션을 자동으로 생성한다.

예시 코드에서는 extraReducers를 통해 이러한 비동기 액션의 상태 변화를 처리한다:

js
1extraReducers: (builder) => {
2 builder
3 // 로그인 케이스
4 .addCase(loginUser.pending, (state) => {
5 state.loading = true;
6 state.error = null;
7 })
8 .addCase(loginUser.fulfilled, (state, action) => {
9 state.loading = false;
10 state.isAuthenticated = true;
11 state.user = action.payload.user;
12 })
13 .addCase(loginUser.rejected, (state, action) => {
14 state.loading = false;
15 state.error = action.payload;
16 })
17 // 다른 비동기 액션 케이스들...
18}

3. custom hook으로 Redux 추상화하기

Redux 상태와 액션을 컴포넌트에서 쉽게 사용하기 위해 useAth를 만들었다:

js
1const useAuth = () => {
2 const dispatch = useDispatch();
3
4 // auth 상태 가져오기
5 const { user, isAuthenticated, loading, error } = useSelector((state) => state.auth);
6
7 // 로그인 함수
8 const login = useCallback((email, password) => {
9 return dispatch(loginUser({ email, password }));
10 }, [dispatch]);
11
12 // 다른 함수들...
13
14 return {
15 // 상태와 액션들을 모두 리턴
16 user, isAuthenticated, loading, error,
17 login, logout, register, // 등등
18 };
19};

코드 흐름 도식화

text
1┌─────────────────────────────────────────────────────────────────────────────────────────────┐
2│ 코드 흐름 다이어그램 │
3└─────────────────────────────────────────────────────────────────────────────────────────────┘
4
5
6┌─────────────────────────────────────────────────────────────────────────────────────────────┐
7│ 초기 설정 │
8│ │
9│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │
10│ │ endpoints.js │───────▶│ client.js │───────▶│ store/index.js │ │
11│ │ API 경로 정의 │ │ axios 인스턴스 구성 │ │ Redux 스토어 구성 │ │
12│ └───────────────────┘ └───────────────────┘ └───────────────────────┘ │
13│ │
14└─────────────────────────────────────────────────────────────────────────────────────────────┘
15
16
17┌─────────────────────────────────────────────────────────────────────────────────────────────┐
18│ 상태 관리 레이어 │
19│ │
20│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │
21│ │ authAPI.js │◀──────▶│ authSlice.js │───────▶│ store/index.js │ │
22│ │ API 통신 함수 정의 │ │ 리듀서 및 액션 정의 │ │ 스토어에 리듀서 등록 │ │
23│ └───────────────────┘ └───────────────────┘ └───────────────────────┘ │
24│ ▲ ▲ │ │
25│ │ │ │ │
26│ └────────────────────────────┘ │ │
27│ │ │
28└─────────────────────────────────────────────────────────────────────────────────────────────┘
29
30
31┌─────────────────────────────────────────────────────────────────────────────────────────────┐
32│ 접근 레이어 │
33│ │
34│ ┌───────────────────┐ │
35│ │ useAuth.jsx │ │
36│ │ 커스텀 훅 정의 │ │
37│ └───────────────────┘ │
38│ │ │
39│ │ │
40└─────────────────────────────────────────────────────────────────────────────────────────────┘
41
42
43┌─────────────────────────────────────────────────────────────────────────────────────────────┐
44│ UI 레이어 │
45│ │
46│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │
47│ │ Login.jsx │ │ Register.jsx │ │ 기타 컴포넌트 │ │
48│ │ 로그인 컴포넌트 │ │ 회원가입 컴포넌트 │ │ │ │
49│ └───────────────────┘ └───────────────────┘ └───────────────────────┘ │
50│ │ │ │ │
51│ │ │ │ │
52│ └────────────────────────────┼──────────────────────────────┘ │
53│ │ │
54│ ▼ │
55│ ┌───────────────────┐ │
56│ │ useAuth() │ │
57│ │ 커스텀 훅 사용 │ │
58│ └───────────────────┘ │
59│ │
60└─────────────────────────────────────────────────────────────────────────────────────────────┘

데이터 흐름 시퀀스

text
1┌───────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ ┌────────────┐
2│ Login.jsx │ │ useAuth │ │ authSlice │ │ authAPI │ │ client.js│ │ Server │
3└─────┬─────┘ └────┬─────┘ └─────┬──────┘ └─────┬──────┘ └────┬─────┘ └─────┬──────┘
4 │ │ │ │ │ │
5 │ login(email,pwd)│ │ │ │ │
6 │────────────────>│ │ │ │ │
7 │ │ dispatch(loginUser) │ │ │
8 │ │─────────────────>│ │ │ │
9 │ │ │ │ │ │
10 │ │ │ loginUser.pending │ │ │
11 │ │<──────────────────│ │ │ │
12 │ loading=true │ │ │ │ │
13 │<────────────────│ │ │ │ │
14 │ │ │ │ │ │
15 │ │ │ authAPI.login() │ │ │
16 │ │ │──────────────────>│ │ │
17 │ │ │ │ axios.post() │ │
18 │ │ │ │────────────────>│ │
19 │ │ │ │ │ HTTP Request │
20 │ │ │ │ │─────────────────>│
21 │ │ │ │ │ │
22 │ │ │ │ │ HTTP Response │
23 │ │ │ │ │<─────────────────│
24 │ │ │ │ API 응답 │ │
25 │ │ │ │<────────────────│ │
26 │ │ │ response data │ │ │
27 │ │ │<──────────────────│ │ │
28 │ │ │ │ │ │
29 │ │ │ fulfilled/rejected│ │ │
30 │ │<──────────────────│ │ │ │
31 │ 상태 업데이트 │ │ │ │ │
32 │<────────────────│ │ │ │ │
33 │ │ │ │ │ │
34 │ UI 업데이트 │ │ │ │ │
35 │─────────────────│ │ │ │ │
36┌─────┴─────┐ ┌────┴─────┐ ┌─────┴──────┐ ┌─────┴──────┐ ┌────┴─────┐ ┌─────┴──────┐
37│ Login.jsx │ │ useAuth │ │ authSlice │ │ authAPI │ │ client.js│ │ Server │
38└───────────┘ └──────────┘ └────────────┘ └────────────┘ └──────────┘ └────────────┘

결론

솔직히 zustand 쓰고 싶지만, 지식의 확장과 탐구력을 그리고 추후 복잡한 상태 관리와 장기적인 유지보수를 해보고 싶어서 redux-toolkit 사용