요르딩딩

섹션11. 최적화 본문

[강의]/[한 입 크기로 잘라 먹는 리액트(React.js)]

섹션11. 최적화

요르딩딩 2025. 4. 7. 15:53
728x90
반응형

10.1) 최적화란

 

10.2) 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(() => {
      console.log("getAnalyzedData 호출!");
      const totalCount = todos.length;
      const doneCount = todos.filter(
        (todo) => todo.isDone
      ).length;
      const notDoneCount = totalCount - doneCount;

      return {
        totalCount,
        doneCount,
        notDoneCount,
      };
    }, [todos]); // todos가 변경될때만 앞의 콜백함수가 실행된다!!! useEffect와 동일!!! 빈값이면 최초한번만 수행됨!!
  // 의존성배열 : deps

  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) => {
          return (
            <TodoItem
              key={todo.id}
              {...todo}
              onUpdate={onUpdate}
              onDelete={onDelete}
            />
          );
        })}
      </div>
    </div>
  );
};

export default List;

 

10.3) React.memo와 컴포넌트 렌더링 최적화

 

# 컴포넌트의 불필요 리랜더링 제거

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

const Header = () => {
  return (
    <div className="Header">
      <h3>오늘은 📆</h3>
      <h1>{new Date().toDateString()}</h1>
    </div>
  );
};

// 매번 랜더링될 필요없는 컴포넌트 최적화!!!
export default memo(Header); // 수정!!!
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>
  );
};

// 수정!!! 얕은비교 (현재와 과거 객체) 매번 다르게 인식 -> HOC방식으로 적용
// export default memo(TodoItem); 

//고차 컴포넌트 (HOC)
export default memo(TodoItem, (prevProps, nextProps) => {
   //반환값에 따라, Props가 바뀌었는지 안바뀌었는지 판단
   // T -> Props 바뀌지 않음 -> 리렌더링 X
   // F -> Props 바뀜 -> 리렌더링 O
   
   if(prevProps.id !== nextProps.id) return false;
   if(prevProps.isDone !== nextProps.isDone) return false;
   if(prevProps.content !== nextProps.content) return false;
   if(prevProps.date !== nextProps.date) return false;

});

 

 

10.4) useCallback과 함수 재생성 방지

import "./App.css";
import { 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 state.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(),
      },
    });
  }, []); // 마운트 되었을때, 1번만 동작함!!!

  const onUpdate = useCallback((targetId) => {
    dispatch({
      type: "UPDATE",
      targetId: targetId,
    });
  }, []); // 마운트 되었을때, 1번만 동작함!!!

  const onDelete = useCallback((targetId) => {
    dispatch({
      type: "DELETE",
      targetId: targetId,
    });
  }, []);  // 마운트 되었을때, 1번만 동작함!!!

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

export default App;

 

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); //App에 useMemo적용으로 사용가능!!!

 

# 순서 : 1. 기능구현 -> 2.최적화

# 대상 : 연산, 함수, 컴포넌트 등

728x90
반응형
Comments