๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ“บ Front End/- React, Next.js

[React-redux] ๋Š˜ ๊ทธ๋ ‡๋“ฏ Todo List ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ฉฐ ๋ฐฐ์šฐ๋Š” ๋ฆฌ์•กํŠธ ๋ฆฌ๋•์Šค

by Wonit 2021. 6. 10.

์˜ค๋Š˜์€ React ์—์„œ Redux ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์šฐ์„  2๊ฐ€์ง€ ๊ธฐ์ˆ ์ด ์„ ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค.

  1. React์™€ React-Hooks
  2. Redux ์˜ ๊ธฐ๋ณธ Concept

์œ„ ๋‚ด์šฉ๋“ค์„ ๋ชจ๋ฅธ๋‹ค๋ฉด ์กฐ๊ธˆ ํž˜๋“ค์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์œ„ ๋งํฌ์—์„œ ํ™•์ธํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

๋ชฉ์ฐจ

  • redux์™€ react-redux์˜ ์ฐจ์ด
  • redux ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…
    • ๊ตฌ์„ฑ ์š”์†Œ
    • Provider
    • useDispatch()
    • useSelector()
  • react-redux ๋กœ todo list ๋งŒ๋“ค๊ธฐ
    • ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ
    • ์˜์กด์„ฑ ์ถ”๊ฐ€
      • styled-components
      • react-redux
    • UI ์ž‘์—…ํ•˜๊ธฐ
      • ํ™ˆ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ
    • ๋ฆฌ๋•์Šค ์ž‘์—…ํ•˜๊ธฐ
      • action type ๋งŒ๋“ค๊ธฐ
      • action creator ๋งŒ๋“ค๊ธฐ
      • reducer ๋งŒ๋“ค๊ธฐ
      • store ์ถ”๊ฐ€ํ•˜๊ธฐ

 

redux์™€ react-redux์˜ ์ฐจ์ด

์ž˜ ์•Œ๋‹ค์‹ถ์ด ๋ฆฌ๋•์Šค๋Š” Vue, Angular, Ember, Vanilla JS ์™€๋Š” ๋ณ„๊ฐœ๋กœ ๋Œ์•„๊ฐ€๋Š” ๋…๋ฆฝ์ ์ธ Javascript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

 

๋ฆฌ๋•์Šค๋Š” ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์“ธ ๋•Œ๋Š” UI ๋ฐ”์ธ๋”ฉ์„ ํ•ด์•ผ๋˜๋Š”๋ฐ, ๊ทธ๊ฒŒ ๋ฐ”๋กœ React์—์„œ๋Š” React-Redux ๋ผ๊ณ  ๋ถˆ๋ฆฐ๋‹ค.

 

react-redux, ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€ ์—์„œ ์ž์„ธํ•œ ์ปจ์…‰์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ™•์ธํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

redux์˜ ๊ธฐ๋ณธ ๊ฐœ๋…

 

redux ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๊ณผ ๊ตฌ์„ฑ์š”์†Œ์— ๋Œ€ํ•ด์„œ๋Š” ์ง€๋‚œ ์‹œ๊ฐ„ ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋˜์งš๊ณ  ๋„˜์–ด๊ฐ€๋ณด์ž.

 

๊ตฌ์„ฑ ์š”์†Œ

  • Provider
  • action
    • ์ˆœ์ˆ˜ js ๊ฐ์ฒด
      • action type
      • action payload
  • action Creator
    • action ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
  • Dispatch
    • action ์„ ๋ฆฌ๋“€์„œ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค
  • Reducer
    • action ์ด (์ƒˆ๋กœ์šด state๋ฅผ ๋ฐ˜ํ™˜)
  • Store
    • ๊ด€๋ฆฌํ•˜๋ ค๋Š” state๋ฅผ ์ €์žฅํ•˜๋Š” ๊ณณ

Provider

 

Provider ๋Š” ๋ฆฌ๋•์Šค์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” Redux ๋ฅผ state ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  Global ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ€์žฅ ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— Provider ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

 

๋ณดํ†ต create-react-app ์„ ํ†ตํ•ด์„œ ๋ฆฌ์•กํŠธ ์•ฑ์„ ๋งŒ๋“ค๊ฒŒ ๋˜๋ฉด App ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ€์žฅ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋œ๋‹ค.


App ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์ด store๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ <Provider /> ๋กœ wrapping ํ•ด์•ผ ํ•œ๋‹ค.

 

import React from "react";
import ReactDOM from "react-dom";

import { Provider } from "react-redux";
import store from "./store";

import App from "./App";

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

 

๋ณดํ†ต React ์—์„œ๋Š” App.js, Next.js ์—์„œ๋Š” _app.js ์—์„œ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

useDispatch ์™€ useSelector

 

useDispatch()์™€ useSelector()๋Š” ๋ฆฌ๋•์Šค๋ฅผ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๋ฆฌ์•กํŠธ๊ฐ€ Hook์„ ๋ฐœํ‘œํ•œ ์ดํ›„๋กœ ์šฐ๋ฆฌ๋Š” ์ด์ œ ๋” ์ด์ƒ ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” connect, mapDispatchToProps, mapStateToProps์™€ ๊ฐ™์€ HOC๋ฅผ ์ด์šฉํ•ด์•ผ ํ–ˆ์ง€๋งŒ ์ด์ œ๋Š” Hook ์„ ์ด์šฉํ•ด์„œ ๋”์šฑ ๊ฐ€๋…์„ฑ ์žˆ๊ณ  ํŽธ๋ฆฌํ•œ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฒƒ์ด๋‹ค.

 

useSelector()

 

useSelector ๋Š” Redux Store ๋กœ ๋ถ€ํ„ฐ state ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

 

import { useSelector } from "react-redux";

const result = useSelector((state) => state.someState);

 

useDispatch()

 

useDispatch ๋Š” action ์„ dispatch ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ›…์ด๋‹ค.

 

import { useDispatch } from "react-redux";

const dispatch = useDispatch();

dispatch(ACTION);

 

useDispatch ์—์„œ๋Š” memoize๋ฅผ ์œ„ํ•ด์„œ useCallBack() ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•˜๋Š”๋ฐ, ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋ฐ”๋กœ ์ด ๋‹ค์Œ ์‹œ๊ฐ„์— ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž!

 

์ด์ œ ์‹ค์ „์œผ๋กœ ๋“ค์–ด๊ฐ€ todo list ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž

 

react-redux ๋กœ todo list ๋งŒ๋“ค๊ธฐ

 

Todo List ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ธ๋ฐ ์•„๋งˆ ๊ฒฐ๊ณผ๋ฌผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ฒผ์„ ๊ฒƒ์ด๋‹ค.

 

์ˆœ์„œ๋Š” ์•ž์„  ๋ชฉ์ฐจ์—์„œ ์ด์•ผ๊ธฐ ํ–ˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๊ฐœ๋ฐœ ํ•œ๋‹ค.

  1. ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ
  2. ์˜์กด์„ฑ ์ถ”๊ฐ€
  3. ๋ฆฌ๋•์Šค ์ž‘์—…ํ•˜๊ธฐ
  4. UI ์ž‘์—…ํ•˜๊ธฐ

 

ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ & ์˜์กด์„ฑ ์ถ”๊ฐ€

 

CRA ๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ„๋‹จํ•œ ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ด๋ณด์ž.

 

$ create-react-app redux-todo

$ npx create-react-app redux-todo

 

๊ทธ๋ฆฌ๊ณ  ์˜์กด์„ฑ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.


๊ฐ„๋‹จํ•˜๊ฒŒ redux์™€ styled-component ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

 

$ npm install -y styled-components

$ npm install -y redux

 

์ด๋ ‡๊ฒŒ ๊นŒ์ง€ ํ–ˆ๋‹ค๋ฉด package.json ์— ์˜์กด์„ฑ์ด ์ž˜ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋ฃจํŠธ ํด๋”์—์„œ App.js์™€ index.js ๋งŒ ๋‚จ๊ธฐ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‚ญ์ œํ•œ ๋’ค, ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ๋“ค์„ ์ง€์›Œ ์‹คํ–‰์ด ๋˜๋Š” ๊ฒƒ ๊นŒ์ง€ ํ™•์ธํ•˜์ž!

 

 

๋ฆฌ๋•์Šค ์ž‘์—…ํ•˜๊ธฐ

์ด๋ฒˆ ๊ธ€์˜ ํ•ต์‹ฌ์ธ ๋ฆฌ๋•์Šค ์ž‘์—…ํ•˜๊ธฐ ๋ถ€๋ถ„์ด๋‹ค.

 

๋ฆฌ๋•์Šค ์ž‘์—…์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” 3๊ฐœ์˜ js ํŒŒ์ผ์ด ํ•„์š”ํ•˜๋‹ค.

  • actions.js
    • action ์ƒ์„ฑ์„ ํ•˜๊ณ  ํ•ด๋‹น action ์ด ์–ด๋–ค ์ผ์„ ์ˆ˜ํ–‰ํ• ์ง€ ์ง€์ •ํ•œ๋‹ค.
  • reducer.js
    • action ์„ ์‹คํ–‰์‹œํ‚ฌ reducer
    • action ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ state ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚จ๋‹ค.
  • store.js
    • reducer ๋กœ global store ๋ฅผ ๋งŒ๋“ค redux store

commons ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ 3๊ฐœ์˜ ํŒŒ์ผ์„ ๊ฐ๊ฐ ์ž‘์„ฑํ•˜๋„๋ก ํ•˜์ž.

actions.js

export const ADD = "ADD_TODO";
export const DELETE = "DELETE_TODO";

let id = 1;

export const add_todo = (todo) => {
  return {
    type: ADD,
    todo: {
      id: id++,
      title: todo.title,
      isComplete: todo.isComplete,
    },
  };
};

export const delete_todo = (id) => {
  return {
    type: DELETE,
    id,
  };
};

 

์šฐ๋ฆฌ๋Š” ์ด๋ฒˆ todo list ์—์„œ 2๊ฐœ์˜ ์•ก์…˜๋งŒ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

  1. todo ์ƒ์„ฑ
  2. todo ์‚ญ์ œ

todo๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์€ todo ๊ฐ์ฒด๋ฅผ reducer ์—๊ฒŒ ๋ฐ˜ํ™˜ ํ•˜๊ณ , todo๋ฅผ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ญ์ œํ•˜๋ ค๋Š” todo์˜ id ๋งŒ์„ ๋„˜๊ฒจ์ฃผ๋„๋ก ํ•œ๋‹ค.

 

์ž์„ธํ•œ ์ƒ์„ฑ, ์‚ญ์ œ ๋กœ์ง์€ reducer ์—๊ฒŒ ์œ„์ž„ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด action์˜ ์ฑ…์ž„์˜ ๋งˆ์ง€๋ง‰์ด๋‹ค.

 

reducer.js

import { ADD, DELETE } from "./actions";

const initialState = {
  todos: [],
};

export const reducer = (state = initialState, action) => {
  if (action.type === ADD) {
    return {
      // ๋งŒ์•ฝ ๋‹ค๋ฅธ state ๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ์ „๊ฐœ ์—ฐ์‚ฐ ...state ๋ฅผ ํ•ด์•ผํ•จ
      // ํ•˜์ง€๋งŒ ํ˜„์žฌ state ์—๋Š” todos ํ•˜๋‚˜ ๋ฟ์ด๋ผ todos ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋จ
      todos: [...state.todos, action.todo],
    };
  } else if (action.type === DELETE) {
    return {
      todos: [...state.todos.filter((todo) => todo.id !== action.id)],
    };
  } else {
    return state;
  }
};

 

action-creator ์—๊ฒŒ ๋ฐ›์€ action.type, action.payload๋ฅผ ๊ฐ€์ง€๊ณ  ์ด์ œ ์‹ค์ œ๋กœ ํ•ด๋‹น ์•ก์…˜์ด ์–ด๋–ค ๊ฒฐ๊ณผ๋ฅผ ๋‚ด์–ด์•ผ ํ•˜๋Š”์ง€๋ฅผ ์ด reducer ์—์„œ ์ •์˜ํ•˜๋„๋ก ํ•œ๋‹ค.

 

์ƒ์„ฑ์„ ์œ„ํ•ด์„œ๋Š” initialState ์— ์กด์žฌํ•˜๋Š” todo ๋ฐฐ์—ด์— ์ƒˆ๋กœ์šด ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•œ ์ƒˆ๋กœ์šด state๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•œ๋‹ค.


๊ทธ๋ฆฌ๊ณ  ์‚ญ์ œ์—์„œ๋Š” action์ด ๋„˜๊ฒจ์ค„ id๋ฅผ ๊ฐ€์ง€๊ณ  Array.filter() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ id๊ฐ€ ๋™์ผํ•œ todo ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•œ todos๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•œ๋‹ค.

 

store.js

import { createStore } from "redux";
import { reducer } from "./reducer";

const store = createStore(reducer);

export default store;

 

๋ฆฌ๋•์Šค ์Šคํ† ์–ด์— reducer๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ์ž‘์—…์„ ํ•ด๋‹น ํŒŒ์ผ์—์„œ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

UI ์ž‘์—…ํ•˜๊ธฐ

UI ๋Š” 4๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ๊ตฌ์„ฑํ•  ๊ฒƒ์ด๊ณ  style ์„ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ์šฉํ•˜๋ ค ํ•œ๋‹ค. ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ณ ์œ ํ•œ styleใ„ด.js ํŒŒ์ผ์„ ๊ฐ–๊ฒŒ ๋œ๋‹ค.

 

  1. App.js
  2. InputForm.js
  3. TodoItem.js
  4. TodoList.js

App.js & App.styles.js

// App.js
import React from "react";
import * as S from "./App.styles";

import InputForm from "./components/InputForm";
import TodoList from "./components/TodoList";

function App() {
  return (
    <S.Container>
      <S.Wrapper>
        <h1>Redux ๋กœ ๋ฐฐ์šฐ๋Š” Todo List</h1>
        <InputForm />
        <TodoList />
      </S.Wrapper>
    </S.Container>
  );
}

export default App;

// App.styles.js
import styled from "styled-components";

export const Container = styled.div`
  width: 100vw;
  height: 200vh;

  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;

  background: #e9ecef;

  input {
    appearance: none;
    outline-style: none;
    border: none;
  }
`;

export const Wrapper = styled.div`
  margin-top: 300px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: flex-start;
`;

 

App.js ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค InputForm, TodoItem, TodoList๋ฅผ ํฌํ•จํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

 

๊ฐ„๋‹จํ•œ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜์˜€์ง€๋งŒ ๋งŒ์•ฝ styled-component๋‚˜ css ์— ๋Œ€ํ•ด์„œ ์•„์ง ๋ชจ๋ฅธ๋‹ค๋ฉด ์Šคํƒ€์ผ์€ ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ€๊ฑฐ๋‚˜ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ•ด๋„ ์ข‹๋‹ค.

 

InputForm.js & InputForm.styles.js

 

ํ•ด๋‹น ํŒŒ์ผ์—์„œ๋Š” TodoItem ์„ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ ํผ ์„ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋‹ค.

 

// InputForm.js
import React, { useState } from "react";
import { useDispatch } from "react-redux";

import * as S from "./InputForm.styles";
import { add_todo } from "../commons/actions";

const InputForm = () => {
  const dispatch = useDispatch();

  const [text, setText] = useState("");

  const handleChange = (e) => {
    const { value } = e.target;

    setText(value);
  };

  const handleClick = () => {
    const todo = {
      title: text,
      isComplete: false,
    };

    dispatch(add_todo(todo));
    setText("");
  };

  const handleKeyPress = (e) => {
    if (e.key === "Enter") {
      handleClick();
    }
  };

  return (
    <S.Container>
      <S.InputBox
        type="text"
        placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”!!"
        onChange={handleChange}
        value={text}
        onKeyDown={handleKeyPress}
      />
      <S.Button onClick={handleClick}>์ถ”๊ฐ€ ํ•˜๊ธฐ</S.Button>
    </S.Container>
  );
};

export default InputForm;

// InputForm.styles.js
import styled from "styled-components";

export const Container = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
`;

export const InputBox = styled.input`
  width: 295px;
  height: 40px;
  margin: 10px 5px;

  border-radius: 15px;
  font-size: 1.2rem;

  background: white;
  padding: 5px 25px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.34);
`;

export const Button = styled.button`
  width: 100px;
  height: 50px;

  font-size: 1.2rem;

  background: white;

  border: none;
  border-radius: 15px;

  color: #20c997;

  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.34);

  cursor: pointer;
`;

 

InputForm ์—์„œ๋Š” ํ…์ŠคํŠธ ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ, ์ถ”๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ state๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ๋ˆˆ์—ฌ๊ฒจ ๋ด์•ผ ํ•  ๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•ด์„œ ๋ชจ๋“  state๋ฅผ ๋ฆฌ๋•์Šค๋กœ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

 

๊ฐ„๋‹จํ•œ state ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๊ทธ๋ƒฅ ๋ฆฌ์•กํŠธ ํ›…์ธ useState() ๋งŒ ์‚ฌ์šฉํ•ด๋„ ์ถฉ๋ถ„ํ•˜๊ณ  global ํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์—ฌ์ง€๊ฐ€ ์žˆ๋Š” todo ๋Š” ๋ฆฌ๋•์Šค๋กœ ๊ด€๋ฆฌํ•˜๋„๋ก ํ–ˆ๋‹ค.

 

TodoItem.js & TodoItem.styles.js

์šฐ๋ฆฌ๊ฐ€ ์•ž์—์„œ reducer ์—์„œ ์ •์˜ํ•œ state ์— ์กด์žฌํ•˜๋Š” todos๋ฅผ ๋ Œ๋”๋ง ํ•  ui ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

 

// TodoItem.js
import React from "react";

import { useDispatch } from "react-redux";

import * as S from "./TodoItem.styles";
import { delete_todo } from "../commons/actions";

const TodoItem = ({ todo }) => {
  const dispatch = useDispatch();

  const { id, title, isComplete } = todo;

  const handleClick = () => {
    dispatch(delete_todo(id));
  };

  return (
    <S.Container>
      <S.TextColumn>
        <div>
          <S.Text>{title}</S.Text>
        </div>
        <S.X onClick={handleClick}>{isComplete || "X"}</S.X>
      </S.TextColumn>
    </S.Container>
  );
};

export default TodoItem;

// TodoItem.styles.js
import styled from "styled-components";

export const Container = styled.div`
  margin: 10px 10px;
  padding: 10px 10px;

  display: flex;
  justify-content: flex-start;
  align-items: center;

  border-radius: 4px;
`;

export const TextColumn = styled.div`
  width: 100%;

  display: flex;
  justify-content: space-between;
  align-items: center;
`;

export const Text = styled.span`
  margin: 0 8px;
`;

export const X = styled.button`
  color: red;
  border: none;
  background: white;

  font-size: 1rem;

  cursor: pointer;
`;

 

์—ฌ๊ธฐ์„œ๋Š” todo item ์„ ์‚ญ์ œ, ์ฆ‰ ํ•ด๋‹น todo๋ฅผ ์™„๋ฃŒํ–ˆ์„ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด์„œ DELETE action์„ dispatch ํ•œ๋‹ค.

 

์ด๋ฅผ ์œ„ํ•ด์„œ ์•ž์„œ ๋ฐฐ์šด useDispatch()๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

 

TodoList.js & TodoList.styles.js

 

TodoItem ์„ ๋ Œ๋”๋งํ•˜๋Š” ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ๊ฒฉ์ธ TodoList ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

 

// TodoList.js
import React from "react";

import * as S from "./TodoList.styles.js";

import TodoItem from "./TodoItem.js";
import { useSelector } from "react-redux";

const TodoList = () => {
  const todos = useSelector((state) => state.todos);

  return (
    <S.Container>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </S.Container>
  );
};

export default TodoList;

// TodoList.styles.js
import styled from "styled-components";

export const Container = styled.div`
  width: 400px;
  padding: 30px;

  display: flex;
  flex-direction: column;

  background: white;

  border-radius: 15px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.34);
`;

 

ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” reducer ์— ์กด์žฌํ•˜๋Š” state์ธ todos๋ฅผ ์ด์šฉํ•ด TodoItem ์„ ๋ Œ๋”๋งํ•  ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

 

useSelector ๋ฅผ ์ด์šฉํ•ด์„œ state ๋ฅผ ๊ฐ€์ ธ์˜จ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 


 

์ด๋ ‡๊ฒŒ ์˜ค๋Š˜์€ Redux๋ฅผ ์ด์šฉํ•ด์„œ TodoList๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

 

์•ž์œผ๋กœ Redux ๊ด€๋ จ๋œ ๋ฆฌํŒฉํ† ๋ง์ด๋‚˜ ์ถ”๊ฐ€ ๊ฐœ๋…๋“ค์˜ ๊ธฐ๋ณธ base๊ฐ€ ๋  ๊ฒŒ์‹œ๋ฌผ์ด๋‹ˆ ๋” ์•Œ์•„๋ณด๊ณ ์‹ถ๋‹ค๋ฉด ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ฐธ๊ณ ํ•ด๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

์ „์ฒด ์‹ค์Šต ์ฝ”๋“œ๋Š” github ์— ์˜ฌ๋ผ๊ฐ€ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค!

๋Œ“๊ธ€