[React] 성능 최적화 1 - useMemo

이재훈·2023년 6월 9일
0

React

목록 보기
15/27

목표

연산결과 재사용

  • 현재 일기 데이터를 분석하는 함수를 제작하고 해당 함수가 일기 데이터의 길이가 변화하지 않을 때 값을 다시 계산하지 않도록 하기
  • Memoization 이해하기

Memoization

이미 계산해 본 결과를 기억 해 두었다가 동일한 계산을 시키면, 다시 연산하지 않고 기억해두었던 데이터를 반환 시키게 하는 방법입니다.

먼저 예제 코드를 만들어 보도록 하겠습니다.

App.js

import { useEffect, useRef, useState } from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
import Lifecycle from "./Lifecycle";

function App() {
  const [data, setData] = useState([]);

  const dataId = useRef(0);

  const getData = async () => {
    const res = await fetch(
      "https://jsonplaceholder.typicode.com/comments"
    ).then((res) => res.json());
    const initData = res.slice(0, 20).map((it) => {
      return {
        name: it.email,
        content: it.body,
        hungry: (Math.floor(Math.random() * 9) + 1) * 10,
        created_date: new Date().getTime(),
        id: dataId.current++,
      };
    });
    setData(initData);
  };

  useEffect(() => {
    getData();
  }, []);

  const onCreate = (name, content, hungry) => {
    const created_date = new Date().getTime();
    const newItem = {
      name,
      content,
      hungry,
      created_date,
      id: dataId.current,
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };

  const onRemove = (targetId) => {
    const newDirayList = data.filter((it) => it.id !== targetId);
    setData(newDirayList);
  };

  const onEdit = (targetId, newContent) => {
    setData(
      data.map((it) =>
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  };

  const getDiaryAnalysis = () => {
    console.log("일기 분석 시작");

    const reallyHungy = data.filter((it) => it.hungry >= 80).length;
    const notHungry = data.filter((it) => it.hungry < 40).length;
    const reallyHungryRatio = (reallyHungy / data.length) * 100;
    const notHungryRatio = (notHungry / data.length) * 100;
    return { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio };
  };

  const { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio } =
    getDiaryAnalysis();

  return (
    <div className="App">
      <Lifecycle />
      <DiaryEditor onCreate={onCreate} />
      <div>전체 일기 : {data.length}</div>
      <div>배부른 일기 개수 : {notHungry}</div>
      <div>배고픈 일기 개수 : {reallyHungy}</div>
      <div>배부른 일기 비율 : {notHungryRatio}</div>
      <div>배고픈 일기 비율 : {reallyHungryRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
    </div>
  );
}

export default App;

기존 코드에 일기분석을 하는 코드를 추가하였습니다.

  const getDiaryAnalysis = () => {
    console.log("일기 분석 시작");

    const reallyHungy = data.filter((it) => it.hungry >= 80).length;
    const notHungry = data.filter((it) => it.hungry < 40).length;
    const reallyHungryRatio = (reallyHungy / data.length) * 100;
    const notHungryRatio = (notHungry / data.length) * 100;
    return { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio };
  };

  const { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio } =
    getDiaryAnalysis();

  return (
    <div className="App">
      <Lifecycle />
      <DiaryEditor onCreate={onCreate} />
      <div>전체 일기 : {data.length}</div>
      <div>배부른 일기 개수 : {notHungry}</div>
      <div>배고픈 일기 개수 : {reallyHungy}</div>
      <div>배부른 일기 비율 : {notHungryRatio}%</div>
      <div>배고픈 일기 비율 : {reallyHungryRatio}%</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
    </div>
  );

해당 코드에는 새롭게 추가되는 개념이 없어서 설명은 넘어가도록 하겠습니다.

원하는대로 잘 출력되는 것을 확인할 수 있습니다.

콘솔로 로그를 보도록 하겠습니다.

일기 분석 시작이 2번 실행 된 것을 확인할 수 있습니다. 연한 색의 로그는 무시하셔도 됩니다.

index.js

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

index.js에 App 태그를 React.StrictMode로 감싸고 있기 때문에 연한색 글씨의 로그가 생기게 되는 것입니다.

무튼 2번 로그가 출력된다는 것은 2번 렌더링이 된다는 말입니다.

Mount 될 때 한번 출력되고,

const { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio } = getDiaryAnalysis();

위의 코드가 실행되면서 두번 렌더링이 되는 것 입니다. 리렌더링 된다는 것은 App함수가 또 호출된다는 말입니다.

일기를 수정해 보았습니다. 그러자 또 getDiaryAnalysis 함수가 실행이 됩니다. 이것은 낭비입니다. 이유는 일기 내용을 수정한다고 해서 통계에 영향을 미치지는 않기 때문입니다. 이 때 사용하는 것이 "Memoization" 기법입니다.

App.js

import { useEffect, useMemo, useRef, useState } from "react";

  const getDiaryAnalysis = useMemo(() => {
    console.log("일기 분석 시작");

    const reallyHungy = data.filter((it) => it.hungry >= 80).length;
    const notHungry = data.filter((it) => it.hungry < 40).length;
    const reallyHungryRatio = (reallyHungy / data.length) * 100;
    const notHungryRatio = (notHungry / data.length) * 100;
    return { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio };
  }, [data.length]);

사용법은 간단합니다. 함수를 useMemo로 감싸고 두번째로 배열을 넘겨야 하는데 useEffect와 마찬가지로 dependency array입니다. 즉, data의 length가 변경되어야 콜백함수가 실행되게 되는 것 입니다.

이렇게 변경을 하고 저장을 누르면 에러가 발생하게 됩니다.

getDiaryAnalysis는 함수가 아니라는 에러입니다. useMemo로 감싸게 되면 더이상 함수가 아니게 됩니다. useMemo는 안에 callback함수가 리턴하는 값을 리턴합니다.

const { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio } =
    getDiaryAnalysis();

위의 코드를 변경시키면 됩니다.

const { reallyHungy, notHungry, reallyHungryRatio, notHungryRatio } =
    getDiaryAnalysis;


이제 일기의 내용을 변경해도 getDiaryAnalysis가 수행되지 않는 것을 확인할 수 있습니다.


해당 게시글은 인프런 강의
"한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지(이정환)"
를 정리한 내용입니다. 쉽게 잘 설명해주시니 여러분도 강의를 듣는 것을 추천드립니다.

profile
부족함을 인정하고 노력하자

0개의 댓글