๐Ÿ“• ์žฌ๋ฏธ์žˆ๋Š” Redux(1)(react์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ)

Lee Jooamยท2022๋…„ 4์›” 21์ผ
1

๋‹ค์‹œ ํ•œ๋ฒˆ ๋งํ•˜์ง€๋งŒ ๋ฆฌ๋•์Šค๋Š” ๋ณ„๋กœ ์žฌ๋ฏธ๊ฐ€ ์—†๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ ๋งํ•˜์ž๋ฉด ๋ฆฌ๋•์Šค๋ฅผ ๋ฐฐ์šฐ๋Š” ๊ณผ์ •์ด ์žฌ๋ฏธ ์—†๋‹ค.

ํ•˜์ง€๋งŒ ํ•œ๋ฒˆ ๋ฐฐ์›Œ ๋†“๊ณ  ์‚ฌ์šฉํ•ด๋ณด๋ฉด ๋ฆฌ๋•์Šค๋Š” ์ฐธ ๋“ ๋“ ํ•˜๋‹ค. ์ข‹์€ ์นœ๊ตฌ ๋ฆฌ๋•์Šค๋ฅผ ๋ฆฌ์•กํŠธ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•์„ ์•Œ์•„๋ณด์ž.

์‹œ์ž‘ํ•˜๊ธฐ

์ผ๋ฐ˜ js ํ™˜๊ฒฝ๊ณผ CRA ํ™˜๊ฒฝ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฑด ํฐ ์ฐจ์ด๊ฐ€ ์—†๋‹ค. ์ „์ฒด์ ์ธ ๋งฅ๋ฝ๋„ ๊ฐ™๋‹ค. ์˜คํžˆ๋ ค CRA์—์„œ ๋” ํŽธํ•˜๊ฒŒ ๋Š๊ปด์ง€๋Š” ๋ถ€๋ถ„๋„ ์žˆ๋‹ค.

yarn add redux
yarn add react-redux

์šฐ์„  ๋‘ ๊ฐœ์˜ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์•ผํ•œ๋‹ค.

redux๋Š” ์ด์ „ ํฌ์ŠคํŠธ์—์„œ ์„ค๋ช…ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ redux core์— ํ•ด๋‹นํ•˜๋Š” ํŒจํ‚ค์ง€์ด๊ณ , react-redux๋Š” ๋ฆฌ์•กํŠธ ํ™˜๊ฒฝ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ๋” ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ํŒจํ‚ค์ง€์ด๋‹ค.

react-redux๋Š” store๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ์™€ redux-hooks ๋“ฑ์„ ์ œ๊ณตํ•ด์ค€๋‹ค.

๋ชจ๋“ˆ ์„ค์ •

counter์™€ todos๋ฅผ ๊ตฌํ˜„ํ•œ ์•ฑ์ด๋‹ค. ๋ฆฌ์•กํŠธ์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์•ก์…˜ ์ƒ์„ฑ์ž, ๋ฆฌ๋“€์„œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. dispatch๋Š” ๋ฆฌ๋•์Šค ํ›…, ์Šคํ† ์–ด๋Š” ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ œ๊ณตํ•œ๋‹ค.

// counter.js
const INCREMENT = "counter/INCREMENT";
const DECREMENT = "counter/DECREMENT";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

const initialState = 0;

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
}

export default counter;
// todos.js
const ADD_TODO = "todos/ADD_TODO";
const DELETE_TODO = "todos/DELETE_TODO";
const CHECK_TODO = "todos/CHECK_TODO";

export const addTodo = (todo) => ({ type: ADD_TODO, todo });
export const deleteTodo = (id) => ({ type: DELETE_TODO, id });
export const checkTodo = (id) => ({ type: CHECK_TODO, id });

let nextId = 0;
const initialState = {};

function todos(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return { ...state, [++nextId]: action.todo };
    case DELETE_TODO:
      const newState = { ...state };
      delete newState[action.id];
      return newState;
    case CHECK_TODO: {
      console.log(!state[action.id].done);

      const newState = {
        ...state,
        [action.id]: { ...state[action.id], done: !state[action.id].done },
      };

      return newState;
    }
    default:
      return state;
  }
}

export default todos;

๊ฐ๊ฐ ๊ธฐ๋Šฅ์— ๋งž๊ฒŒ ์•ก์…˜ ํƒ€์ž…, ์•ก์…˜ ์ƒ์„ฑ์ž, ์ดˆ๊ธฐ ์ƒํƒœ, ๋ฆฌ๋“€์„œ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ์•ก์…˜์˜ ๊ฒฝ์šฐ ํ•„์š”ํ•œ ์ธ์ž๋“ค์„ ์ถ”๊ฐ€๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋ณดํ†ต์€ payload๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์„ ์–ธํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ํฌ์ŠคํŠธ์—์„œ๋Š” ์—ญํ• ์— ๋งž๊ฒŒ ์ด๋ฆ„์„ ๋ถ€์—ฌํ•˜๊ฒ ๋‹ค.

export๋œ ๋ฆฌ๋“€์„œ๋Š” ๋ฆฌ๋•์Šค ํŒจํ‚ค์ง€์˜ combineReducer ๋ฉ”์†Œ๋“œ๋กœ ํ•ฉ์น  ์ˆ˜ ์žˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ

// module/index.js
import { combineReducers } from "redux";
import counter from "./counter";
import todos from "./todos";

const rootReducer = combineReducers({ counter, todos });

export default rootReducer;
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { createStore } from "redux";
import rootReducer from "./module";
import { Provider } from "react-redux";

const store = createStore(rootReducer);

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

Provider๋Š” store๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ๋‹ค. ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๋ฆฌ๋•์Šค ํ›…์„ ์ด์šฉํ•ด ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฌธ๋“ createStore์— ์ทจ์†Œ์„ ์ด ์ƒ๊ฒจ ์žˆ๊ธธ๋ž˜ ํ™•์ธํ•ด๋ดค๋”๋‹ˆ redux-toolkit์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธธ ๊ถŒ์žฅํ•œ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์˜€๋‹ค.

deperecated ๋˜์—ˆ๋‹ค๊ณ  ํ•˜๋‹ˆ ์กฐ๋งŒ๊ฐ„ ์‚ฌ์šฉ๋ฒ•์„ ์ตํ˜€์•ผ๊ฒ ๋‹ค.

// counter.jsx
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { increment, decrement } from "../module/counter";

const Counter = () => {
  const number = useSelector((state) => state.counter);
  const dispatch = useDispatch();

  const onIncrement = () => {
    dispatch(increment());
  };
  const onDecrement = () => {
    dispatch(decrement());
  };

  return (
    <>
      <strong style={{ display: "block" }}>{number}</strong>
      <button onClick={onIncrement}>Plus</button>
      <button onClick={onDecrement}>Minus</button>
    </>
  );
};

export default Counter;

counter ์ปดํฌ๋„ŒํŠธ๋‹ค. ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์€ ์—†๊ณ  ๊ตฌ๋…ํ•œ state์—์„œ ๋ฐ›์€ ๊ฐ’์„ ๋ Œ๋”๋งํ•œ๋‹ค.

์ด๋•Œ useSelector ํ›…๊ณผ useDispatch ํ›…์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ useSelector๋Š” ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•ด์ฃผ๋Š” ์—ญํ• , useDispatch ํ›…์œผ๋กœ ์ƒ์„ฑ๋œ dispatch๋Š” ์•ก์…˜์„ dispatchํ•˜์—ฌ state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

// todos.jsx
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addTodo, checkTodo, deleteTodo } from "../module/todos";

const Todo = React.memo(({ id, todo, onDelete, onCheck }) => {
  return (
    <li>
      <h3>{todo.title}</h3>
      <input type='checkbox' checked={todo.done} onChange={() => onCheck(id)} />
      <button onClick={() => onDelete(id)}>์‚ญ์ œ</button>
    </li>
  );
});

const Todos = () => {
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const insertTodo = useCallback(
    (e) => {
      e.preventDefault();

      const title = e.target[0].value;
      dispatch(addTodo({ title, done: false }));
      e.target.reset();
    },
    [dispatch]
  );

  const onDelete = useCallback(
    (id) => {
      dispatch(deleteTodo(id));
    },
    [dispatch]
  );

  const onCheck = useCallback(
    (id) => {
      dispatch(checkTodo(id));
    },
    [dispatch]
  );

  return (
    <>
      <ul>
        {Object.keys(todos).map((id) => (
          <Todo
            id={id}
            key={id}
            todo={todos[id]}
            onDelete={onDelete}
            onCheck={onCheck}
          />
        ))}
      </ul>
      <form onSubmit={insertTodo}>
        <input type='text' />
        <button type='submit'>์ถ”๊ฐ€</button>
      </form>
    </>
  );
};

export default Todos;

todos์˜ ์ƒํƒœ๋ฅผ object ํ˜•ํƒœ๋กœ ์ƒ๊ฐํ–ˆ๋”๋‹ˆ ์ฝ”๋“œ๊ฐ€ ์ข€ ์ง€์ €๋ถ„ํ•ด์ง„ ๋Š๋‚Œ์ด๋‹ค. ๋ฐฐ์—ด๋กœ ํ•œ๋‹ค๋ฉด ์ข€ ๋” ๊น”๋”ํ•˜๊ฒŒ ๋ณ€ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

์ด๋•Œ Todos์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— useCallback, ๊ทธ๋ฆฌ๊ณ  state์— ๋”ฐ๋ผ ๋ Œ๋”๋ง๋˜๋Š” Todo์— React.memo๋ฅผ ์„ค์ •ํ•ด์คฌ๋Š”๋ฐ ์ด๊ฒƒ์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ถ”ํ›„ ๋ฆฌ์•กํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ์›์ธ๊ณผ ๊ทธ์— ๋”ฐ๋ฅธ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•๋„ ํฌ์ŠคํŒ…ํ•ด์•ผ๊ฒ ๋‹ค.

ํ›„๊ธฐ

์ •๋ง ๊ฐ„๋‹จํ•œ Counter์™€ Todo์ง€๋งŒ ๋ฆฌ๋•์Šค๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•ด๋ดค๋”๋‹ˆ ์‰ฝ์ง€ ์•Š์•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฆฌ๋•์Šค๋Š” ์•ก์…˜์„ ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ฆฌ๋•์Šค ์™ธ๋ถ€์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ๋งŒ๋“ค๊ณ  dispatch๋ฅผ ํ•ด์•ผํ•œ๋‹ค.

์ด ์ž‘์—…์€ ๋ณต์žกํ•œ ๋กœ์ง์ด๋ผ๋ฉด ๊ต‰์žฅํžˆ ๋ถˆํŽธํ•ด์งˆ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋‹คํ–‰ํžˆ ๋ฆฌ๋•์Šค๋Š” ์•ก์…˜์— ํ•ด๋‹นํ•˜๋Š” ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋„์ž…ํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์Œ์€ ๋ฆฌ๋•์Šค ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์— ์ฃผ๋กœ ์“ฐ์ด๋Š” redux-saga์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ…ํ•  ์˜ˆ์ •์ด๋‹ค.

profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ๊ฑธ์–ด๊ฐ€๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

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

comment-user-thumbnail
2022๋…„ 4์›” 23์ผ

ใ„ธ

1๊ฐœ์˜ ๋‹ต๊ธ€