Front-end/Next.js

[Next.js] Next.js + TypeScript 환경에서 Redux Toolkit 사용하기

영벨롭 2024. 7. 10. 17:35

📌 Redux Toolkit이란?

Redux Toolkit효율적인 Redux 개발을 위해, Redux의 설정을 간소화하고 편리한 API를 제공하는 전역 상태 관리 도구입니다.

yarn add @reduxjs/toolkit

 

✅ Redux의 단점

  • 보일러플레이트 코드
    • Redux의 일반적인 설정과 사용 방법에 따라 보일러플레이트 코드가 많이 필요합니다.
    • 특히 액션 타입, 액션 생성자 함수 등을 수동으로 작성해야 할 때가 많습니다. 
🤔 보일러플레이트 코드 (boilerplate code)
프로그래밍에서 반복적이고 일반적인 작업을 수행하기 위해 필요한 기본적인 코드 템플릿

 

 

Redux Toolkit 은 이러한 리덕스의 단점을 보완하고자 등장하였습니다.

리덕스의 설정을 간소화하고, 많은 리덕스 보일러플레이트 코드를 줄일 수 있습니다.

슬라이스(slice) 기반의 리듀서, 액션 생성 함수의 자동 생성 등을 통해 코드 작성의 생산성을 높일 수 있습니다. 

 


📌 Next.js + TypeScript 프로젝트에 적용

1. 패키지 설치

yarn add react-redux @reduxjs/toolkit

 

2. 리덕스 스토어(store) 생성

@reduxjs/toolkit 의 configureStore 를 사용하여 리덕스 스토어를 생성합니다.

 

(rootReducer 는 추후에 설명하겠습니다)

// src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";

import rootReducer from "./rootReducer";

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

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;
  • RootState: Redux 스토어의 전체 상태를 나타내는 타입입니다.
    • ReturType<typeof store.getState> 를 통해 store.getState 함수의 반환 타입을 추론합니다.
    • useSelector 훅을 사용할 때, RootState 타입을 참조함으로써 상태의 타입을 정확하게 체크할 수 있습니다.
  • AppDispatch: Redux 스토어의 디스패치 함수 타입을 정의합니다.
    • store.dispatch 는 Redux 액션을 디스패치하는 함수입니다.
    • useDispatch 훅을 사용할 때, AppDispatch 타입을 참조함으로써 디스패치 함수의 타입을 정확하게 체크할 수 있습니다.

 

3. 리덕스 슬라이스(Slice) 생성

스토어를 생성한 파일 위치에 각각의 리덕스 슬라이스를 관리할 slices 폴더를 생성합니다.

 

각 슬라이스는 @reduxjs/toolkit의 createSlice 메소드를 사용하여 생성합니다. 

 

이때 createSlice 메소드는 객체를 인수로 받아 슬라이스를 생성하며, 이 객체는 다음과 같은 속성들이 포함됩니다.

  • name: 슬라이스의 이름으로 쓸 문자열입니다.
  • initialState: 슬라이스의 초기 상태입니다.
  • reducers: 상태를 업데이트하는 리듀서 함수들의 객체입니다.
    • 각 함수는 상태(state)와 액션(action)을 인수로 받아 새로운 상태를 반환합니다.
    • state: 현재 슬라이스의 상태입니다.
    • action: 디스패치된 액션 객체로, typepayload 속성을 가집니다
      • type: 어떤 액션이 실행되었는지를 나타내는 문자열입니다. (createSlice를 사용하면 자동으로 생성됩니다)
      • payload: 액션과 함께 전달된 데이터입니다. 
🤔 슬라이스 Slice
- 상태(state)와 해당 상태를 관리하는 리듀서(reducer), 액션(action)을 묶어주는 단위
- createSlice 를 통해 리덕스에서 상태와 관련된 모든 것을 하나의 슬라이스로 묶어서 관리 가능
🤔 리듀서 Reducer
- 리덕스에서 상태 변화를 처리하는 순수 함수
- 현재 상태(state)와 액션(action)을 받아 새로운 상태를 반환
- 즉, action.type 과 action.payload에 따라 새로운 상태를 변환하는 함수
// src/store/slices/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: CounterState) {
      state.value++;
    },
    decrement(state: CounterState) {
      state.value--;
    },
    incrementByAmount(state: CounterState, action: PayloadAction<number>) {
      state.value += action.payload;
    },
  },
});

// 이 액션들은 useDispatch 의 dispatch 함수의 인수로 지정될 액션입니다.
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

 

 

4. RootReducer 생성

@reduxjs/toolkit의 combineReducers 메소드를 통해 루트 리듀서를 생성합니다.

이를 통해 리덕스에서 여러 개의 리듀서를 하나의 최상위 리듀서 함수로 결합하여 관리할 수 있습니다.

  • 코드 분할: 애플리케이션이 커질수록 복잡해지는 상태 관리 로직을 combineReducers를 통해 여러 개의 작은 리듀서로 분리하여 관리할 수 있습니다.
  • 유지보수성 향상: 각 리듀서가 특정 상태 슬라이스를 관리하므로, 코드의 가독성과 유지보수성이 높아집니다.
  • 모듈화: 기능별로 상태와 리듀서를 모듈화할 수 있습니다.
// src/store/rootReducer.ts
import { combineReducers } from "@reduxjs/toolkit";

import counterReducer from "./slices/counterSlice";

const rootReducer = combineReducers({
  counter: counterReducer, // 이 부분에서 지정한 이름(counter)이 useSelector에서 사용됩니다.
});

export default rootReducer;

 

 

5. Next.js 와 Redux 연결

_app.tsx 파일에서 react-redux의 Provider 를 추가하여 리덕스 스토어를 애플리케이션 전체에 제공합니다. 

 

// pages/_app.tsx
import type { AppProps } from "next/app";
import { GlobalStyle } from "../src/styles";

import { Provider } from "react-redux";
import store from "../src/store";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <GlobalStyle />
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;

 

 

6. 컴포넌트에서 Redux 사용

이제 컴포넌트에서 useSelector와 useDispatch 훅을 사용하여 리덕스 상태와 액션을 사용하겠습니다.

// pages/index.tsx
import type { NextPage } from "next";

import styled from "styled-components";

import { RootState, AppDispatch } from "../src/store";
import { increment, decrement } from "../src/store/slices/counterSlice";
import { useDispatch, useSelector } from "react-redux";

const Home: NextPage = () => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch<AppDispatch>();

  const handleIncrement = () => {
    dispatch(increment());
  };

  const handleDecrement = () => {
    dispatch(decrement());
  };

  return (
    <StyledDiv>
      <button onClick={handleDecrement}>-</button>
      <span>{count}</span>
      <button onClick={handleIncrement}>+</button>
    </StyledDiv>
  );
};

export default Home;

const StyledDiv = styled.div`
  padding: 20px 30px;
  width: max-content;
  border-radius: 5px;
  font-weight: bold;
  background-color: orange;
`;

 

 

✅ useSelector 훅

useSelector 훅은 Redux 스토어의 상태를 조회하는 데 사용됩니다.

특정 컴포넌트 내에서 useSelector 훅을 통해 상태를 조회하고, 해당 상태가 변경되면 컴포넌트도 자동으로 리렌더링됩니다.

import { useSelector } from 'react-redux';

const selectedState = useSelector((state: RootState) => state.[선택할리듀서이름].[선택할상태이름]);

(이때 '선택할리듀서이름' 은 rootReducer에서 combineReducers 메소드에서 지정한 리듀서 이름입니다)

 

 

✅ useDispatch 훅

useDispatch 훅은 Redux 스토어의 dispatch 함수를 반환합니다.

dispatch 함수는 컴포넌트 내에서 액션을 디스패치할 수 있으며, 이를 통해 Redux 스토어의 상태를 변경할 수 있습니다.

import { useDispatch } from 'react-redux';

const dispatch = useDispatch<AppDispatch>();

dispatch(디스패치할액션함수());

(이때 '디스패치할액션함수()' 는 createSlice에서 reducers 에 지정된 액션들입니다.)

 


 

 

 

 

 

 

 

반응형