πŸ” μ΅œμ ν™”

κΉ€μˆ˜μ§„Β·2025λ…„ 7μ›” 10일

ν•œ μž… 크기둜 잘라 λ¨ΉλŠ” λ¦¬μ•‘νŠΈ μ„Ήμ…˜ 11 - μ΅œμ ν™”

μ΅œμ ν™”λž€?

μ›Ή μ„œλΉ„μŠ€μ˜ μ„±λŠ₯을 κ°œμ„ ν•˜λŠ” λͺ¨λ“  ν–‰μœ„λ₯Ό μΌμ»«λŠ” 말이닀.

React App λ‚΄λΆ€μ˜ μ΅œμ ν™” 방법

  1. μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ˜ λΆˆν•„μš”ν•œ 연산을 λ°©μ§€ν•œλ‹€.
  2. μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ˜ λΆˆν•„μš”ν•œ ν•¨μˆ˜ μž¬μƒμ„±μ„ λ°©μ§€ν•œλ‹€.
  3. μ»΄ν¬λ„ŒνŠΈμ˜ λΆˆν•„μš”ν•œ λ¦¬λ Œλ”λ§μ„ λ°©μ§€ν•œλ‹€.

useMemo - λΆˆν•„μš”ν•œ μ—°μ‚° λ°©μ§€

λ©”λͺ¨λ¦¬μ œμ΄μ…˜(결과값을 μ €μž₯ν•˜μ—¬ κΈ°μ–΅) 기법을 기반으둜 λΆˆν•„μš”ν•œ 연산을 μ΅œμ ν™”ν•˜λŠ” λ¦¬μ•‘νŠΈ 훅이닀.

import "./List.css";
import TodoItem from "./TodoItem";
import { useState, useMemo } from "react";

const List = ({ todos, onUpdate, onDelete }) => {
  const [search, setSearch] = useState("");

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

  const getFilteredData = () => {
    if (search === "") {
      return todos;
    }
    return todos.filter((todo) =>
      todo.content.toLowerCase().includes(search.toLowerCase())
    );
  };

  const filteredTodos = getFilteredData();

  const { totalCount, doneCount, notDoneCount } = useMemo(() => {
    const totalCount = todos.length;
    const doneCount = todos.filter((todo) => todo.isDone).length;
    const notDoneCount = totalCount - doneCount;
    return { totalCount, doneCount, notDoneCount };
  }, [todos]);

  return (
    <div className="List">
      <h4>Todo List 🌱</h4>
      <div>
        <div>total: {totalCount}</div>
        <div>done: {doneCount}</div>
        <div>notDone: {notDoneCount}</div>
      </div>
      <input
        value={search}
        onChange={onChangeSearch}
        placeholder="검색어λ₯Ό μž…λ ₯ν•˜μ„Έμš”"
      />
      <div className="todos_wrapper">
        {filteredTodos.map((todo) => (
          <TodoItem
            key={todo.id}
            {...todo}
            onUpdate={onUpdate}
            onDelete={onDelete}
          />
        ))}
      </div>
    </div>
  );
};

export default List;

List.jsx에 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ˜€λ‹€. getAnalyzedDataκ°€ λΆˆν•„μš”ν•˜κ²Œ μ—°μ‚°λ˜λŠ”κ²ƒμ„ 막기 μœ„ν•΄ useMemoλ₯Ό μ‚¬μš©ν•˜μ˜€λ‹€.

좜λ ₯ ν™”λ©΄:

React.memo

μ»΄ν¬λ„ŒνŠΈλ₯Ό 인수둜 λ°›μ•„, μ΅œμ ν™”λœ μ»΄ν¬λ„ŒνŠΈλ‘œ λ§Œλ“€μ–΄ λ°˜ν™˜ν•˜λŠ” 것
즉, λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈκ°€ λ¦¬λ Œλ”λ§μ΄ λ˜λ”λΌλ„ μžμ‹ μ΄ λ°›λŠ” propsκ°€ λ°”λ€Œμ§€ μ•ŠμœΌλ©΄ λ‹€μ‹œ λ¦¬λ Œλ”λ§μ΄ λ°œμƒν•˜μ§€ μ•Šλ„λ‘ λ©”λͺ¨μ΄μ œμ΄μ…˜μ΄ 되기 λ•Œλ¬Έμ— λΆˆν•„μš”ν•œ λ¦¬λ Œλ”λ§μ΄ λ°©μ§€λ˜μ–΄μ„œ μžλ™μœΌλ‘œ μ΅œμ ν™”κ°€ μ΄λ£¨μ–΄μ§€κ²Œ 되게 ν•˜λŠ” 것이닀.

import "./Header.css";
import { memo } from "react";

const Header = () => {
  return (
    <div className="Header">
      <h3>μ˜€λŠ˜μ€ πŸ“† </h3>
      <h1>{new Date().toDateString()}</h1>
    </div>
  );
};
const memoizedHeader = memo(Header);

export default memoizedHeader; //window + . 둜 이λͺ¨μ§€ 좔각 κ°€λŠ₯ν•˜λ‹€.

Header μ»΄ν¬λ„ŒνŠΈκ°€ λΆˆν•„μš”ν•˜κ²Œ λ¦¬λ Œλ”λ§ λ˜λŠ” λ¬Έμ œμ μ„ ν•΄κ²°ν•  수 μžˆλ‹€.

useCallback - λΆˆν•„μš”ν•œ ν•¨μˆ˜ μž¬μƒμ„± λ°©μ§€ν•˜κΈ°

κΈ°λŠ₯ λ¨Όμ € κ΅¬ν˜„ν•˜κ³  μ΅œμ ν™”λŠ” λ‚˜μ€‘μ— ν•˜λŠ” 것을 ꢌμž₯
μ΅œμ ν™”κ°€ ν•„μš”ν•œ λΆ€λΆ„λ§Œ ν•˜κΈ° (λͺ¨λ“  뢀뢄은 λΆˆν•„μš”)

import "./TodoItem.css";
import { memo } from "react";

const TodoItem = ({ id, isDone, content, date, onUpdate, onDelete }) => {
  const onChangeCheckbox = () => {
    onUpdate(id);
  };

  const onClickDeleteButton = () => {
    onDelete(id);
  };

  return (
    <div className="TodoItem">
      <input
        onChange={onChangeCheckbox}
        readOnly
        checked={isDone}
        type="checkbox"
      />
      <div className="content">{content}</div>
      <div className="date">{new Date(date).toLocaleDateString()}</div>
      <button onClick={onClickDeleteButton}>μ‚­μ œ</button>
    </div>
  );
};

export default memo(TodoItem);
import "./App.css";
import { useState, useRef, useReducer, useCallback } from "react";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";

const mockData = [
  {
    id: 0,
    isDone: false,
    content: "React κ³΅λΆ€ν•˜κΈ°",
    date: new Date().getTime(),
  },
  {
    id: 1,
    isDone: false,
    content: "μ½”μ–΄νƒ€μž„ κ°–κΈ°",
    date: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    content: "일찍 μΌμ–΄λ‚˜κΈ°",
    date: new Date().getTime(),
  },
];

function reducer(state, action) {
  switch (action.type) {
    case "CREATE":
      return [action.data, ...state];
    case "UPDATE":
      return state.map((item) =>
        item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
      );
    case "DELETE":
      return PopStateEvent.filter((item) => item.id !== action.targetId);
    default:
      return state;
  }
}

function App() {
  const [todos, dispatch] = useReducer(reducer, mockData);
  const idRef = useRef(3);

  const onCreate = useCallback((content) => {
    dispatch({
      type: "CREATE",
      data: {
        id: idRef.current++,
        isDone: false,
        content: content,
        date: new Date().getTime(),
      },
    });
  }, []);

  const onUpdate = useCallback((targetId) => {
    dispatch({
      type: "UPDATE",
      targetId: targetId,
    });
  }, []);

  const onDelete = useCallback((targetId) => {
    dispatch({
      type: "DELETE",
      targetId: targetId,
    });
  }, []);

  return (
    <div className="App">
      <Header />
      <Editor onCreate={onCreate} />
      <List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
    </div>
  );
}

export default App;

App.jsx 와 TodoItem.jsx μ½”λ“œμ΄λ‹€.

0개의 λŒ“κΈ€