[React] Typescript + React Redux 프로젝트에 적용하기
지난 프로젝트에서 Vite (비트) + Typescript 로 리액트 프로젝트를 진행하며 상태관리 툴로 리액트 리덕스를 사용하였다.
타입스크립트 리액트 프로젝트에서 리덕스를 사용하는 방법을 정리하고
또 언제 리덕스를 사용하면 좋은지 작성해 보고자 한다.
지난 프로젝트 폴더 구성이다.
기능별로 store을 나누어 생성했고, index에서 관리하였다.
0. 라이브러리 설치
npm install @reduxjs/toolkit react-redux
1. store 생성
store/index.ts
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
},
});
export type RootReducer = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
jsx와 동일하게 configureStore 을 이용하여 생성할 수 있다.
외부 컴포넌트에서 state,dispatch를 사용하기 위해서는 타입 지정 필요
1-1. 훅(Hooks) 타입 지정
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootReducer } from "../store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootReducer> = useSelector;
- TypedUseSelectorHook : 타입 지원
useAppDispatch: 새 커스텀 훅 정의, 액션을 디스패치 할때마다 타입 체크가 가능해져 타입 안정성을 보장할 수 있다.
또, useAppSelector을 새로 정의하면서 타입에 맞게 상태를 선택할 수있어 역시 타입 안정성이 강화된다.
2. 슬라이스 생성
예시로, 채팅 슬라이스의 코드를 가져와보았다.
store/chatPage/chatSlice.ts
// chatSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface ChatState {
chats: string[];
selectedChatId: string | null;
selectedChat: string | null;
}
const initialState: ChatState = {
chats: [],
selectedChatId: null,
selectedChat: null,
};
const chatSlice = createSlice({
name: "chat",
initialState,
reducers: {
setSelectedChatId(state, action: PayloadAction<string | null>) {
state.selectedChatId = action.payload;
state.selectedChat =
state.chats.find(
(chat, index) => index.toString() === action.payload
) || null;
},
setChats(state, action: PayloadAction<string[]>) {
state.chats = action.payload;
},
},
});
export const { setSelectedChatId, setChats } = chatSlice.actions;
export default chatSlice.reducer;
state와 action의 타입 정의 및 지정이 필요하다.
각 슬라이스 파일은 초기 상태 값에 대한 유형을 정의해야 createSlice 가 가능하다.
따라서 인터페이스로 타입 정의를 먼저 해주고,
initialState에서 초기값을 설정해 주어야 한다.
PlayloadAction<T> 로 'action.playload'의 타입을 정의하여야 한다.
셀렉터를 사용하려면 아래와 같이 store 파일 유형으로 가져와야 한다.
import type { RootState } from './store'
export const selectCategory = (state: RootState) => state.chat.value
3. store에 리듀서 추가
store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import chatReducer from "./chatPage/chatSlice.ts";
export const store = configureStore({
reducer: {
chatReducer,
},
});
export type RootReducer = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
사용할 리듀서 들을 작성해 준다.
4. 컴포넌트에서 store의 state와 action 접근하기
pages/ChatPage/Chat.tsx
import React, { useEffect } from 'react'
import { useAppSelector, useAppDispatch } from "../../../hooks/redux";
import { setSelectedChatId, setChats } from "../../../store/chatPage/chatSlice";
const Chat = () => {
const selectedChatId = useAppSelector(
(state) => state.chatReducer.selectedChatId
);
const dispatch = useAppDispatch();
// ...
useEffect(() => {
dispatch(setChats(data));
});
}
미리 구성한 hooks에서 selector와 dispatch 함수를 가져와 이용할 수 있고,
store 에 저장된 값을 꺼내 쓰거나, 액션 함수를 사용 가능하다.
이번 프로젝트에서 openvidu 라는 오픈 소스 라이브러리를 jsx -> tsx 로 마이그레이션 하여 적용하며
타입과 그로인한 리덕스 적용에 많은 문제를 만났었지만,
오히려 덕분에 한번 더 개념을 정리할 수 있어서 도움이 되었던 것 같다.