이전에 Redux에 대해서 기본 개념을 익혔으니 이제 프로젝트에 적용하기 위해 Redux Toolkit에서 사용하는 용어를 먼저 알아보자.
yarn add @reduxjs/toolkit
Redux Toolkit의 공식 패키지는 타입 정의를 내장하고 있기 때문에 별도의 타입 정의 패키지를 설치하지 않아도 된다.
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
는 Redux 스토어를 설정하고 생성하는 데 사용된다.
configureStore
는 기본적으로 다음과 같은 매개변수를 받는다.
reducer: 애플리케이션의 루트 리듀서다. Redux 스토어는 이 리듀서를 통해 상태 업데이트를 처리한다.
middleware: Redux 미들웨어를 추가하는 배열이다. 미들웨어는 액션과 리듀서 사이에서 동작하며, Redux Toolkit은 기본적으로 Redux Thunk를 지원한다.
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: rootReducer,
});
현재 프로젝트에서 미들웨어는 굳이 필요하지 않기 때문에 루트 리듀서만 넘겨줬다.
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,
});
슬라이스를 총 세 개 만들었기 때문에 세 개의 리듀서를 하나의 루트 리듀서로 결합했다.
이제 만들어진 store와 action을 컴포넌트에서 사용하기 위해 React-Redux를 사용해야 한다.
yarn add react-redux
react-redux
패키지 또한 7.1.0
버전부터 타입 정의를 내장하고 있기 때문에 최신 버전으로 설치하면 별도의 타입 정의 패키지가 필요 없다.
useDispatch
는 React 함수형 컴포넌트에서 Redux 스토어에 접근하여 액션을 디스패치할 수 있다.
디스패치(dispatch)란
Redux에서 액션을 발생시키는 것과 액션을 Redux 스토어로 보내는 작업을 의미한다.
디스패치를 통해 액션을 발생시키면, Redux는 해당 액션을 처리하여 상태를 업데이트하고, 연관된 리듀서를 실행하여 새로운 상태를 반환한다.
import { useDispatch } from "react-redux";
function Component() {
const dispatch = useDispatch();
dispatch(액션함수());
return ...
}
위 코드와 같이 사용할 수 있다.
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
는 스토어에 액션을 보내는 메서드다.
TypedUseSelectorHook
은useSelector
훅의 제네릭 타입이다.
위 코드를 한 줄씩 살펴보자.
store.getState()
메서드의 반환 타입을 추론하여 RootState
타입을 정의한다. 스토어의 전체 상태를 나타내는 타입이다.
store.dispatch
의 타입을 AppDispatch
로 정의한다. 스토어의 디스패치 함수 타입을 나타낸다.
useDispatch
훅을 사용하여 AppDispatch
를 가져오는 함수다. () => AppDispatch
는 반환 타입을 정의한다.
useSelector
훅을 사용하여 RootState
에 대한 선택자 함수를 가져오는 함수다.
위 코드들을 함께 사용하면 Redux Toolkit과 react-redux를 통해 간편하게 타입스크립트에서도 Redux 상태에 접근하고 디스패치를 사용할 수 있다.
useAppDispatch
를 사용해 디스패치 함수를 가져오고, useAppSelector
를 사용해 상태를 선택할 수 있다.
useAppDispatch
와 useAppSelector
함수를 커스텀 훅으로 만들어서 사용했다.
커스텀 훅을 사용해서 컴포넌트에서 간결하게 코드를 작성할 수 있다.
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;
스토어에서 현재 상태를 가져오는 훅이다.
글 잘 봤습니다, 감사합니다.