๐Ÿ“† 22.10.10 - Redux, Router๋ฅผ TodoList์— ์ ์šฉํ•ด๋ณด๊ธฐ - 1

๋ฒ„๋“คยท2022๋…„ 10์›” 10์ผ
0

โœจToday I Learn (TIL)

๋ชฉ๋ก ๋ณด๊ธฐ
5/62
post-custom-banner

Before the start..๐Ÿš—


๋ฒŒ์จ ํ•ญํ•ด 4์ฃผ์ฐจ (react ์‹ฌํ™”)๊ฐ€ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘๋˜์—ˆใ„ทr...โœจ
์—ฌ๊ธฐ์„œ ์ตœ๋Œ€ํ•œ redux์™€ router์˜ ๊ธฐ๋ณธ์„ ์ตํ˜€์•ผ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ๋Š” ์•ˆ๋ชฉ์ด ์ƒ๊ธธ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.

TodoList ๋งŒ๋“ค๊ธฐ (์ด 2๋ถ€๋กœ ์ง„ํ–‰ ๋  ๋ฆฌ์•กํŠธ ์‹ฌํ™” ์ž…๋‹ˆ๋‹ค~)



1. React ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ์ƒ์„ฑ

๋จผ์ € ๋ชจ๋‘๊ฐ€ ์•Œ๋‹ค์‹œํ”ผ ์‹œ์ž‘์€ CRA๋กœ ์‹œ์ž‘์„ ํ•œ๋‹ค.

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” redux์™€ router๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋” ๋ฐ ํŒŒ์ผ ๊ตฌ์„ฑ์„ ๊ธฐ๋Šฅ๋ณ„๋กœ ์ตœ์ ํ™” ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ์— ๋จผ์ € ํด๋” ๋ฐ ํŒŒ์ผ ๊ตฌ์„ฑ์— ๋‚˜์„œ๊ธฐ๋กœ ํ–ˆ๋‹ค.

์œ„์˜ ๊ทธ๋ฆผ์€ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ๋ชจ์€ components, ๋ผ์šฐํ„ฐ ์„ค์ • ํŒŒ์ผ์„ ๋ณด๊ด€ํ•œ router, ๋ผ์šฐํ„ฐ๋กœ ์—ฐ๊ฒฐํ•  ํŽ˜์ด์ง€๋“ค์„ ๋ชจ์€ pages, ๋งˆ์ง€๋ง‰์œผ๋กœ redux ์„ค์ • ํŒŒ์ผ, ๋ชจ๋“ˆ ํŒŒ์ผ์„ ๊ฐ๊ฐ ํ•˜์œ„ํด๋”๋กœ ๊ตฌ๋ถ„ ํ›„ ๊ทธ ์ƒ์œ„ ํด๋”๋ฅผ redux ๋กœ ์„ค์ •ํ•˜์˜€๋‹ค.

๋ฌผ๋ก  redux์™€ router๋Š” ๊ฐ๊ฐ npm i redux react-redux, npm i react-router-dom ์œผ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜์˜€๋‹ค.


2. ์ปดํฌ๋„ŒํŠธ ๋‚˜๋ˆ„๊ธฐ

๋จผ์ € ์›น์„œ๋น„์Šค ๋ชจ์–‘์„ ์งœ์ฃผ๋Š” ๊ฒƒ์ด ์ดˆ๊ธฐ์— ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” 1ไบบ์ด๋‹ค. ์ด๋Ÿฐ๊ฑธ ๋ณดํ†ต ์™€์ด์–ด ํ”„๋ ˆ์ž„์ด๋ผ ํ•˜๋Š”๋ฐ, ์ปดํฌ๋„ŒํŠธ ํ˜•์„ฑ ๋˜ํ•œ, ์™€์ด์–ด ํ”„๋ ˆ์ž„๊ณผ ๊ฒฐ์ด ๋น„์Šทํ•˜๋‹ค๊ณ  ๋Š๋‚€๋‹ค.
์ด๋ฒˆ react๋กœ ๋งŒ๋“œ๋Š” todo-list๋Š” ํฌ๊ฒŒ Header(์ƒ๋‹จ), TodoForm(์ž…๋ ฅ ๊ณต๊ฐ„), TodoBox(Todo ๋“ค์„ ๋‹ด์•„์ฃผ๋Š” ๊ณต๊ฐ„), Todo(Todo list ๊ทธ ์ž์ฒด)๋กœ componentsํด๋” ์•ˆ์— jsx ํ˜•์‹์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ถ”ํ›„์— ๋ผ์šฐํŒ…์„ ํ•˜๊ธฐ ์œ„ํ•ด ์ด ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ ๋ถ€๋ชจ ์š”์†Œ๋ฅผ Home.jsx๋ผ๋Š” ํŒŒ์ผ๋กœ ํŽ˜์ด์ง•์„ ํ•œ ํ›„์— pages ํด๋”์— ์ƒ์„ฑํ•˜์˜€๋‹ค.

์•„๋ž˜๋Š” Home.jsx ์˜ ์ผ๋ถ€์ด๋‹ค.

import "../App.css";
import styled from "styled-components";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";

import Header from "../components/Header";
import TodoBox from "../components/TodoBox";
import TodoForm from "../components/TodoForm";

function Home() {
  const todo = useSelector((state) => state.todoStore.list);
  const param = useParams();
  console.log(todo);
  console.log(param);
  return (
    <>
      <Header />
      <StHome>
        <TodoForm />
        <TodoBox />
      </StHome>
    </>
  );
}

export default Home;

const StHome = styled.div`
  max-width: 1200px;
  min-width: 800px;
  max-height: 100vh;
  margin: 20px auto;
  padding: 10px;
  background: rgba(255, 255, 255, 0.35);
  box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 45px;
  border: 1px solid rgba(255, 255, 255, 0.28);
`;

๋จผ์ € ์™€์ด์–ด ํ”„๋ ˆ์ž„๋Œ€๋กœ Header -> TodoForm -> TodoBox ์‹์œผ๋กœ ๋‚˜ํƒ€๋‚ด์—ˆ๊ณ (๋ถ€๋ชจ ์ž์‹ ์š”์†Œ๊ฐ€ ์•„๋‹Œ ๋™๋“ฑํ•ฉ๋‹ˆ๋‹ค.), Todo.jsx ๋Š” ์œ„์—์„œ ๋งํ•œ๋Œ€๋กœ TodoBox๋‚ด์—์„œ map์œผ๋กœ ๋ฐ›์€ ๊ฐ’์— ๋”ฐ๋ผ์„œ ๋‚˜์˜ค๋Š” ์š”์†Œ๋กœ ์ •๋ฆฌํ•  ๊ณ„ํš์ด๋‹ค.
( StHome ์ด๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” styled-components๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋‚˜์˜ ์ฐฝ์„ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— ํ‘œ์‹œ๋  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž์‹์š”์†Œ๋กœ ๋„ฃ์—ˆ๋‹ค.)

js ์š”์†Œ๊ฐ€ ๋งŽ์ด ์•ˆ๋“ค์–ด๊ฐ„ Home.jsx ์™€ Header.jsx ๋Š” ์ปดํฌ๋„ŒํŠธ ๊ด€๊ณ„์™€ css ์ฒ˜๋ฆฌ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค. ๋ฌธ์ œ๋Š” ๋‚˜๋จธ์ง€ ์ปดํฌ๋„ŒํŠธ ๋“ค์ด๋‹ค..

โ€ป ์ด์ œ๋ถ€ํ„ฐ ์–ด๋ ค์šด ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐ„๋‹ค..redux


3. Redux๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๊ด€๋ฆฌ

์ดˆ๊ธฐ ์„ค์ •

์ด์ „ ์ฃผ์ฐจ์—์„œ ์ง„ํ–‰ํ–ˆ๋˜ todo๋Š” prop์™€ state๋กœ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ์—ˆ๋‹ค๋ฉด, ์ด๋ฒˆ ์ฃผ์ฐจ์—์„œ๋Š” Redux๋ฅผ ํ†ตํ•ด props drilling์„ ํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

Redux๋Š” ์„ค์ •ํŒŒ์ผ๊ณผ state ๊ฐ’๋“ค์„ ๋ชจ์•„๋‘๋Š” store ํŒŒ์ผ๋กœ ํฌ๊ฒŒ ๋ถ„๋ฅ˜๊ฐ€ ๋œ๋‹ค.

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” config ํด๋”์— configStore.js. ๋ผ๋Š” ์„ค์ •ํŒŒ์ผ, moduleใ„ด ํด๋”์— todoStore ์ด๋ผ๋Š” store ํŒŒ์ผ์„ ์ƒ์„ฑํ–ˆ๋‹ค.

๋จผ์ € ์„ค์ •ํŒŒ์ผ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

// createStore is not recommended para
import { createStore } from "redux";
import { combineReducers } from "redux";

// todoStore์ด๋ผ๋Š” storeํŒŒ์ผ์„ ๊บผ๋‚ด์˜จ๋‹ค.
import todoStore from "../modules/todoStore";

// ๊ทธ๋ฆฌ๊ณ  ๊ทธ store ํŒŒ์ผ์„ reducer๋กœ ํ™œ์šฉํ•œ๋‹ค
const rootReducer = combineReducers({
  todoStore,
});

// ์œ„ ์ž‘์—…์„ ๋ณ€์ˆ˜์— ๋„ฃ๊ณ  export
const store = createStore(rootReducer);

export default store;

createStore๋Š” ์กฐ๋งŒ๊ฐ„ ์—†์–ด์งˆ ๋ชจ์–‘์ด๋‹ค. ๋‹ค์Œ์—๋Š” toolkit์„ ์จ๋ณด์ž..!

We recommend using the configureStore method of the @reduxjs/toolkit package, which replaces createStore.

๊ทธ ํ›„ ์ตœ์ƒ๋‹จ ๋””๋ ‰ํ† ๋ฆฌ์˜ index.js๋ฅผ ์ˆ˜์ •ํ•ด์ค€๋‹ค. ์•„๊นŒ export default ํ•œ store๋ฅผ ์ธ์‹ ์‹œ์ผœ์ฃผ๋ ค๊ณ  ๊ทธ๋Ÿฐ ๊ฒƒ ๊ฐ™๋‹ค.

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

/* for redux */
import store from "./redux/config/configStore";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

reportWebVitals();

module ๋งŒ์ง€๊ธฐ

์ด์ „์— ๋งŒ๋“ค์—ˆ๋˜ modules/todoStore.js ๋ฅผ ๋งŒ์ ธ๋ณผ ์ฐจ๋ก€์ด๋‹ค. ์ด ํŒŒ์ผ ์ž์ฒด๊ฐ€ Redux์˜ ๋ชจ๋“  ๊ฒƒ์„ ๋ณด์—ฌ์ค€๋‹ค๊ณ  ๊ณผ์–ธ์ด ์•„๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์€ ํฌ๊ฒŒ Action Action Return Function Initial State Reducer ๋กœ ํ˜•์„ฑ ๋˜์–ด์žˆ๋‹ค.

Redux๊ฐ€ ์–ด๋–ป๊ฒŒ ํ๋ฅด๋Š”์ง€ ์•Œ์•„๋ณด์ž

Action

// Action type
const SEND = "TODOSTORE/SEND"; // ์ƒ์ˆ˜๋กœ ์ƒ์„ฑ
const DELETE = "TODOSTORE/DELETE";
const UPDATE = "TODOSTORE/UPDATE";
const REDIRECT = "TODOSTORE/REDIRECT";
๐Ÿ’ก ์›๋ž˜๋Š” ์•ก์…˜ ํƒ€์ž…๋“ค์„ {type : "TODOSTORE/SEND"} ์ด๋Ÿฐ์‹์œผ๋กœ Key ๊ฐ’์„ ์„ค์ •ํ•ด์„œ ๋ณด๋‚ด์•ผ ํ•˜์ง€๋งŒ, ์˜คํƒ€์˜ ์œ„ํ—˜์„ ๋ฐฉ์ง€ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ณ€์ˆ˜๋‚ด์— ์ƒ์ˆ˜ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด ๋†“์€ ์•ก์…˜ ํƒ€์ž…๋“ค์ด๋‹ค
์ด ์•ก์…˜๊ฐ์ฒด๋Š” Reducer์— ๋ณด๋‚ผ ๋ช…๋ น์ด ๋œ๋‹ค.

Action Return Function

// Action type

export const sendTodo = (payload) => {
  return {
    type: SEND,
    payload,
  };
};

export const deleteTodo = (payload) => {
  return {
    type: DELETE,
    payload,
  };
};

// payload value : id, isDone
export const updateTodo = (payload) => {
  return {
    type: UPDATE,
    payload,
  };
};

export const redirectTodo = (id) => {
  return {
    type: REDIRECT,
    id,
  };
};
๐Ÿ’ก ์œ„์˜ ์•ก์…˜ ๊ฐ์ฒด๋“ค์„ ๊ฐ€์ง€๊ณ  ์•ก์…˜ ๋ฐ˜ํ™˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค. ์œ„์˜ ํ•จ์ˆ˜๋“ค์€ export ๋˜์–ด์•ผ ์ปดํฌ๋„ŒํŠธ์—์„œ 
ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ๋‚ด๋ณด๋‚ด์ค˜์•ผํ•œ๋‹ค. 

Initial State

// Action type
const initialState = {
  list: [
    {
      id: 1,
      title: "react",
      content: "react๋ฅผ ์•Œ์•„๋ด์š”",
      isDone: true,
    },
    {
      id: 2,
      title: "vue",
      content: "vue๋ฅผ ์•Œ์•„๋ด์š”",
      isDone: false,
    },
  ],

  /* ์ƒ์„ธํŽ˜์ด์ง€์—์„œ'๋งŒ' ์“ฐ์ด๊ธฐ ์œ„ํ•œ  */
  void: [
    {
      id: "0",
      title: "",
      content: "",
      isDone: false,
    },
  ],
};

๐Ÿ’ก Initial State๋ž€, state์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์˜๋ฏธํ•œ๋‹ค.
์—ฌ๊ธฐ์„œ list๋ผ๋Š” ๋ฐฐ์—ด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ์›น์„œ๋น„์Šค๊ฐ€ ์ฒ˜์Œ ๋ Œ๋”๋ง ๋  ๋•Œ ๊ธฐ๋ณธ์œผ๋กœ ๋ณด์—ฌ์ค„ todo๋“ค์„ ๋งŒ๋“ค์–ด ์ค„ ๊ฒƒ์ด๋‹ค.
key๊ฐ’์ด ๋  id, ์ œ๋ชฉ์˜ title, ๋‚ด์šฉ์˜ content, ๋งˆ์ง€๋ง‰์œผ๋กœ ์ง„ํ–‰ ์ค‘์„ ํŒ๋ณ„ํ•  boolean ํƒ€์ž…์˜ isDone์„ ๊ฐ์ฒด ์š”์†Œ๋กœ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

void๋ผ๋Š” ๋ฐฐ์—ด ๊ฐ์ฒด๋Š” ์ถ”ํ›„์— Detail.jsx ์—์„œ ๋‹ค๋ฃฐ ์˜ˆ์ •

Reducer

const todoStore = (state = initialState, action) => {
  switch (action.type) {
    case SEND: {
      return {
        ...state, // ๊นŠ์€๋ณต์‚ฌ๋กœ state๋ฅผ ์ดˆ๊ธฐํ™”
 /* list ๊ฐ’์— useState ์ฒ˜๋Ÿผ list๊ฐ’์„ ๊นŠ์€ ๋ณต์‚ฌ ํ›„์— payload๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ๋ชจ๋“  ๊ฐ์ฒด ๊ฐ’์„ ์‚ฝ์ž… (Create)*/
        list: [...state.list, action.payload], 
      };
    }
    case UPDATE: {
      return {
        // state ๊นŠ์€ ๋ณต์‚ฌ
        ...state,
        list: state.list.map((todo) => {
   // ๋งŒ์•ฝ์— ์„ ํƒ ๊ฐ์ฒด์˜ id์™€ ์ผ์น˜ํ•˜๋ฉด
          if (todo.id === action.payload) {
            return {
              // ๊ทธ ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌ
              ...todo,
              // ๊ทธ ๊ฐ์ฒด์˜ isDone ๊ฐ’์„ ๋ฐ˜๋Œ€๋กœ
              isDone: !todo.isDone,
            };
          } else {
            // ์•„๋‹ˆ๋ผ๋ฉด ๊ทธ๋ƒฅ ๋ฐ˜ํ™˜ (Update)
            return todo;
          }
        }),
      };
    }
    case DELETE: {
      return {
        // state ๊นŠ์€ ๋ณต์‚ฌ
        ...state,
   /* state์—์„œ list ๋ฐฐ์—ด์—์„œ ํ•ด๋‹น ๊ฐ์ฒด์˜ id๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฐ์ฒด๋“ค๋งŒ filterํ•˜์—ฌ
   ์ƒˆ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ (Delete)*/
        list: state.list.filter((todo) => todo.id !== action.payload),
      };
    }
      
  
    case REDIRECT: {
      return {
        // state ๊นŠ์€ ๋ณต์‚ฌ
        ...state,
      /* id ๋งŒ ํ•„์š”ํ•œ ๋นˆ ๊ฐ์ฒด์—์„œ list ๋ฐฐ์—ด์— ์ ‘๊ทผ ํ›„ ์„ ํƒํ•œ id ๋งŒ ๋ฐ˜ํ™˜ (Read ํ›„์— router๋ฅผ ํ†ตํ•ด ์ฝ์„ ๋•Œ ์“ฐ์ผ ์˜ˆ์ •) */
        void: state.list.find((todo) => {
          return todo.id === action.id;
        }),
      };
    }
    default:
      return state;
  }
};
๐Ÿ’ก Reducer ๋Š” store ๋‚ด์˜ state ๊ฐ’์— ๋ณ€ํ™”๋ฅผ ์ฃผ๋Š” ํ•จ์ˆ˜๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

๋จผ์ € const todoStore = (state = initialState, action) ๋ฅผ ๋ณด๋ฉด parameter๊ฐ€ state = initialState action์„ ๋ฐ›๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์„ ์ž๋Š” state ๊ฐ’์œผ๋กœ initialState๋ฅผ ๋ฐ›์•„์„œ ์“ด๋‹ค๋Š” ์˜๋ฏธ๋ฉฐ, action ๊ฐ์ฒด๋ฅผ ๊บผ๋‚ด์–ด ์‚ฌ์šฉํ•œ๋‹ค.

switch ๋ฌธ์„ ํ†ตํ•ด์„œ ์•ก์…˜ ํƒ€์ž…๋ณ„ state์— ์˜ํ•ด ๋ณ€ํ™”๋ฅผ ์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๊ธฐ ์ด Reducer์—์„œ CRUD (Create, Read, Update, Delete) ๋ฅผ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฐ๊ฐ reducer ํ•จ์ˆ˜๋Š” ์ฃผ์„์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”๐Ÿฅฐ

profile
ํƒœ์–ด๋‚œ ๊น€์— ๋งŽ์€ ๊ฒฝํ—˜์„ ํ•˜๋ ค๊ณ  ์•„๋“ฑ๋ฐ”๋“ฑ ์• ์“ฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž
post-custom-banner

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