리덕스(Redux) & 리덕스 툴킷(Redux Toolkit)

 

누구는 리덕스를 안 쓰는 회사가 없다고 한다.

누구는 리덕스의 러닝커브 때문에 Recoil, Zustand, MobX 사용한다고 한다.

누구는 리액트에서 제공하는 useReducer, context api 조합으로 해도 충분하다고 한다.

 

의견들이 너무 다양하다... 그냥 다 간단하게라도 개념을 알고, 사용해 보는 경험을 가지는 게 좋을 것 같다.  

 


 

리덕스(Redux)는 애플리케이션의 상태를 중앙에서 관리하기 위한 JS 라이브러리이다. 주로 React 애플리케이션에서 많이 사용되며, 상태 관리의 복잡성을 줄이고 애플리케이션의 일관성을 유지하는 데 도움이 된다. 하지만 리덕스는 설정이 복잡하고 많은 보일러플레이트 코드가 필요하기 때문에 이를 개선하기 위해 리덕스 툴킷(Redux Toolkit)이 등장했다.

 

 

리덕스(Redux)

리덕스는 다음과 같은 주요 개념으로 구성되어 있다.

 

1. 스토어(store)

 - 애플리케이션의 상태가 저장되는 곳이다.

 

2. 액션(action)

 - 상태에 변화를 일으키기 위한 지시사항이다. 주로 객체 형태이며 type 프로퍼티를 가진다.

 

3. 리듀서(reducer)

 - 액션을 처리하여 새로운 상태를 반환하는 함수이다.

 

4. 디스패치(dispatch)

 - 액션을 스토어에 전달하는 함수이다.

 

 

리덕스 툴킷(Redux Toolkit)

리덕스 툴킷은 리덕스의 사용을 더 간편하고 효율적으로 만들기 위해 개발된 라이브러리이다. 주요 기능으로는 configureStore, createSlice, createAsyncThunk 등이 있다.

 

주요 기능

1. configureStore : 스토어 설정을 간편하게 해준다.

 

2. createSlice : 액션과 리듀서를 한 번에 정의할 수 있게 해 준다.

 

3. createAsyncThunk : 비동기 액션을 쉽게 생성할 수 있다.

 

1. 리덕스 툴킷 설치

npm install @reduxjs/toolkit react-redux

 

2. 스토어 설정

configureStore를 사용하여 스토어를 설정한다.

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

 

3. 슬라이스(Slice) 생성

createSlice를 사용하여 액션과 리듀서를 한 번에 정의한다.

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

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

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

 

4. 리액트 컴포넌트에서 사용

리액트 컴포넌트에서 리덕스를 사용하려면 react-redux의 Provider와 useDispatch, useSelector 훅을 사용해야 한다.

import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import store from './store';
import { increment, decrement, incrementByAmount } from './counterSlice';

function Counter() {
  const dispatch = useDispatch();
  const count = useSelector((state) => state.counter.value);

  return (
    <div>
      <div>
        <button onClick={() => dispatch(decrement())}>-</button>
        <span>{count}</span>
        <button onClick={() => dispatch(increment())}>+</button>
      </div>
      <button onClick={() => dispatch(incrementByAmount(5))}>
        Add 5
      </button>
    </div>
  );
}

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

 

➕ 리덕스 툴킷에는 몇 가지 주요 라이브러리와 유틸리티가 포함되어 있어 Redux 애플리케이션을 더 쉽게 만들고 관리할 수 있다. 주로 포함되어 있는 라이브러리와 유틸리티는 다음과 같다.

 

1. immer : 불변성을 쉽게 관리하기 위한 라이브러리.

2. Redux Thunk : 비동기 로직을 처리하기 위한 미들웨어.

3. Reselect : 메모이제이션된 선택자를 만들기 위한 유틸리티.

 

🚩Immer

Immer는 Redux Toolkit의 내장 라이브러리로, 불변 상태 관리를 더 간단하게 한다. Immer를 사용하면 " draft " 상태를 직접 변경할 수 있으며, Immer가 이를 기반으로 불변 상태를 자동으로 생성해 준다.

const userSlice = createSlice({
  name: 'user',
  initialState: {
    name: 'kim',
    age: 20,
  },
  reducers: {
    changeName(state) {
      state.name = '설민'; // Immer 덕분에 불변 상태를 신경 쓰지 않아도 됨
    },
    plusAge(state, action) {
      state.age += action.payload; // 마찬가지로 불변 상태를 쉽게 변경 가능
    },
  },
});

 

왜 포함되어 있는가?

 - 불변성 유지 : 불변 상태 관리는 Redux의 핵심 원칙 중 하나이다. Immer는 이를 쉽게 유지할 수 있게 도와준다.

 - 코드 간결성 : 불변 상태 관리를 직접 구현하는 것보다 코드를 더 간결하게 만들어준다.

 

🚩Redux Thunk

Redux Thunk는 Redux 애플리케이션에서 비동기 로직(예:API 호출)을 처리하는 데 사용되는 미들웨어이다. Thunk는 액션 생성자가 함수를 반환할 수 있게 해 준다. 이 함수는 ` dispatch ` 와 ` getState `를 인자로 받아서 비동기 작업을 수행한 후 필요한 액션을 디스패치한다.

import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, thunkAPI) => {
    const response = await axios.get(`/api/users/${userId}`);
    return response.data;
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    name: 'kim',
    age: 20,
    status: 'idle',
    error: null,
  },
  reducers: {
    // 동기적 리듀서들
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.name = action.payload.name;
        state.age = action.payload.age;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

 

왜 포함되어 있는가?

 - 비동기 로직 처리 : Redux는 기본적으로 동기적인 상태 관리 라이브러리이다. Thunk를 사용하면 비동기 작업을 처리할 수 있다.

 - 간단한 미들웨어 : Thunk는 비교적 간단한 미들웨어로, 추가적인 설정 없이 바로 사용할 수 있다.

 

🚩Reselect

Reselect는 Redux 상태에서 파생된 데이터를 메모이제이션하여 캐싱할 수 있는 유틸리티이다. 메모이제이션을 통해 불필요한 리렌더링을 줄이고 성능을 최적화할 수 있다.

import { createSelector } from 'reselect';

const selectCartItems = (state) => state.cartItem;
const selectCartTotal = createSelector(
  [selectCartItems],
  (cartItems) =>
    cartItems.reduce((total, item) => total + item.count, 0)
);

 

왜 포함되어 있는가?

 - 성능 최적화 : 불필요한 리렌더링을 줄여 성능을 최적화할 수 있다.

 - 간결한 코드 : 파생된 데이터를 간결하게 계산할 수 있다.