React + TS에서 Redux Toolkit 적용하기

Jemin·2023년 7월 17일
0

개발 지식

목록 보기
23/52
post-thumbnail

Redux Toolkit 사용하기

이전에 Redux에 대해서 기본 개념을 익혔으니 이제 프로젝트에 적용하기 위해 Redux Toolkit에서 사용하는 용어를 먼저 알아보자.

yarn add @reduxjs/toolkit

Redux Toolkit의 공식 패키지는 타입 정의를 내장하고 있기 때문에 별도의 타입 정의 패키지를 설치하지 않아도 된다.

createSlice

createSlice는 리듀서와 액션 생성자를 한 번에 생성하는 함수다. createSlice를 사용하면 반복적이고 번거로운 리듀서 및 액션 생성 작업을 최소화할 수 있다.

createSlice 함수는 세 개의 매개변수를 받습니다.

  • name: 슬라이스의 이름을 나타내는 문자열이다.

  • initialState: 슬라이스의 초기 상태를 정의하는 객체다.

  • reducers: 리듀서 함수를 정의하는 객체다.

밑의 코드는 get 요청으로 받아온 데이터를 저장하는 슬라이스다.

import { ResponseData } from "@/api";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

interface ContentsStateType { // 슬라이스 초기 상태 타입
  contents: ResponseData | null;
  isLoading: boolean;
  error: string | null;
}

const initialState: ContentsStateType = { // 슬라이스 초기 상태 객체
  contents: null,
  isLoading: false,
  error: null,
};

const contentsStateSlice = createSlice({
  name: "contentsState", // 슬라이스 이름
  initialState, // 초기 상태
  reducers: { // 리듀서
    // 액션들
    setContents: (state, action: PayloadAction<ResponseData>) => {
      state.contents = action.payload;
    },
    setIsLoading: (state, action: PayloadAction<boolean>) => {
      state.isLoading = action.payload;
    },
    setError: (state, action: PayloadAction<string | null>) => {
      state.error = action.payload;
    },
  },
});

export const contentsStateAction = contentsStateSlice.actions;
export const contentsState = contentsStateSlice.name;
export const contentsStateReducer = contentsStateSlice.reducer;

PayloadAction<타입>으로 action으로 넘어오는 값의 타입을 정의할 수 있다.
하나의 슬라이스는 하나의 리듀서 함수를 가질 수 있다. 그 안에 여러 개의 액션을 만들 수 있다.

configureStore

configureStore는 Redux 스토어를 설정하고 생성하는 데 사용된다.

configureStore는 기본적으로 다음과 같은 매개변수를 받는다.

  • reducer: 애플리케이션의 루트 리듀서다. Redux 스토어는 이 리듀서를 통해 상태 업데이트를 처리한다.

  • middleware: Redux 미들웨어를 추가하는 배열이다. 미들웨어는 액션과 리듀서 사이에서 동작하며, Redux Toolkit은 기본적으로 Redux Thunk를 지원한다.

import { configureStore } from "@reduxjs/toolkit";

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

현재 프로젝트에서 미들웨어는 굳이 필요하지 않기 때문에 루트 리듀서만 넘겨줬다.

combineReducers

combineReducers는 여러 개의 리듀서를 결합하여 하나의 루트 리듀서를 생성하는 데 사용된다. Redux는 단일 리듀서만을 허용하기 때문에 여러 개의 리듀서가 필요한 경우 이 함수를 사용할 수 있다.

import { combineReducers } from "@reduxjs/toolkit";
import { RefOffsetListStateReducer } from "@/store/RefTopStateSlice";
import { contentsStateReducer } from "./contentsStateSlice";
import { skillDescStateReducer } from "./skillDescStateSlice";

const rootReducer = combineReducers({
  RefOffsetListStateReducer,
  contentsStateReducer,
  skillDescStateReducer,
});

슬라이스를 총 세 개 만들었기 때문에 세 개의 리듀서를 하나의 루트 리듀서로 결합했다.

React-Redux 사용하기

이제 만들어진 store와 action을 컴포넌트에서 사용하기 위해 React-Redux를 사용해야 한다.

yarn add react-redux

react-redux 패키지 또한 7.1.0 버전부터 타입 정의를 내장하고 있기 때문에 최신 버전으로 설치하면 별도의 타입 정의 패키지가 필요 없다.

useDispatch

useDispatch는 React 함수형 컴포넌트에서 Redux 스토어에 접근하여 액션을 디스패치할 수 있다.

디스패치(dispatch)란
Redux에서 액션을 발생시키는 것과 액션을 Redux 스토어로 보내는 작업을 의미한다.
디스패치를 통해 액션을 발생시키면, Redux는 해당 액션을 처리하여 상태를 업데이트하고, 연관된 리듀서를 실행하여 새로운 상태를 반환한다.

import { useDispatch } from "react-redux";

function Component() {
  const dispatch = useDispatch();

  dispatch(액션함수());
  
  return ...
}

위 코드와 같이 사용할 수 있다.

useSelector

useSelector는 스토어의 상태를 선택(select)하기 위해 사용된다.
이 훅은 선택한 상태 값을 반환하고, 해당 상태가 변경될 때 자동으로 컴포넌트를 리렌더링한다.

선택자 함수란
상태 스토어에서 원하는 데이터를 추출하는 함수다.

import { useSelector } from "react-redux";

const selectValue = useSelector(state => state.슬라이스리듀서.상태값);

위 코드와 같이 사용할 수 있다.

타입스크립트에서 사용하기

타입스크립트에서 사용하기 위해 먼저 타입을 정의한 유틸리티 함수로 만들어줘야 사용하기 편하다.

// store가 있는 파일
import { useDispatch, useSelector, TypedUseSelectorHook } from "react-redux";

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

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

store.getState는 스토어에서 현재 전체 상태를 반환하는 메서드다.
store.dispatch는 스토어에 액션을 보내는 메서드다.
TypedUseSelectorHookuseSelector 훅의 제네릭 타입이다.

위 코드를 한 줄씩 살펴보자.

  • store.getState() 메서드의 반환 타입을 추론하여 RootState 타입을 정의한다. 스토어의 전체 상태를 나타내는 타입이다.

  • store.dispatch의 타입을 AppDispatch로 정의한다. 스토어의 디스패치 함수 타입을 나타낸다.

  • useDispatch 훅을 사용하여 AppDispatch를 가져오는 함수다. () => AppDispatch는 반환 타입을 정의한다.

  • useSelector 훅을 사용하여 RootState에 대한 선택자 함수를 가져오는 함수다.

위 코드들을 함께 사용하면 Redux Toolkit과 react-redux를 통해 간편하게 타입스크립트에서도 Redux 상태에 접근하고 디스패치를 사용할 수 있다.
useAppDispatch를 사용해 디스패치 함수를 가져오고, useAppSelector를 사용해 상태를 선택할 수 있다.

커스텀 훅으로 만들기

useAppDispatchuseAppSelector 함수를 커스텀 훅으로 만들어서 사용했다.
커스텀 훅을 사용해서 컴포넌트에서 간결하게 코드를 작성할 수 있다.

디스패치 커스텀 훅

import { getContentsData } from "@/api";
import { useAppDispatch } from "@/store";
import { contentsStateAction } from "@/store/contentsStateSlice";
import { useEffect } from "react";

export const useAxiosGetContents = () => {
  // contents 데이터 get 요청해서 store에 저장하는 훅
  const dispatch = useAppDispatch(); // 디스패치 함수 가져오기
  useEffect(() => {
    dispatch(contentsStateAction.setIsLoading(true)); // 로딩 시작
    dispatch(contentsStateAction.setError(null)); // 에러 초기화

    getContentsData()
      .then((res) => dispatch(contentsStateAction.setContents(res))) // 콘텐츠 저장
      .catch((e) => dispatch(contentsStateAction.setError(e.message))) // 에러 메세지 저장
      .finally(() => dispatch(contentsStateAction.setIsLoading(false))); // 로딩 종료
  }, [dispatch]);
};

export default useAxiosGetContents;

API 요청을 통해 받아온 데이터를 스토어에 저장하는 훅이다.

선택자 함수 커스텀 훅

import { RootState, useAppSelector } from "@/store";

export const useGetContents = () => {
  // store에 저장된 contents를 가져오는 훅
  const { contents, error, isLoading } = useAppSelector(
    (state: RootState) => state.contentsStateReducer
  );
  return {
    contentsData: contents,
    contentsError: error,
    isContentsLoading: isLoading,
  };
};

export default useGetContents;

스토어에서 현재 상태를 가져오는 훅이다.

profile
경험은 일어난 무엇이 아니라, 그 일어난 일로 무엇을 하느냐이다.

2개의 댓글

comment-user-thumbnail
2023년 7월 18일

글 잘 봤습니다, 감사합니다.

1개의 답글