[React] Redux - TypeScript 사용법

H_Chang·2023년 12월 19일
1

Redux의 기본적인 틀을 만들어보자!

1. store/index.ts

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

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

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  • 아직 리듀서를 만들지 않았기 때문에 들어가는 것은 없고,
  • RootState와 AppDispatch는 Hooks에서 사용될 Dispatch와 Selector에 사용할 타입이다.

2. hooks/index.ts

import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "@/store";

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

3. useAppDispatch

const x : (a:number) => string = (x) => String(x)
const x : (a:number) => string

4. useAppSelector

  const count = useSelector((state: RootState) => state.counter.value)
  • useSelector에 state 타입을 지정해주지 않으면, 사용할 때마다 매번 state의 타입을 지정해야 한다.
const count = useAppSelector((state) => state.counter.value)
  • 하지만 미리 설정해둔다면 따로 state 타입을 지정할 필요가 없다.

5. reducer/counter.ts

import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";

export interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  initialState,

  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },

    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
  • createSlice를 사용해서 초기값, reducer 등을 정의할 수 있다.

  • 그 후 action으로 사용하기 위해서 counderSlice.actions를 export 하고, 스토어에서 사용하기 위한 counterSlice.reducer도 export 한다.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../reducer/test";

export const store = configureStore({
  reducer: {
    // 추가
    counter: counterReducer,
  },
});

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

React에 적용하기

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

interface Props {}

const App = ({}: Props) => {
  return (
    <Provider store={store}>
      <Router />
    </Provider>
  );
};

export default App;
  • Provider를 사용해서 store에 우리가 만든 스토어를 넣어주면 된다.

비동기 처리하기

interface MyKnownError {
  errorMessage: string
}

// 통신 성공 시 가져오게 될 데이터의 타입
interface TodosAttributes {
  id: number;
  text: string;
  completed: boolean
}

// 비동기 통신 구현
const fetchTodos = createAsyncThunk<
  // 성공 시 리턴 타입
  TodosAttributes[],
  // input type. 아래 콜백함수에서 userId 인자가 input에 해당
  number,
  // ThunkApi 정의({dispatch?, state?, extra?, rejectValue?})
  { rejectValue: MyKnownError }
>('todos/fetchTodos', async(userId, thunkAPI) => {
  try { 
    const {data} = await axios.get(`https://localhost:3000/todos/${userId}`);
    return data;
  } catch(e){
    // rejectWithValue를 사용하여 에러 핸들링이 가능하다
    return thunkAPI.rejectWithValue({ errorMessage: '알 수 없는 에러가 발생했습니다.' });
  }
})
  • 비동기 작업을 위해서 사용하는 createASyncThunk이다.
  • <> 의 첫 번째 타입의 경우, 서버 통신이 원활하게 완료된 경우의 타입을 말한다.
  • 두 번째 타입의 경우, 매개변수, 코드에선 userId의 타입을 말한다.
  • 세 번째의 경우 오류가 발생했을 때 rejectValue가 return되는데, 그때의 타입을 말한다.
// ... //

const todosSlice = createSlice({
  // ...
  
  extraReducers: (builder) => {
    builder
      // 통신 중
      .addCase(fetchTodos.pending, (state) => {
        state.error = null;
        state.loading = true;
      })
      // 통신 성공
      .addCase(fetchTodos.fulfilled, (state, { payload }) => {
        state.error = null;
        state.loading = false;
        state.todos = payload;
      })
      // 통신 에러
      .addCase(fetchTodos.rejected, (state, { payload }) => {
        state.error = payload;
        state.loading = false;
      });
  },
  • 다음으로 reducer 설정이다.
  • addCase를 사용해서 pending, fulfilled, rejected 상태를 설정한다.
  • 각각 통신 중, 통신 성공, 통신 에러를 나타낸다.
import {unwrapResult} from '@reduxjs/toolkit';

// ... //

  try{
    const resultAction = await dispatch(fetchTodos(1));
    const todos = unwrapResult(resultAction);
    setTodos(todos);
  } catch(e){
    console.log(e)
  }
  • 마지막으로 사용할 때, Thunk는 기본적으로 프로미스를 반환한다.
  • 하지만 unwrapResult를 사용하면 바로 결과값을 핸들링을 할 수 있다
profile
프론트 엔드 시작하는 뉴비!

0개의 댓글