개발/프론트엔드

왜 Redux 대신 Redux Toolkit을 사용하는가?

paice 2024. 12. 23. 23:00

Redux는 리액트에서 상태 관리를 효율적으로 할 수 있도록 도와주는 라이브러리로, 많은 프론트엔드 개발자들이 사용하고 있다.

하지만 Redux를 사용할 때, 초기 설정과 사용 과정에서 복잡하고 장황한 코드가 필요하다는 단점이 있다.

이를 해결하기 위해, 더 간소화된 버전인 Redux Toolkit(RTK)을 사용할 수 있다.

이 글에서는 Redux와 Redux Toolkit의 차이를 예시 코드를 통해 살펴보며, Redux Toolkit이 어떻게 코드의 간결화, 비동기 작업 처리, 불변성 관리 등을 개선하는지 구체적으로 알아보겠다.


1. 보일러플레이트 코드 감소

Redux에서는 상태를 관리하기 위해 액션 생성자, 액션 타입, 리듀서, 미들웨어 설정을 각각 작성해야 하지만,

Redux Toolkit은 createSlice를 통해 액션리듀서를 한 번에 정의할 수 있어 코드가 훨씬 간결해진다.

 

Redux

// action types
const INCREMENT = 'INCREMENT';

// action creators
const increment = () => ({
  type: INCREMENT,
});

// reducer
const counterReducer = (state = { value: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, value: state.value + 1 };
    default:
      return state;
  }
};

export { increment, counterReducer };

 

Redux Toolkit

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
  },
});

export const { increment } = counterSlice.actions;
export default counterSlice.reducer;

 


2. 비동기 작업 처리의 단순화

Redux에서 비동기 작업(예: API 호출)을 처리하려면 redux-thunkredux-saga 같은 미들웨어를 설정해야 한다.

Redux Toolkit은 createAsyncThunk를 제공하여 비동기 작업을 쉽게 처리가 가능하며,

요청 상태(pending, fulfilled, rejected)를 자동으로 관리한다.

 

Redux

// action creators, thunk
const fetchUser = () => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_PENDING' });
  try {
    const response = await fetch('/api/user');
    const data = await response.json();
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });
  } catch (error) {
    dispatch({ type: 'FETCH_USER_ERROR', error });
  }
};

// reducer
const userReducer = (state = { data: null, loading: false, error: null }, action) => {
  switch (action.type) {
    case 'FETCH_USER_PENDING':
      return { ...state, loading: true };
    case 'FETCH_USER_SUCCESS':
      return { ...state, loading: false, data: action.payload };
    case 'FETCH_USER_ERROR':
      return { ...state, loading: false, error: action.error };
    default:
      return state;
  }
};

 

Redux Toolkit

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUser = createAsyncThunk('user/fetchUser', async () => {
  const response = await fetch('/api/user');
  return response.json();
});

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export default userSlice.reducer;

 


3. 불변성 관리의 간소화

Redux는 상태 불변성을 유지하기 위해 Object.assign이나 스프레드 연산자(...)를 사용해야 한다.

Redux Toolkit은 내부적으로 Immer를 사용하여 상태를 직접 수정하는 것처럼 작성하더라도 불변성을 자동으로 유지할 수 있다.

 

Redux

const counterReducer = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, value: state.value + 1 };
    default:
      return state;
  }
};

 

Redux Toolkit

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1; // Immer로 직접 수정 가능
    },
  },
});

 


결론

Redux는 상태 관리 기능이 뛰어나고, 복잡한 애플리케이션에서도 일관성 있게 상태를 관리할 수 있도록 돕는다.

하지만 사용하기 복잡한 반면, Redux Toolkit은 간단한 설정, 적은 코드, 편리한 기능으로 이러한 문제를 해결하는 장점을 가지고 있다.

Redux를 처음 사용하는 개발자나 기존 프로젝트를 단순화하려는 개발자에게 Redux Toolkit이 훨씬 적합하다고 생각한다.