Redux 부분(saga, toolkit)을 타입스크립트로 변경해보자

개발공부·2023년 3월 10일
0
post-thumbnail

* 작성 의도

▶ 초기 설정 및 Redux 부분을 타입스크립트로 변경 시 자바스크립트와 차이점이 무엇인지 작성하기 위함
typescript로 바꿀 때 일부만 추가될 뿐 엄청나게 차이나지 않음
next로 작성되므로 _app.tsx에서 redux에서 가져온 wrapper로 감싸짐
▶ 결과는 이전 포스팅에서 확인 가능함

[폴더 구조]

[]로 닫힌 부분 : 폴더
.tsx.ts로 쓰여진 부분 : 파일

[compontents]
	ㄴ[post]
    	ㄴQuotes.tsx
[pages]
	ㄴ[page]
    	ㄴindex.js //화면
[redux]
	ㄴ[feature] //toolkit부분
    	ㄴindex.ts //rootReducer 존재
        ㄴquoteSlice.ts
   ㄴ[sagas]
   		ㄴrootSaga.ts //rootSaga 존재
		ㄴquoteSaga.ts
  ㄴstore.ts //RootSate 존재
type.ts //타입지정

* 타입스크립트에서 추가된 항목들

◆ 주로 타입 지정과 관련됨
type.ts : 타입 지정
store.ts : 코드 추가됨(store쪽과 interface)
quoteSaga.ts, quoteSlice.ts
Quotes.tsx

* 순서

1. 타입 지정

[type.ts]

export interface Quote {
    name: string,
    content: string
}

export interface quoteProps  {
    quoteResult: null | Quote,
    showQuoteLoading: boolean,
    showQuoteComplete: boolean,
    showQuoteError: any,
}

export interface quoteResponse {
     data: string;
}

2. redux 수정(store과 toolkit)

[store.ts]

▶ interface RootState 추가됨

import { configureStore } from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import { createWrapper } from "next-redux-wrapper";

import rootReducer from "./feature";
import rootSaga from "./sagas/rootSaga";
import { quoteProps } from "../type";

const isDev = process.env.NODE_ENV === "development";

const createStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware];
  const store = configureStore({
    reducer: rootReducer,
    middleware: middlewares,
    devTools: isDev,
  });
  (store as any).sagaTask = sagaMiddleware.run(rootSaga);
  return store;
};

const wrapper = createWrapper(createStore, {
  debug: isDev,
});

//타입스크립트에서 추가되는 부분
export interface RootState {
  quote: quoteProps;
}

export default wrapper;

[feature/index.ts]

▶ 자바스크립트 파일과 동일함

import { combineReducers } from "redux";
import { HYDRATE } from "next-redux-wrapper";
import quote from "./quoteSlice";

const rootReducer = (state, action) => {
  switch (action.type) {
    case HYDRATE:
      // console.log("HYDRATE", action);
      return action.payload;
    default: {
      const combinedReducer = combineReducers({
        quote,
      });
      return combinedReducer(state, action);
    }
  }
};

export default rootReducer;

[quoteSlice.ts]

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

const initialState:quoteProps = {
  quoteResult: null,
  showQuoteLoading: false,
  showQuoteComplete: false,
  showQuoteError: null,
};

export const quoteSlice = createSlice({
  name: "quote",
  initialState,
  reducers: {
    showQuoteRequest: (state: quoteProps, action:PayloadAction<null>) => {
      state.showQuoteLoading = true;
      state.showQuoteError = null;
      state.showQuoteComplete = false;
    },
    showQuoteSuccess: (state: quoteProps, action:PayloadAction<null>) => {
      const data = action.payload;
      state.showQuoteLoading = false;
      state.showQuoteComplete = true;
      state.quoteResult = data;
    },
    showQuoteError: (state, action) => {
      state.showQuoteLoading = true;
      state.showQuoteError = action.payload.error;
    },
  }
});

export const { showQuoteRequest, showQuoteSuccess, showQuoteError } =
  quoteSlice.actions;

export default quoteSlice.reducer;

3. redux 수정(saga)

[rootSaga.ts]

▶ 자바스크립트 파일과 동일함

import { all } from "redux-saga/effects";
import { quoteSaga } from "./quoteSaga";
import axios from "axios";

axios.defaults.baseURL = "http://localhost:3005";
axios.defaults.withCredentials = true;

export default function* rootSaga() {
  yield all([
    ...quoteSaga,
  ]);
}

[quoteSaga.ts]

▶ quoteResponse를 불러와서 적용함

import axios from "axios";
import { takeLatest, put, fork, call } from "redux-saga/effects";
import { quoteResponse } from "../../type";
import {
  showQuoteRequest,
  showQuoteSuccess,
  showQuoteError,
} from "../feature/quoteSlice";

function showQuoteAPI() {
  return axios.get<quoteResponse>("/quote");
}

function* showQuote() {
  try {
    const response = yield call(showQuoteAPI);
    yield put(showQuoteSuccess(response.data));
  } catch (error) {
    if (axios.isAxiosError<quoteResponse>(error)) {
    yield put(showQuoteError(error.response));
    console.log(error);
    } 
  }
}

function* show_Quote_Req() {
  yield takeLatest(showQuoteRequest.type, showQuote);
}

export const quoteSaga = [fork(show_Quote_Req)];

4. redux 적용(App단)

[_app.tsx]

import React from "react";
import Head from "next/head";
import "../styles/globals.css"; //tailwindcss 적용
import wrapper from "../redux/store"; //redux 적용
import { ThemeProvider } from "next-themes"; //darkmode적용

const App = ({ Component }) => {
  return (
    <>
      <Head>
        <title>engWord</title>
      </Head>
      <ThemeProvider attribute="class">
        <Component />
      </ThemeProvider>
    </>
  );
};

export default wrapper.withRedux(App);

5. redux 적용(page)

[pages/post/index.js]

▶ 다른 컴포넌트도 존재하는데 아직 수정하지 않아서 충돌 생김
▶ 자바스크립트나 타입스크립트나 크게 차이 존재하지 않음

import axios from "axios";
import Head from "next/head";
import dynamic from "next/dynamic";
import wrapper from "../../redux/store";

const Qutoes = dynamic(import("../../components/post/Quotes"));

  return (
    <>
       <Qutoes />
    </>
  );
};

export const getServerSideProps = wrapper.getServerSideProps(
  async (context) => {
    const cookie = context.req ? context.req.headers.cookie : "";
    axios.defaults.headers.Cookie = "";
    if (context.req && cookie) {
      axios.defaults.headers.Cookie = cookie;
    }

    //context.store.dispatch(loadMyInfoRequest());
    //context.store.dispatch(loadPostsRequest());
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
  }
);

export default Index;

6. 컴포넌트

[Quotes.tsx]

useSelector 사용
▶ 핵심은 useSelector 사용 전RootState으로 타입을 표기해야 함

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { showQuoteRequest } from "../../redux/feature/quoteSlice";
import { RootState } from "../../redux/store";

const Quotes = () => {
  const dispatch = useDispatch();
  const { quoteResult } = useSelector((state: RootState) => state.quote);

  useEffect(() => {
    dispatch(showQuoteRequest());
  }, []);

  return (
    <div>
      <div>
        <h2>추천 영어 문장</h2>
      </div>
      <div>
        <div>
          <p>
              {quoteResult?.content === undefined ? "영어 인용문 찾아 오는 중" : quoteResult?.content}
          </p>
            <p>{quoteResult?.name === undefined ? null : `-${quoteResult?.name}-`}</p>
        </div>
      </div>
    </div>
  )
};

export default Quotes;
profile
개발 블로그, 티스토리(https://ba-gotocode131.tistory.com/)로 갈아탐

0개의 댓글