Redux์ Router ์ฌ์ฉํด์ TodoList ๋ง๋ค๊ธฐ 2์ฐจ์ด๋ค. ์ด๋ฒ์๋ ๋ผ์ฐํฐ ์๋ฆฌ์ redux ๋ฐ์ดํฐ๋ฅผ ์ปดํฌ๋ํธ์ ์ ์ฉํ ์๋ฅผ ๋ค๋ฃจ๋ TIL์ด๋ค.
Redux ๊ตฌ์กฐ ์์๋ณด๋ฌ๊ฐ๊ธฐ! (1๋ถ)
์์ํ๊ธฐ ์ ์ ๋ฆฌ์กํธ์ ๋ผ์ดํ์ฌ์ดํด์ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ๋ ค๊ณ ํ๋ค.
๋จผ์ LifeCycle์ ํ๊ตญ์ด๋ก '์๋ช
์ฃผ๊ธฐ' ๋ผ๊ณ ํ๋ค.
react์์์ LifeCycle์ components์ ์๋ช
์ฃผ๊ธฐ๋ฅผ ๋ปํ๋ฉฐ, mount(์์ฑ) -> updating(์์ ) -> unmount(์ ๊ฑฐ) ๋ฐฉ์์ผ๋ก ์ด๋ฃจ์ด์ง๋ค.
react class components ๊ฐ LifeCycle ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ , ํจ์ํ ์ปดํฌ๋ํธ๋ Hook์ ์ฌ์ฉํ๋ค.
Hook์ ๊ดํด์๋ ํ๋จ์์ ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ฑํ๋ฉด์ ์ฐ์ด๋ ๊ฒ์ ์ค๋ช
ํ๋๋ก ํ๊ฒ ๋ค.
constructor
์ปดํฌ๋ํธ ์์ฑ์ ๋ฉ์๋๋ก, ์์ฑ๋๋ฉด ๊ฐ์ฅ ๋จผ์ ์คํ๋๋ ๋ฉ์๋. this.props, this.state์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๊ณ ๋ฆฌ์กํธ ์์๋ฅผ ๋ฐํ
getDerivedStateFromProps
props๋ก๋ถํฐ ํ์๋ state๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ฆ props๋ก ๋ฐ์์จ ๊ฒ์ state์ ๋ฃ์ด์ฃผ๊ณ ์ถ์ ๋ ์ฌ์ฉ
render
์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ ๋ฉ์๋
componentDidMount
์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ๋๋ค. ์ปดํฌ๋ํธ์ ์ฒซ๋ฒ์งธ ๋ ๋๋ง์ด ๋ง์น๋ฉด ํธ์ถ๋๋ ๋ฉ์๋
getDerivedStateFromProps
์ปดํฌ๋ํธ์ props๋ state๊ฐ ๋ฐ๋์์ ๋ ํธ์ถ
shouldComponentUpdate
์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ํ ์ง ๋ง์ง๋ฅผ ๊ฒฐ์ ํ๋ ๋ฉ์๋
componentDidUpdate
์ปดํฌ๋ํธ๊ฐ ์
๋ฐ์ดํธ ๋๊ณ ๋ ํ ๋ฐ์
๐ก react-router-dom : ์ฌ๋ฌ ํ์ด์ง๋ฅผ ๊ฐ์ง ์น์ ๋ง๋ค ์ ์๊ฒ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
npm install react-router-dom
components
ํด๋ ์์ ์ปดํฌ๋ํธ๋ค์ todoํ์ด์ง ํ๋์ ๋ชจ๋ ๊ทธ๋ฆด ๊ฒ์ด๋ผ pages๋ผ๋ ํด๋์ Home.jsx์ ์ด๋ฐ ํ์์ผ๋ก ํ์ฉ์ ํ์๋ค.
pages
๋ผ๋ ํด๋๋ ๋ง๊ทธ๋๋ก ํ์ด์ง๋ค์ ๋ณด๊ดํ๋ ํด๋๋ก ์ค์ ์ ํ์๋ค. ์์ Detail.jsx์ Home.jsx๊ฐ ์์ผ๋ ์ด ํ๋ก์ ํธ์์ ๋ง๋๋ ํ์ด์ง ์๋ ์ด 2๊ฐ์ธ ๊ฒ์ด๋ค.
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๋ฅผ ํ์ฑํ ์์ผ๋ณด๋ ค๊ณ ํ๋ค.
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๋ฅผ ์ฌ์ฉํ๋ค.
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;
`;
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;
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;
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;