๋ฒ์จ ํญํด 4์ฃผ์ฐจ (react ์ฌํ)๊ฐ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์๋์ใทr...โจ
์ฌ๊ธฐ์ ์ต๋ํ redux์ router์ ๊ธฐ๋ณธ์ ์ตํ์ผ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅผ ์ ์๋ ์๋ชฉ์ด ์๊ธธ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ ๋ค.TodoList ๋ง๋ค๊ธฐ (์ด 2๋ถ๋ก ์งํ ๋ ๋ฆฌ์กํธ ์ฌํ ์ ๋๋ค~)
๋จผ์ ๋ชจ๋๊ฐ ์๋ค์ํผ ์์์ CRA๋ก ์์์ ํ๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์๋ redux์ router๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํด๋ ๋ฐ ํ์ผ ๊ตฌ์ฑ์ ๊ธฐ๋ฅ๋ณ๋ก ์ต์ ํ ํด์ฃผ๋ ๊ฒ์ด ์ข๋ค๊ณ ์๊ฐํ๊ธฐ์ ๋จผ์ ํด๋ ๋ฐ ํ์ผ ๊ตฌ์ฑ์ ๋์๊ธฐ๋ก ํ๋ค.
์์ ๊ทธ๋ฆผ์ ์ปดํฌ๋ํธ ํ์ผ์ ๋ชจ์ components, ๋ผ์ฐํฐ ์ค์ ํ์ผ์ ๋ณด๊ดํ router, ๋ผ์ฐํฐ๋ก ์ฐ๊ฒฐํ ํ์ด์ง๋ค์ ๋ชจ์ pages, ๋ง์ง๋ง์ผ๋ก redux ์ค์ ํ์ผ, ๋ชจ๋ ํ์ผ์ ๊ฐ๊ฐ ํ์ํด๋๋ก ๊ตฌ๋ถ ํ ๊ทธ ์์ ํด๋๋ฅผ redux ๋ก ์ค์ ํ์๋ค.
๋ฌผ๋ก redux์ router๋ ๊ฐ๊ฐ npm i redux react-redux
, npm i react-router-dom
์ผ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ์๋ค.
๋จผ์ ์น์๋น์ค ๋ชจ์์ ์ง์ฃผ๋ ๊ฒ์ด ์ด๊ธฐ์ ๊ฐ์ฅ ์ค์ํ๋ค๊ณ ์๊ฐํ๋ 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
์ด์ ์ฃผ์ฐจ์์ ์งํํ๋ 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();
์ด์ ์ ๋ง๋ค์๋ modules/todoStore.js
๋ฅผ ๋ง์ ธ๋ณผ ์ฐจ๋ก์ด๋ค. ์ด ํ์ผ ์์ฒด๊ฐ Redux์ ๋ชจ๋ ๊ฒ์ ๋ณด์ฌ์ค๋ค๊ณ ๊ณผ์ธ์ด ์๋๋ค.
๋ชจ๋์ ํฌ๊ฒ Action
Action Return Function
Initial State
Reducer
๋ก ํ์ฑ ๋์ด์๋ค.
Redux๊ฐ ์ด๋ป๊ฒ ํ๋ฅด๋์ง ์์๋ณด์
// Action type
const SEND = "TODOSTORE/SEND"; // ์์๋ก ์์ฑ
const DELETE = "TODOSTORE/DELETE";
const UPDATE = "TODOSTORE/UPDATE";
const REDIRECT = "TODOSTORE/REDIRECT";
๐ก ์๋๋ ์ก์
ํ์
๋ค์ {type : "TODOSTORE/SEND"} ์ด๋ฐ์์ผ๋ก Key ๊ฐ์ ์ค์ ํด์ ๋ณด๋ด์ผ ํ์ง๋ง, ์คํ์ ์ํ์ ๋ฐฉ์ง ํ๊ธฐ ์ํด์ ๋ณ์๋ด์ ์์ ๊ฐ์ผ๋ก ์ค์ ํด ๋์ ์ก์
ํ์
๋ค์ด๋ค
์ด ์ก์
๊ฐ์ฒด๋ Reducer์ ๋ณด๋ผ ๋ช
๋ น์ด ๋๋ค.
// 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 ๋์ด์ผ ์ปดํฌ๋ํธ์์
ํ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ๋ด๋ณด๋ด์ค์ผํ๋ค.
// 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
์์ ๋ค๋ฃฐ ์์
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 ํจ์๋ ์ฃผ์์ ์ฐธ๊ณ ํด์ฃผ์ธ์๐ฅฐ