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

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

โœจToday I Learn (TIL)

๋ชฉ๋ก ๋ณด๊ธฐ
6/58
post-thumbnail

Redux์™€ Router ์‚ฌ์šฉํ•ด์„œ TodoList ๋งŒ๋“ค๊ธฐ 2์ฐจ์ด๋‹ค. ์ด๋ฒˆ์—๋Š” ๋ผ์šฐํ„ฐ ์›๋ฆฌ์™€ redux ๋ฐ์ดํ„ฐ๋ฅผ ์ปดํฌ๋„ŒํŠธ์— ์ ์šฉํ•œ ์˜ˆ๋ฅผ ๋‹ค๋ฃจ๋Š” TIL์ด๋‹ค.
Redux ๊ตฌ์กฐ ์•Œ์•„๋ณด๋Ÿฌ๊ฐ€๊ธฐ! (1๋ถ€)


0. react lifecycle

์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๋ฆฌ์•กํŠธ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋ ค๊ณ ํ•œ๋‹ค.
๋จผ์ € LifeCycle์€ ํ•œ๊ตญ์–ด๋กœ '์ƒ๋ช…์ฃผ๊ธฐ' ๋ผ๊ณ  ํ•œ๋‹ค. 
react์—์„œ์˜ LifeCycle์€ components์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋œปํ•˜๋ฉฐ, mount(์ƒ์„ฑ) -> updating(์ˆ˜์ •) -> unmount(์ œ๊ฑฐ) ๋ฐฉ์‹์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

react class components ๊ฐ€ LifeCycle ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋Š” Hook์„ ์‚ฌ์šฉํ•œ๋‹ค.
Hook์— ๊ด€ํ•ด์„œ๋Š” ํ•˜๋‹จ์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด์„œ ์“ฐ์ด๋Š” ๊ฒƒ์„ ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

Class Components LifeCycle

mount

  • constructor
    ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ์ž ๋ฉ”์„œ๋“œ๋กœ, ์ƒ์„ฑ๋˜๋ฉด ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ. this.props, this.state์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ณ  ๋ฆฌ์•กํŠธ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜

  • getDerivedStateFromProps
    props๋กœ๋ถ€ํ„ฐ ํŒŒ์ƒ๋œ state๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ฆ‰ props๋กœ ๋ฐ›์•„์˜จ ๊ฒƒ์„ state์— ๋„ฃ์–ด์ฃผ๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ

  • render
    ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฉ”์„œ๋“œ

  • componentDidMount
    ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋œ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ์ฒซ๋ฒˆ์งธ ๋ Œ๋”๋ง์ด ๋งˆ์น˜๋ฉด ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ

updating

  • getDerivedStateFromProps
    ์ปดํฌ๋„ŒํŠธ์˜ props๋‚˜ state๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ํ˜ธ์ถœ

  • shouldComponentUpdate
    ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ํ• ์ง€ ๋ง์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ

  • componentDidUpdate
    ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋˜๊ณ  ๋‚œ ํ›„ ๋ฐœ์ƒ

unmount

  • componentWillUnmount
    ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง€๊ธฐ ์ „์— ํ˜ธ์ถœ

1. Router ์„ค์ •

๐Ÿ’ก react-router-dom : ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ง„ ์›น์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

1. react-router-dom ์„ค์น˜

npm install react-router-dom

2. router ์ ์šฉ ์ˆœ์„œ

  • page Components
    : ex ) ์ด todo ์˜ˆ์ œ๋Š” ์ด๋Ÿฐ ๊ตฌ์กฐ๋กœ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆด๋‹ค.

components ํด๋” ์•ˆ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์€ todoํŽ˜์ด์ง€ ํ•˜๋‚˜์— ๋ชจ๋‘ ๊ทธ๋ฆด ๊ฒƒ์ด๋ผ pages๋ผ๋Š” ํด๋”์— Home.jsx์— ์ด๋Ÿฐ ํ˜•์‹์œผ๋กœ ํ™œ์šฉ์„ ํ•˜์˜€๋‹ค.

pages๋ผ๋Š” ํด๋”๋Š” ๋ง๊ทธ๋Œ€๋กœ ํŽ˜์ด์ง€๋“ค์„ ๋ณด๊ด€ํ•˜๋Š” ํด๋”๋กœ ์„ค์ •์„ ํ•˜์˜€๋‹ค. ์œ„์˜ Detail.jsx์™€ Home.jsx๊ฐ€ ์žˆ์œผ๋‹ˆ ์ด ํ”„๋กœ์ ํŠธ์—์„œ ๋งŒ๋“œ๋Š” ํŽ˜์ด์ง€ ์ˆ˜๋Š” ์ด 2๊ฐœ์ธ ๊ฒƒ์ด๋‹ค.

Home.jsx

function Home() {
  const todo = useSelector((state) => state.todoStore.list);
  const param = useParams();
  console.log(todo);
  console.log(param);
  return (
    <>
      <Header />
      <StHome>     {/* StHome์€ ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆˆ๊ฑฐ์—์š” */}
        <TodoForm />
        <TodoBox />
      </StHome>
    </>
  );
}

์ž, ์ด์ œ ํŽ˜์ด์ง€์— ์–ด๋–ค ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ๊ฒƒ์ธ์ง€ ๋Œ€๊ฐ• ๋‚˜์™”์œผ๋‹ˆ Router๋ฅผ ํ™œ์„ฑํ™” ์‹œ์ผœ๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

Router.jsx

import React from "react";
// 1. react-router-dom์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•„๋ž˜ API๋“ค์„ import ํ•ฉ๋‹ˆ๋‹ค.
import Detail from "../pages/Detail";
import Home from "../pages/Home";
import { BrowserRouter, Route, Routes, Link } from "react-router-dom";

// 2. Router ๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        {/* home /  element ์š”์†Œ๋กœ ์ปดํฌ๋„ŒํŠธ ๋„ฃ๊ธฐ*/}
        <Route path="/" element={<Home />}></Route>
        {/* id๊ฐ’์— ๋”ฐ๋ผ์„œ dynamic route ์„ค์ • ์•„๋ž˜๋Š” /:id ๋กœ id ๊ฐ’์— ๋”ฐ๋ผ 
        ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€ ์ƒ์„ฑ */}
        <Route path="detail/:id" element={<Detail />}></Route>
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
์œ„์˜ ํŒŒ์ผ์€ ๋ผ์šฐํŒ…์„ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์ฃผ๋Š” Router์˜ ํ•ต์‹ฌ ํŒŒ์ผ์ด๋‹ค. ์œ„์˜ importํ•œ Detail๊ณผ 
Home์„ ๋ณด๋ฉด ์•„๊นŒ ๋งŒ๋“  pages์˜ ํด๋”์˜ ํŒŒ์ผ๋“ค์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ณด์ธ๋‹ค. 
Detail ํŽ˜์ด์ง€์™€ Home์„ Router.jsx๋ฅผ ํ†ตํ•ด์„œ ์—ฐ๊ฒฐํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  detail ์ปดํฌ๋„ŒํŠธ๋Š” ์ถ”ํ›„์— id๊ฐ’์— ๋”ฐ๋ผ ํŽ˜์ด์ง€์— ๋ Œ๋”๋ง ๋˜๋Š” ๊ฐ’์„ ๋‹ฌ๋ฆฌ ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, 
dynamic route๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

TodoForm.jsx

import "../App.css";
import styled from "styled-components";
import { useRef, useEffect } from "react";
// useDispatch ๋กœ redux reducer ์‚ฌ์šฉ
import { useDispatch } from "react-redux";
import { sendTodo } from "../redux/modules/todoStore"; // action์ด ๋‹ค์ˆ˜๋ฉด, ํ•ด๋‹น ํ•จ์ˆ˜์˜ action ๊ฐ์ฒด๋ฅผ import

function TodoForm() {
  const dispatch = useDispatch();
  const refTitle = useRef();
  const refContent = useRef(); // ref or event.target usually recomended?
  // todoId์— ref์œผ๋กœ ์ง€์ •ํ•œ ๋ถ€๋ถ„์— 3์„ ๋Œ€์ž…
  const todoId = useRef(3);

  // useEffect hook์œผ๋กœ ๋จผ์ € ๋ Œ๋”๋ง๋ ๋•Œ dom์„ ์ง€์ •ํ•˜๋ฉด ๋œ๋‹ค๊ณ  ํ•˜๊ธฐ๋Š” ํ•จ..

  const sendItem = (event) => {
    // ref ์œผ๋กœ ์ง€์ •ํ•œ ์š”์†Œ์˜ ํ˜„์žฌ ๊ฐ’์„ ์ €์žฅ
    const title = refTitle.current.value;
    const content = refContent.current.value;
    //console.log(original); // useState ํ™•์ธ ์šฉ

    // ์ƒˆ๋กœ๊ณ ์นจ์ด ๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ๋ผ๊ฐ€์ง€ ์•Š๊ฒŒ ์ด๋ฒคํŠธ ๋ง‰๊ธฐ (form ์†์„ฑ)
    event.preventDefault();
    if (title.trim() === "" || content.trim() === "") return null;

    //  reducer์—์„œ sendTodo๋ฅผ ๋ถˆ๋Ÿฌ์™€ payload ๊ฐ’์— ํ•ด๋‹น ๊ฐ’๋“ค ๋Œ€์ž…
    dispatch(
      sendTodo({
        id: todoId.current,
        title: title,
        content: content,
        isDone: false,
      })
    );
    refTitle.current.value = "";
    refContent.current.value = "";
    todoId.current += 1;
  };

  useEffect(() => {
    refContent.current.addEventListener("keypress", function (event) {
      if (event.key === "Enter") {
        sendItem();
      }
    });
  });

  return (
    <StTdForm onSubmit={sendItem}>
      <StIContainer>
        <h2>title</h2>
        <StInput
          type="text"
          name="title"
          ref={refTitle}
          placeholder="Type here"
        />
      </StIContainer>
      <StIContainer>
        <h2>content</h2>
        <StInput
          type="text"
          name="content"
          ref={refContent}
          placeholder="Type here"
          //onKeyUp={keyControl}
          //onKeyPress={enterItem}
          // -> this feature is no longer recommended
        />
      </StIContainer>
      <StButton type="submit" className="btn glass ml-[10px]">
        Add Item
      </StButton>
    </StTdForm>
  );
}

export default TodoForm;

`;

TodoBox.jsx

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

function TodoBox() {
  /* ๊ตฌ์กฐ ๊ฐ์ฒด ๋ถ„ํ•ด๋ฅผ ํ•˜์—ฌ ์‹ค์ œ todo > list ์˜ ์ฃผ์†Œ์— ์ ‘๊ทผ map ์œผ๋กœ isDone ๊ฐ’์ด false
  ์ธ ๊ฒƒ๋งŒ ์ถ”์ถœํ•˜์—ฌ ์ƒˆ ๋ฐฐ์—ด ํ˜•์„ฑ
  */
  const { list } = useSelector((state) => state.todoStore);

  return (
    <form>
      <StH1>Working.. ๐Ÿ”ฅ</StH1>
      <StTodoBox>
        // ๊ตฌ์กฐ ๊ฐ์ฒด ๋ถ„ํ•ด๋ฅผ ํ•˜์—ฌ ์–ป์€ list ๋ฐฐ์—ด ์ฃผ์†Œ์—
        {list.map((todo) => {
          if (todo.isDone == 0) {
            return (
              // !!! ์ค‘์•™์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ์“ด๋‹ค๊ณ  props๋ฅผ ์•ˆ์“ด๋‹ค๋Š” ์ƒ๊ฐ์„ ๋ฒ„๋ฆฌ์ž
              // Todo ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— key, title, content, todo๋ฅผ prop๋กœ ๋ณด๋‚ธ๋‹ค
              <Todo
                key={todo.id}
                title={todo.title}
                content={todo.content}
                todo={todo}
              />
            );
          } else {
            return null;
          }
        })}
      </StTodoBox>
      <StHr />
      <StH1>Done..! ๐ŸŽ‰</StH1>
      <StTodoBox>
        //์œ„์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ isDone์ด True ์ธ ๊ฐ’๋งŒ ๋นผ์„œ ์ƒˆ ๋ฐฐ์—ด๋กœ ์ƒ์„ฑํ•œ๋‹ค
        {list.map((todo) => {
          if (todo.isDone == 1) {
            return (
              <Todo
                key={todo.id}
                title={todo.title}
                content={todo.content}
                todo={todo}
              />
            );
          } else {
            return null;
          }
        })}
      </StTodoBox>
    </form>
  );
}

export default TodoBox;

Todo.jsx

import "../App.css";
import styled from "styled-components";
import { useSelector, useDispatch } from "react-redux";
import { deleteTodo, updateTodo } from "../redux/modules/todoStore";
import { useParams, Link } from "react-router-dom";

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ TodoBox์—์„œ props๋กœ ๋ฐ›์•„์˜ด
function Todo({ todo, title, content }) {
  const param = useParams();
  console.log(param);
  const dispatch = useDispatch();

  const deleteItem = (id) => {
    dispatch(deleteTodo(id));
  };

  const doneItem = (id) => {
    dispatch(updateTodo(id));
  };

  return (
    <StTodo>
      <StLink to={`/detail/${todo.id}`} key={todo.id}>
        {/* useNavigation = <a> ๋ธŒ๋ผ์šฐ์ € ์ƒˆ๋กœ๊ณ ์นจ๋˜์–ด ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์„ ๋ชปํ•จ -> ์ดˆ๊ธฐํ™” ๋˜์„œ
      ์ด๊ฑธ <Link> ๊ฐ€ ์ง€์ผœ์ค€๋ฐ */}
        <p>์ƒ์„ธ๋ณด๊ธฐ</p>
      </StLink>
      <StTitle> {title} </StTitle>
      <p>{content}</p>
      {/* ์ฆ‰์‹œ ์‹คํ–‰ ํ•จ์ˆ˜๋ฅผ ์ฌ ์ด์œ ๋Š” Clouser ๊ฐœ๋…*/}
      <StButton type="button" onClick={() => deleteItem(todo.id)}>
        Delete
      </StButton>
      <StButton2 type="button" onClick={() => doneItem(todo.id)}>
        {/* ๋‚˜๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆด๊ธฐ์— props๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ map์˜ current value์ธ 'todo'๋ฅผ ๋ฐ›์•„์™€์•ผ ํ•œ๋‹ค. */}
        {todo.isDone ? "Cancle" : "Done!"}
      </StButton2>
    </StTodo>
  );
}

export default Todo;

Detail.jsx

import "../App.css";
import styled from "styled-components";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { redirectTodo } from "../redux/modules/todoStore";

function Detail() {
  const dispatch = useDispatch();
  const todo = useSelector((state) => state.todoStore.void);
  console.log(todo);
  /* useParams ๋กœ ๋ผ์šฐํ„ฐ ํŽ˜์ด์ง€์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ฑฐ์ž„. ๋ญ์‹œ๊ธฐ๋ƒ, props ๊ธฐ๋Šฅ์ด ์ ˆ๋Œ€ ์•„๋‹˜
  ์ฃผ์†Œ์— ๋“ค์–ด๊ฐ€๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ชจ๋‘ STRING*/
  const { id } = useParams();
  /* ๋ Œ๋”๋ง ์‹œ ๋ฐ”๋กœ dispatch , ์˜์กด์„ฑ ๋ฐฐ์—ด ์‚ฌ์šฉ */
  useEffect(() => {
    /* ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“ค์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ •์ˆ˜ํ™”๋ฅผ ํ•ด์•ผ๋จ */
    dispatch(redirectTodo(+id));
  }, [dispatch, id]);
  const navigate = useNavigate();
  // console.log(param);
  return (
    <StDetail>
      <BtnBack type="button" onClick={() => navigate("/")}>
        ์ด์ „์œผ๋กœ
      </BtnBack>
      <StId>ID : {id}</StId>
      <StTitle> {todo.title} </StTitle>
      <StContent> {todo.content} </StContent>
    </StDetail>
  );
}

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

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