๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
JavaScript/React

[React] ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•

by soy๋ฏธ๋‹ˆ 2021. 10. 27.

 

 

 

 

ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์—์„œ React.memo() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ ์ตœ์ ํ™”๊ฐ€ ๋๋‚˜์ง€๋Š” ์•Š๋Š”๋‹ค. 

์˜ˆ๋ฅผ ๋“ค์–ด ์ตœ์‹  ์ƒํƒœ์˜ todos ๋ฅผ ์ฐธ์กฐํ•˜๋Š” onClick ๊ฐ™์€ ํ•จ์ˆ˜๊ฐ€ ์žˆ์–ด์„œ todos ๋ฐฐ์—ด์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค๊ณ  ํ•  ๋•Œ, ์ด๋ ‡๊ฒŒ ํ•จ์ˆ˜๊ฐ€ ๊ณ„์† ๋งŒ๋“ค์–ด์ง€๋Š” ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€์ด๋‹ค.

 

 

 

1. useState ์˜ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ ๊ธฐ๋Šฅ ์‚ฌ์šฉํ•˜๊ธฐ

์˜ˆ๋ฅผ ๋“ค์–ด ๊ธฐ์กด์— setTodos ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๊ณ  ํ•˜๋ฉด, ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ๋Š” ๋Œ€์‹  ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ์ •์˜ํ•ด์ฃผ๋Š” ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ๋„ฃ์Œ์œผ๋กœ์จ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

setNumber(number+1) ์ด ์•„๋‹ˆ๋ผ setNumber(preNumber => preNumber + 1) ๊ณผ ๊ฐ™์€ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

 

const onRemove = useCallback(id => {
   setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);

const onToggle = useCallback(id => {
   setTodos(todos => todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo,
   ),
  );
}, []);

// const onToggle = useCallback(id => {
//    setTodos(todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo,
//    ),
//   );
// }, [todos],);

 

 

 

2. useReducer ์‚ฌ์šฉํ•˜๊ธฐ

 

import TodoInsert from "./components/TodoInsert";
import TodoTemplate from "./components/TodoTemplate";
import TodoList from "./components/TodoList";
import { useReducer, useState, useRef, useCallback } from "react";

function createBulkTodos() {
  const array = [];
  for (let i = 1; i <= 2500; i++) {
    array.push({
      id: 1,
      text: `ํ•  ์ผ ${i}`,
      checked: false,
    });
  }
  return array;
}

// useReducer ์ถ”๊ฐ€๋œ ์ฝ”๋“œ
function todoReducer(todos, action) {
  switch (action.type) {
    case "INSERT":
      return todos.concat(action.todo);
    case "REMOVE":
      return todos.filter((todo) => todo.id !== action.id);
    case "TOGGLE":
      return todos.map((todo) =>
        todo.id === action.id ? { ...todo, checked: !todo.checked } : todo
      );
    default:
      return todos;
  }
}

// ---------------------------- ์ด๊นŒ์ง€

const App = () => {
  // createBulkTodos() ํ˜•ํƒœ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„ฃ์–ด ์ฃผ๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
  // createBulkTodos ์ฒ˜๋Ÿผ ํ•จ์ˆ˜ ํ˜•ํƒœ๋กœ ๋„ฃ์–ด ์ฃผ๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋ Œ๋”๋ง ๋  ๋•Œ๋งŒ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
  const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
  const nextId = useRef(2501);
  // ํ•  ์ผ ์ถ”๊ฐ€
  const onInsert = useCallback((text) => {
    const todo = {
      id: nextId.current,
      text,
      checked: false,
    };
    dispatch({ type: "INSERT", todo });
    nextId.current += 1;
  }, []);

  // ํ•  ์ผ ์‚ญ์ œ
  const onRemove = useCallback((id) => {
    dispatch({ type: "REMOVE", id });
  }, []);

  // ์ฒดํฌ๋ฐ•์Šค ์ฒดํฌ
  const onToggle = useCallback((id) => {
    dispatch({ type: "TOGGLE", id });
  }, []);

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};

export default App;

 

useReducer ๋Š” ์›๋ž˜ ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋„ฃ๋Š”๋ฐ ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์— undefined ๋ฅผ ๋„ฃ๊ณ  ์„ธ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” createBulkTodos ๋ฅผ ๋„ฃ์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งจ ์ฒ˜์Œ ๋ Œ๋”๋ง ๋  ๋•Œ๋งŒ createBulkTodos ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

 

 

 

 

 

โœ ๋ฆฌ์ŠคํŠธ ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ 100 ๊ฐœ ์ด์ƒ์ด๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ๊ณผ ๋ฆฌ์ŠคํŠธ, ์ด ๋‘ ๊ฐ€์ง€ ์ปดํฌ๋„ŒํŠธ ๋ฐ˜๋“œ์‹œ ์ตœ์ ํ™”ํ•ด์ฃผ๊ธฐ

 

 

 

3. react-virtualized ์‚ฌ์šฉํ•˜๊ธฐ

๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์Šคํฌ๋กค ๋˜๊ธฐ ์ „์— ๋ณด์ด์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋งํ•˜์ง€ ์•Š๊ณ  ํฌ๊ธฐ๋งŒ ์ฐจ์ง€ํ•˜๊ฒŒ๋” ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์Šคํฌ๋กค ๋˜๋ฉด ํ•ด๋‹น ์Šคํฌ๋กค ์œ„์น˜์—์„œ ๋ณด์—ฌ์ฃผ์–ด์•ผ ํ•  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

# ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜
yarn add react-virtualized

 

import TodoListItem from "./TodoListItem";
import "./TodoList.scss";
import React, { useCallback } from "react";
import { List } from "react-virtualized";

const TodoList = ({ todos, onRemove, onToggle }) => {
  const rowRenderer = useCallback(
    ({ index, key, style }) => {
      const todo = todos[index];
      return (
        <TodoListItem
          todo={todo}
          key={key}
          onRemove={onRemove}
          onToggle={onToggle}
          style={style}
        />
      );
    },
    [onRemove, onToggle, todos]
  );
  
  return (
    <List
      className="TodoList"
      width={512}
      height={513}
      rowCount={todos.length}
      rowHeight={57}
      rowRenderer={rowRenderer}
      list={todos}
      style={{ outline: "none" }}
    />
  );
};

export default React.memo(TodoList);

 

import {
  MdCheckBoxOutlineBlank,
  MdRemoveCircleOutline,
  MdCheckBox,
} from "react-icons/md";
import "./TodoListItem.scss";
import cn from "classnames";
import React from "react";

const TodoListItem = ({ todo, onRemove, onToggle, style }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem-virtualized" style={style}>
      <div className="TodoListItem">
        <div
          className={cn("checkbox", { checked })}
          onClick={() => onToggle(id)}
        >
          {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
          <div className="text">{text}</div>
        </div>
        <div className="remove" onClick={() => onRemove(id)}>
          <MdRemoveCircleOutline />
        </div>
      </div>
    </div>
  );
};

export default React.memo(TodoListItem);

 

 

 

 

 

๋Œ“๊ธ€