๐Ÿฒ Next.js + TypeScript + Redux-Toolkit + Redux-Saga์ ์šฉ

๋ฐ•์ƒ์€ยท2022๋…„ 7์›” 4์ผ
0

๐Ÿƒ blegram

๋ชฉ๋ก ๋ณด๊ธฐ
3/20

Next.js + TypeScript + Redux-Toolkit + Redux-Saga ์ ์šฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ํฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

๐Ÿ‘‡ ์„ค์น˜

create-next-app์„ ์ด์šฉํ•ด์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๋‚˜๋จธ์ง€ ์„ค์น˜ํ•  ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค.

npm i @reduxjs/toolkit next-redux-wrapper redux-saga react-redux
npm i -D @types/react-redux

๐Ÿค” Redux-Toolkit ์‚ฌ์šฉ ์ด์œ 

Redux์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”!

Redux-Toolkit์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Redux๋ฅผ ๋ฒ ์ด์Šค๋กœ ์žก๊ณ  ์ฝ”๋“œ ์ž‘์„ฑ์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

Redux๋ฅผ ์‚ฌ์šฉํ•ด๋ดค๋‹ค๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์œ ์šฉํ•œ ๋งŒํผ ์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ๋ถˆํŽธํ•˜๊ณ  ๊ท€์ฐฎ์Šต๋‹ˆ๋‹ค.
ํ•˜๋‚˜์˜ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ํƒ€์ž…, ์•ก์…˜, ๋ฆฌ๋“€์„œ๋ฅผ ์ถ”๊ฐ€/์ˆ˜์ •์ด ํ•„์š”ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ๋„ ๋ถˆ๋ณ€์„ฑ์„ ์ง€์ผœ์ค˜์•ผ ํ•ด์„œ ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๊ธฐ๊ฐ€ ํž˜๋“ญ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  typescript๋ฅผ ์ ์šฉํ•˜๋ฉด ๊ฐ๊ฐ ์ฃผ๊ณ  ๋ฐ›๋Š” ํƒ€์ž…์„ ์ •์˜ํ•˜๊ณ  ์ ์ ˆํ•œ ๊ณณ์— ์ ์šฉํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๊ธ€๋กœ ์ ์–ด์„œ ๋ณ„๊ฑฐ ์•„๋‹Œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋ฉด ํ•˜๋‚˜์˜ ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ํ•œ์ˆจ์ด ๋‚˜์˜ฌ ์ •๋„๋กœ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์ด๋ผ๊ณ  ๋Š๊ปด์ง‘๋‹ˆ๋‹ค.

์ด๋Ÿฐ Redux์˜ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์–ด๋Š ์ •๋„ ํ•ด์†Œํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ Redux-Toolkit์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
์œ„ ์„ค์น˜์—์„œ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ๊ธฐ๋ณธ์ ์œผ๋กœ Redux-Toolkit์—๋Š” Redux, immer, redux-thunk ๋“ฑ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด์„œ ๊ตณ์ด Redux๋ฅผ ๋”ฐ๋กœ ์„ค์น˜ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

โœจ Redux-Toolkit ์‚ฌ์šฉ๋ฒ•

๊ธฐ์กด Redux์˜ ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”!
์ „์ฒด ์ฝ”๋“œ๋Š” ๋„ˆ๋ฌด ๋งŽ์•„์„œ ๊ธฐ๋ณธ ์„ธํŒ…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ฒŒ์‹œ๊ธ€๋“ค์„ ๋กœ๋“œํ•˜๋Š” ์ฝ”๋“œ๋งŒ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ํด๋” ๊ตฌ์กฐ

  1. /src/configureStore.ts: ๋ฆฌ๋“€์„œ, ์‚ฌ๊ฐ€, ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ•ฉ์ณ ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑ + ๋ฆฌ๋“€์„œ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ๊ณต๊ฐ„
    ( ๋ฆฌ๋“€์„œ ํƒ€์ž…์ด๋ž€ react-redux์˜ useSelector()ํ›…์„ ์ด์šฉํ•ด์„œ ๊ฐ€์ ธ์˜ค๋Š” store์˜ ํƒ€์ž…์„ ์˜๋ฏธ )
  2. /src/store/api: ajax ์š”์ฒญ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋Š” ๊ณต๊ฐ„
  3. /src/store/reducers: ๋ฆฌ๋“€์„œ๋“ค์„ ์ •์˜ํ•˜๊ณ  index.ts์—์„œ rootReducer๋ฅผ ์ •์˜ ( createSlice() ์‚ฌ์šฉ )
  4. /src/store/sagas: ์‚ฌ๊ฐ€๋“ค์„ ์ •์˜ํ•˜๊ณ  index.ts์—์„œ rootSaga๋ฅผ ์ •์˜
  5. /src/store/types: ๋ฆฌ๋•์Šค์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ํƒ€์ž…๋“ค์„ ์ •์˜ํ•˜๋Š” ๊ณต๊ฐ„ ( api์— ๋Œ€ํ•œ ์š”์ฒญ/์‘๋‹ต ํƒ€์ž… ์ •์˜ )

์•ก์…˜ ํƒ€์ž…๊ณผ ์•ก์…˜ ํฌ๋ฆฌ์—์ดํ„ฐ๋Š” createSlice()๋กœ ๋งŒ๋“  slice.actions๋กœ ๋Œ€์ฒด ( ์ƒ์„ฑ ๋ฐฉ์‹์„ ์ฐธ๊ณ ํ•ด์„œ ์ž๋™์œผ๋กœ ์•ก์…˜์„ ์ƒ์„ฑํ•ด์คŒ )

2. type ์ƒ์„ฑ

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฃผ๊ณ  ๋ฐ›๋Š” ํƒ€์ž…์˜ ํ˜•ํƒœ
type responseType = {
  status: {
    ok: boolean,
    // ... ์ƒํƒœ๋ž‘ ๊ด€๋ จ๋œ ๋ฐ์ดํ„ฐ๋“ค
  },
  data: {
    message: string;
    // ... ์‘๋‹ต์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋“ค
  }
}
  • /src/store/types/index.ts
// ์‘๋‹ต status ๊ธฐ๋ณธ ํƒ€์ž…
export type ResponseStatus = {
  status: {
    ok: boolean;
  };
};
// ์‘๋‹ต ๋ฐ์ดํ„ฐ ๊ธฐ๋ณธ ํƒ€์ž…
export type ResponseData = {
  message: string;
};
// ์˜ˆ์ธก๊ฐ€๋Šฅํ•œ ์‹คํŒจ์ธ ๊ฒฝ์šฐ ์‘๋‹ต ํƒ€์ž… ( 403, 409 ๋“ฑ )
export type ResponseFailure = {
  status: { ok: boolean };
  data: { message: string };
};

export type { LoadPostsBody, LoadPostsResponse } from "./post";
  • /src/store/types/post.ts
import type { ResponseData, ResponseStatus } from ".";

// ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค ์ •๋ณด ๋กœ๋“œ ์š”์ฒญ/์‘๋‹ต ํƒ€์ž…
export type LoadPostsBody = {
  lastId: number;
  limit: number;
};
export type LoadPostsResponse = ResponseStatus & {
  data: ResponseData & {
    limit: number;
    posts: IPostWithPhotoAndCommentAndLikerAndCount[];  // "IPostWithPhotoAndCommentAndLikerAndCount"๋Š” ๊ฒŒ์‹œ๊ธ€ ํƒ€์ž…
  };
};

3. slice ์ƒ์„ฑ

  • /src/store/reducers/index.ts
import { HYDRATE } from "next-redux-wrapper";
import { combineReducers } from "@reduxjs/toolkit";
import type { AnyAction, CombinedState } from "@reduxjs/toolkit";

// reducers ( ๋‚˜๋จธ์ง€ ๋ฆฌ๋“€์„œ๋„ ์žˆ๋‹ค๊ณ  ๊ฐ€์ • )
import authReducer, { AuthStateType } from "./authReducer";
import userReducer, { UserStateType } from "./userReducer";
import postReducer, { PostStateType } from "./postReducer";
import chatReducer, { ChatStateType } from "./chatReducer";

// actions ( ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ import ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ export ~ from ์‚ฌ์šฉ )
export { authActions } from "./authReducer";
export { userActions } from "./userReducer";
export { postActions } from "./postReducer";
export { chatActions } from "./chatReducer";

type ReducerState = {
  auth: AuthStateType;
  post: PostStateType;
  user: UserStateType;
  chat: ChatStateType;
};

// ์›๋ž˜ "rootReducer"๋กœ ํ•ฉ์ณ์ค„ ํ•„์š” ์—†์ด "configureStore()"์—์„œ ํ•ฉ์น  ์ˆ˜ ์žˆ์ง€๋งŒ "HYDRATE"๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์šฉ
const rootReducer = (state: any, action: AnyAction): CombinedState<ReducerState> => {
  switch (action.type) {
    // SSR์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉ ( "next.js"์˜ "getServerSideProps()" )
    case HYDRATE:
      return {
        ...state,
        ...action.payload,
      };

    default:
      return combineReducers({
        auth: authReducer,
        user: userReducer,
        post: postReducer,
        chat: chatReducer,
      })(state, action);
  }
};

export default rootReducer;
  • /src/store/reducers/postReducer.ts
import { createSlice } from "@reduxjs/toolkit";

import type { PayloadAction } from "@reduxjs/toolkit";
import type { LoadPostsBody, LoadPostsResponse } from "@src/store/types";
  
export type PostStateType = {
  posts: IPostWithPhotoAndCommentAndLikerAndCount[];  // "IPostWithPhotoAndCommentAndLikerAndCount"๋Š” ๊ฒŒ์‹œ๊ธ€ ํƒ€์ž…
  loadPostsLoading: boolean;
  loadPostsDone: null | string;
  loadPostsError: null | string;
};

const initialState: PostStateType = {
  // ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜
  posts: [],
  
  // ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค ์š”์ฒญ ๊ด€๋ จ ๋ณ€์ˆ˜
  loadPostsLoading: false,
  loadPostsDone: null,
  loadPostsError: null,
};

/**
 * "createSlice()"๋Š” ์•ก์…˜ ํƒ€์ž…, ์•ก์…˜ ํฌ๋ฆฌ์—์ดํ„ฐ, ๋ฆฌ๋“€์„œ๋ฅผ ํ•œ ๋ฒˆ์— ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 * name: ์œ ๋‹ˆํฌํ•œ ์•ก์…˜์„ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ
 * initialState: ์ตœ์ดˆ ์ƒํƒœ
 * reducers: ๋ฆฌ๋“€์„œ๋“ค์„ ์ •์˜
 * PayloadAction๋กœ ์ธ์ž์˜ ํƒ€์ž…์„ ์ •์˜ํ•ด์ฃผ๋ฉด ์ž๋™์™„์„ฑ ์ง€์›๋จ
 */
const postSlice = createSlice({
  name: "post",
  initialState,
  reducers: {
    // ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค ํŒจ์น˜
    loadPostsRequest(state, action: PayloadAction<LoadPostsBody>) {
      state.loadPostsLoading = true;
      state.loadPostsDone = null;
      state.loadPostsError = null;
    },
    loadPostsSuccess(state, action: PayloadAction<LoadPostsResponse>) {
      state.loadPostsLoading = false;
      state.loadPostsDone = action.payload.data.message;
      // ์—ฌ๊ธฐ์„œ๋Š” "immer"๊ฐ€ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆ๋ณ€์„ฑ์„ ์ง€ํ‚ค์ง€ ์•Š์•„๋„ ๋จ
      // ํ•˜์ง€๋งŒ ์•„๋ž˜์ฒ˜๋Ÿผ ๋ถˆ๋ณ€์„ฑ ์ง€ํ‚ค๋Š”๊ฒŒ ์ฝ”๋“œ๊ฐ€ ๋” ๊ฐ„๋‹จํ•ด๋ณด์—ฌ์„œ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•จ
      state.posts = [...state.posts, ...action.payload.data.posts];
      state.hasMorePosts = action.payload.data.posts.length === action.payload.data.limit;
    },
    loadPostsFailure(state, action: PayloadAction<ResponseFailure>) {
      state.loadPostsLoading = false;
      state.loadPostsError = action.payload.data.message;
    },
  },
});

// ์•ก์…˜ ํƒ€์ž…๊ณผ ์•ก์…˜ ํฌ๋ฆฌ์—์ดํ„ฐ ๋Œ€์‹  ์‚ฌ์šฉ ( "dispatch()"์—์„œ ์‚ฌ์šฉ => ex) dispatch(postActions.loadPostsRequest({ lastId: 0, limit: 10 })) )
export const postActions = postSlice.actions;
// RootReducer ์ƒ์„ฑ ์‹œ ์‚ฌ์šฉ
export default postSlice.reducer;

4. api ์ƒ์„ฑ

  • /src/store/api/index.ts
import axios from "axios";

export const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_SERVER_URL + "/api",
  withCredentials: true,
  timeout: 10000,
});

export { apiLoadPosts } from "./post";
  • /src/store/api/post.ts
import { axiosInstance } from ".";

import type { LoadPostsBody, LoadPostsResponse } from "@src/store/types";

// ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค ์š”์ฒญ
export const apiLoadPosts = ({ lastId, limit }: LoadPostsBody) =>
  axiosInstance.get<LoadPostsResponse>(`/posts?lastId=${lastId}&limit=${limit}`);

5. saga ์ƒ์„ฑ

  • /src/store/sagas/index.ts
import { all, fork } from "redux-saga/effects";

// ๋‚˜๋จธ์ง€ ์‚ฌ๊ฐ€๋„ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
import authSaga from "./authSaga";
import userSaga from "./userSaga";
import postSaga from "./postSaga";
import chatSaga from "./chatSaga";

export default function* rootSaga() {
  yield all([fork(authSaga), fork(userSaga), fork(postSaga), fork(chatSaga)]);
}
  • /src/store/sagas/post.ts
import { all, call, fork, put, takeLatest } from "redux-saga/effects";

// action
import { postActions } from "@src/store/reducers";

// types
import type { AxiosResponse } from "axios";
import type { PayloadAction } from "@reduxjs/toolkit";
import type { LoadPostsResponse, LoadPostsBody } from "@src/store/types";

// api
import { apiLoadPosts } from "@src/store/api";

function* loadPosts(action: PayloadAction<LoadPostsBody>) {
  try {
    // api ์š”์ฒญ ๋ฐ ์‘๋‹ต ๋Œ€๊ธฐ
    const { data }: AxiosResponse<LoadPostsResponse> = yield call(apiLoadPosts, action.payload);

    // ์„ฑ๊ณตํ•œ ์•ก์…˜ ๋””์ŠคํŒจ์น˜
    yield put(postActions.loadPostsSuccess(data));
  } catch (error: any) {
    console.error("postSaga loadPosts >> ", error);

    // "AxiosError"๋ผ๋ฉด ์˜ˆ์ธกํ•œ ์—๋Ÿฌ๋ผ์„œ ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ์ „์†ก๋œ ๋ฉ”์‹œ์ง€๋กœ ์‘๋‹ตํ•˜๊ณ  ์•„๋‹ˆ๋ผ๋ฉด ์•Œ ์ˆ˜ ์—†๋Š” ์„œ๋ฒ„ ์ธก ์—๋Ÿฌ๋ผ๋Š” ๋ฉ”์‹œ์ง€ ์‘๋‹ต
    // ํŒ”๋กœ์šฐํ•œ ์œ ์ €๋ฅผ ๋‹ค์‹œ ํŒ”๋กœ์šฐ, ์ข‹์•„์š” ๋ˆ„๋ฅธ ๊ฒŒ์‹œ๊ธ€์— ๋‹ค์‹œ ์ข‹์•„์š” ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ๊ฐ™์€ ๊ฒฝ์šฐ "409"๋กœ ์‘๋‹ตํ•˜๋Š”๋ฐ "axios"์—์„œ "2xx"๊ฐ€ ์•„๋‹ˆ๋ฉด ์—๋Ÿฌ๋กœ ์ฒ˜๋ฆฌํ•จ
    const message = (error?.name === "AxiosError") ? error.response.data.data.message : "์„œ๋ฒ„์ธก ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค. \n์ž ์‹œํ›„์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”";

    // ์‹คํŒจํ•œ ์•ก์…˜ ๋””์ŠคํŒจ์น˜
    yield put(postActions.loadPostsFailure({ status: { ok: false }, data: { message } }));
  }
}
function* watchLoadPosts() {
  // "postActions.loadPostsRequest"์˜ ์š”์ฒญ์ด ์˜ค๋ฉด "loadPosts()" ์‹คํ–‰
  yield takeLatest(postActions.loadPostsRequest, loadPosts);
}

export default function* postSaga() {
  yield all([fork(watchLoadPosts)]);
}

6. store ์ƒ์„ฑ ( ๋ฆฌ๋“€์„œ, ์‚ฌ๊ฐ€ ์ ์šฉ )

  • /src/store/configureStore.ts
import createSagaMiddleware from "redux-saga";
import { createWrapper } from "next-redux-wrapper";
import { configureStore } from "@reduxjs/toolkit";

import rootReducer from "./reducers";
import rootSaga from "./sagas";

const createStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware];
  const store = configureStore({
    reducer: rootReducer,
    middleware: middlewares,
    devTools: process.env.NEXT_PUBLIC_NODE_ENV === "development",
  });
  // ์—ฌ๊ธฐ์„œ type ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ "redux.d.ts" ์ •์˜ ํ•„์š”
  store.sagaTask = sagaMiddleware.run(rootSaga);

  return store;
};

const wrapper = createWrapper(createStore, {
  debug: process.env.NEXT_PUBLIC_NODE_ENV === "development",
});

const store = createStore();
// "useSelector()"์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž…
export type RootState = ReturnType<typeof store.getState>;

// "_app.ts"์—์„œ "wrapper.withRedux()"๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ๋จ
export default wrapper;

7. ์‚ฌ์šฉ ์˜ˆ์‹œ

import { useDispatch, useSelector } from "react-redux";

// redux + server-side-rendering
import wrapper from "@src/store/configureStore";
import { END } from "redux-saga";
import { axiosInstance } from "@src/store/api";

// actions
import { postActions } from "@src/store/reducers";

// type
import type { GetServerSideProps, GetServerSidePropsContext, NextPage } from "next";
import type { RootState } from "@src/store/configureStore";

// redux์™€ ๊ด€๋ จ ์—†๋Š” ์ฝ”๋“œ ์ƒ๋žต
const Home: NextPage = () => {
  const dispatch = useDispatch();
  const { posts, hasMorePosts, loadPostsLoading } = useSelector(({ post }: RootState) => post);
  
  // ์ž„์‹œ๋กœ ๋งŒ๋“  ์˜ˆ์‹œ
  const onClick = useCallback(() => {
    dispatch(postActions.loadPostsRequest({ lastId: -1, limit: 15 }));
  }, [dispatch])
  
  return (<></>);
};

export const getServerSideProps: GetServerSideProps =
  wrapper.getServerSideProps(
    (store) => async (context: GetServerSidePropsContext) => {
      /**
       * front-server์™€ backend-server๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ๋•Œ๋ฌธ์—
       * axios์˜ "withCredentials" ์˜ต์…˜์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์˜ ์ฟ ํ‚ค๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์—†์Œ
       * ๋”ฐ๋ผ์„œ ์ง์ ‘ axios์— ์ฟ ํ‚ค๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  ์„œ๋ฒ„๋กœ ์š”์ฒญ ํ›„ ๋‹ค์‹œ axios์˜ ์ฟ ํ‚ค๋ฅผ ์ œ๊ฑฐํ•ด์ฃผ๋Š” ๊ณผ์ •์„ ๊ฑฐ์นจ
       * ํด๋ผ์ด์–ธํŠธ๋Š” ์—ฌ๋Ÿฌ ๋Œ€์ง€๋งŒ ์„œ๋ฒ„๋Š” ํ•œ๋Œ€์ด๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ์‚ฌ์šฉํ•œ ์ฟ ํ‚ค๋Š” ๋ฐ˜๋“œ์‹œ ์ œ๊ฑฐํ•ด ์ค˜์•ผ ํ•จ
       */
      let cookie = context.req?.headers?.cookie;
      cookie = cookie ? cookie : "";
      axiosInstance.defaults.headers.Cookie = cookie;

      // ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ dispatchํ•  ๋‚ด์šฉ์„ ์ ์–ด์คŒ
      store.dispatch(postActions.loadPostsRequest({ lastId: -1, limit: 15 }));

      // ๋ฐ‘์— ๋‘ ๊ฐœ๋Š” REQUEST์ดํ›„ SUCCESS๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์ฃผ๊ฒŒ ํ•ด์ฃผ๋Š” ์ฝ”๋“œ
      store.dispatch(END);
      await store.sagaTask?.toPromise();

      // ์œ„์—์„œ ๋งํ•œ๋Œ€๋กœ axios์˜ ์ฟ ํ‚ค ์ œ๊ฑฐ
      axiosInstance.defaults.headers.Cookie = "";
      
      // ์œ„์˜ ์ž‘์—…๋“ค์ด ์ •์ƒ์ž‘๋™์„ ํ•œ๋‹ค๋ฉด ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ Œ๋”๋งํ•  ๋•Œ ์ด๋ฏธ redux์˜ store์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ์Œ
	  // ๋”ฐ๋ผ์„œ props์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ํ•„์š” ์—†์Œ
      return {
        props: {},
      };
    }
  );

export default Home;

๐Ÿ‘ ๋งˆ๋ฌด๋ฆฌ

๊ฐ„์†Œํ™”ํ•œ ์˜ˆ์‹œ ์ฝ”๋“œ๋„ ๋„ˆ๋ฌด ๋งŽ์ง€๋งŒ, ๊ธฐ์กด์— Redux๋งŒ ์‚ฌ์šฉํ•  ๋•Œ๋ณด๋‹ค๋Š” ์ฝ”๋“œ์–‘์ด ์ค„์—ˆ์Šต๋‹ˆ๋‹ค. ( ๊ธฐ์กด ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ๋“€์„œ -> 1130์ค„์—์„œ 890์ค„ )
๋ฌผ๋ก  ๊ธฐ์กด์—๋Š” ๋ถˆ๋ณ€์„ฑ์„ ์ง€ํ‚ค๋ฉด์„œ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์ต์ˆ™ํ•ด์ง€๋ฉด immer๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋งˆ์ธ๋“œ ๋•Œ๋ฌธ์— ๋ถˆ๋ณ€์„ฑ์„ ์ง€ํ‚ค๋Š๋ผ ์ฝ”๋“œ๊ฐ€ ๋งŽ์ด ๊ธธ์–ด์ง„ ์ด์œ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  Action ๊ด€๋ จํ•œ ์ฝ”๋“œ๋ฅผ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†๋‹ค๋Š” ๊ฒƒ๋„ ์žฅ์ ์ด์ง€๋งŒ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ TypeScript์™€์˜ ํ˜ธํ™˜์„ฑ์ด๋ผ๊ณ  ๋Š๊ผˆ์Šต๋‹ˆ๋‹ค.
๊ธฐ์กด์— ์ œ๋Œ€๋กœ ์ ์šฉํ•˜์ง€ ๋ชปํ•œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์ง€๋งŒ reducer, saga์—์„œ ํƒ€์ž…์ด ์ ์šฉ์ด ์•ˆ ๋๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํŽธํ–ˆ๊ณ , ์–ด๋”˜๊ฐ€ ๋งž์ถฐ์ง€์ง€ ์•Š๋Š” ๋Š๋‚Œ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Redux-Toolkit์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํƒ€์ž…์ด ์ œ๋Œ€๋กœ ์ ์šฉ๋˜๋Š” ์ ์ด ๋งค์šฐ ์ข‹์•˜์Šต๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ React.js์™€ Redux๋ฅผ ์‚ฌ์šฉํ•  ์ •๋„๋กœ ๊ณต๋ถ€ํ–ˆ๋‹ค๋ฉด ์ด๋ฏธ TypeScript๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์ด์™• Redux๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊น€์— Redux-Toolkit๊ณผ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋ฏธ๋ž˜์˜ ๋ณธ์ธ์—๊ฒŒ๋„ ๋” ์ด์ ์ด ๋˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€