[React - 기초] Memoization

2022. 3. 15. 12:48·React/패스트캠퍼스
반응형

1. Memoization ? 

Memoization은 컴퓨터 프로그램이 동일한 계산을 반복해야할 때, 이전 계산한 값을 메모리에 저장함으로 동일한 

계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 

 

2. React의 Memoization ? 

1. memo

동일한 props로 렌더링을 한다면, React.memo를 사용하면 성능 향상을 누릴 수 있다. 

memo를 사용하면 React는 컴포넌트를 렌더링하지 않고, 마지막으로 렌더링된 결과를 재사용한다.

 

import React, { useState, useEffect } from "react";
import MemoItem from "./MemoItem";

const commentList = [
  { title: "comment1", content: "message1", likes: 1 },
  { title: "comment2", content: "message2", likes: 1 },
  { title: "comment3", content: "message3", likes: 1 },
  { title: "comment4", content: "message4", likes: 1 },
  { title: "comment5", content: "message5", likes: 1 },
  { title: "comment6", content: "message6", likes: 1 },
];

const Comments = ({ commentList }) => {
  return (
    <div>
      {commentList.map((comment) => (
        <MemoItem
          key={comment.title}
          title={comment.title}
          content={comment.content}
          likes={comment.likes}
        />
      ))}
    </div>
  );
};

const Memo = () => {
  const [comments, setComments] = useState(commentList);

  useEffect(() => {
    const interval = setInterval(() => {
      setComments((prevComment) => [
        ...prevComment,
        {
          title: `comment${prevComment.length + 1}`,
          content: `message${prevComment.length + 1}`,
          likes: 1,
        },
      ]);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <Comments commentList={comments} />;
};

export default Memo;

1초에 댓글이 하나씩 추가되는 예시를 만들어봤다. 

 

useEffect()에서 setInterval를 사용해서 1초마다 setComments의 배열에 하나씩 추가하는 방식이다. 

import React, { memo, Profiler } from "react";

const MemoItem = ({ title, content, likes }) => {
  return (
    <Profiler
      id="CommentItem"
      onRender={(
        id,
        phase,
        actualDuration,
        baseDuration,
        startTime,
        comitTime,
        interactions
      ) => {
        console.log(`actualDuration : ${actualDuration}`);
      }}
    >
      <div
        style={{
          borderBottom: `1px solid grey`,
          padding: `10px`,
          cursor: `pointer`,
        }}
      >
        <span>{title}</span>
        <br />
        <span>{content}</span>
        <br />
        <span>{likes}</span>
      </div>
    </Profiler>
  );
};

export default memo(MemoItem);

중요한 곳은 이곳으로 memo() 를 사용하지 않는다면, 댓글이 업데이트 될 때마다 모든 MemoItem 컴포넌트가 다시 

렌더링이 발생한다. 

 

memo()를 사용한다면, 이미 있는 MemoItem은 그대로 사용하고 새롭게 추가된 MemoItem만 렌더링을 하게 된다. 

 

※ Profiler ? 

중간에 특이한 태그를 봤을 건데, 해당 기능은 React에서 제공하는 성능 측정 API로 태그 안에 있는 내용의 렌더링에

걸리는 시간을 측정한다.

 

2 useCallback

만약 부모에게서 props로 함수를 받는다면 어떻게 될까?? 

import React, { useState, useEffect } from "react";
import MemoComments from "./MemoComments";

const commentList = [
  { title: "comment1", content: "message1", likes: 1 },
  { title: "comment2", content: "message2", likes: 1 },
  { title: "comment3", content: "message3", likes: 1 },
  { title: "comment4", content: "message4", likes: 1 },
  { title: "comment5", content: "message5", likes: 1 },
  { title: "comment6", content: "message6", likes: 1 },
];

const Memo = () => {
  const [comments, setComments] = useState(commentList);

  useEffect(() => {
    const interval = setInterval(() => {
      setComments((prevComment) => [
        ...prevComment,
        {
          title: `comment${prevComment.length + 1}`,
          content: `message${prevComment.length + 1}`,
          likes: 1,
        },
      ]);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <MemoComments commentList={comments} />;
};

export default Memo;

최 상위 부모로 1초에 1번씩 댓글을 만들어내고 있다. 

 

import React from "react";
import MemoItem from "./MemoItem";

export default function MemoComments({ commentList }) {
  const clickHandler = () => {
    alert("눌렸음!");
  };

  return (
    <div>
      {commentList.map((comment) => (
        <MemoItem
          key={comment.title}
          title={comment.title}
          content={comment.content}
          likes={comment.likes}
          onClick={clickHandler}
        />
      ))}
    </div>
  );
}

두 번째로 MemoItem에게 click 함수를 만들어서 전달해주고 있다. 

 

import React, { memo, Profiler } from "react";

const MemoItem = ({ title, content, likes, onClick }) => {
  return (
    <Profiler
      id="CommentItem"
      onRender={(
        id,
        phase,
        actualDuration,
        baseDuration,
        startTime,
        comitTime,
        interactions
      ) => {
        console.log(`actualDuration : ${actualDuration}`);
      }}
    >
      <div
        style={{
          borderBottom: `1px solid grey`,
          padding: `10px`,
          cursor: `pointer`,
        }}
        onClick={onClick}
      >
        <span>{title}</span>
        <br />
        <span>{content}</span>
        <br />
        <span>{likes}</span>
      </div>
    </Profiler>
  );
};

export default memo(MemoItem);

memo를 통해서 props의 변화가 없다면 리렌더링되지않고 기존에 있는 MemoItem은 그대로 사용하게 만들었다. 

 

결과를 말하자면, 모든 MemoItem이 다시 리렌더링된다. 

분명 props가 동일한 경우 memo를 통해 재사용하게 만들었는데 왜 이런 결과가 발생한걸까? 

export default function MemoComments({ commentList }) {
  const clickHandler = () => {
    alert("눌렸음!");
  };

  return (
    <div>
      {commentList.map((comment) => (
        <MemoItem
          key={comment.title}
          title={comment.title}
          content={comment.content}
          likes={comment.likes}
          onClick={clickHandler}
        />
      ))}
    </div>
  );
}

MemoIteml의 부모인 MemoComments의 props인 commentList가 1초당 댓글이 추가되므로 변경되기때문에 

부모 자체가 리렌더링되고있어서 발생하는 문제다. 

 

부모가 리렌더링되기 때문에 clickHandler라는 함수가 1초마다 새롭게 만들어지고 있어서 MemoItem에게 넘기는 

props에 변화가 생겨 memo를 사용하더라도 전체가 리렌더링되는 것이다. 

 

문제 해결을 위해서 사용되는 것이 useCallback이다. 

 
const clickHandler = useCallback(() => {
    alert("눌렸음!"); 
}, []);

useCallback은 dependency Array가 변경될 경우에만 함수를 새롭게 만들어주는 Hook이다. 

 

그렇기때문에 commentList가 변경되더라도 dependency Array 안에 commentList가 없기때문에 함수는 새롭게 만들어

지지 않는다.

 

3. useMemo

사이트를 만들다가 useState 같이 동적으로 변화하는 값을 다루는 일은 많이 한다. 

import React, { memo, Profiler, useMemo, useState } from "react";

const MemoItem = ({ title, content, likes, onClick }) => {
  const [clickCount, setClickCount] = useState(0);

  const rate = () => {
    console.log("???");
    return likes > 10 ? "Good" : "bad";
  };

  return (
    <Profiler
      id="CommentItem"
      onRender={(
        id,
        phase,
        actualDuration,
        baseDuration,
        startTime,
        comitTime,
        interactions
      ) => {
        console.log(`actualDuration : ${actualDuration}`);
      }}
    >
      <div
        style={{
          borderBottom: `1px solid grey`,
          padding: `10px`,
          cursor: `pointer`,
        }}
        onClick={() => {
          onClick();
          setClickCount((prevCount) => prevCount + 1);
        }}
      >
        <span>{title}</span>
        <br />
        <span>{content}</span>
        <br />
        <span>{likes}</span>
        <br />
        <span>{rate()}</span>
      </div>
    </Profiler>
  );
};

export default memo(MemoItem);

기존 코드에서 clickCount 라는 조회 기능의 state를 만들고, onClick에서 누를 경우 +1 시켜주는 기능을 만들었다. 

 

그리고 추가로 rate()라는 함수를 만들었는데, 

const rate = () => {
  console.log("???");
  return likes > 10 ? "Good" : "Bad";
};

likes를 확인해서 10 이상이면 Good을 리턴, 이하면 Bad를 리턴하게 하였다. 

 

여기서 우리가 댓글중 하나를 클릭하면 해당 댓글의 rate 함수가 다시 호출된다. 

onClick과 rate의 접점은 하나도 없지만, onClick에서 clickCount를 변경해서 해당 컴포넌트가 리렌더링이 발생했다.

 

당연히 state가 바뀌기 때문에 리렌더링 되는것은 당연하다. 하지만, rate 함수가 다시 호출될 필요는 없는 것이다. 

 

이것을 막아주는 기능이 useMemo이다. 

const rate = useMemo(() => {
    console.log("???");
    return likes > 10 ? "Good" : "bad";
}, [likes]);

useMemo의 dependency Array 안에 있는 값이 변경될 때만 다시 호출되게 하는 Hook이다. 

 

useCallback과 useMemo가 헷갈릴텐데, 

useCallback은 return 을 통해서 값을 얻는 함수가 아닌 경우 사용을 하고, useMemo는 return을 통해 값을 얻을때 사용한다.

반응형
저작자표시 비영리 변경금지 (새창열림)

'React > 패스트캠퍼스' 카테고리의 다른 글

[React - 기초] Render Props  (1) 2022.03.18
[React - 기초] Portals  (2) 2022.03.17
[React - 기초] Hooks  (0) 2022.03.10
[React - 기초] React와 리랜더링  (0) 2022.03.09
[React - 기초] JSX, Babel  (0) 2022.03.08
'React/패스트캠퍼스' 카테고리의 다른 글
  • [React - 기초] Render Props
  • [React - 기초] Portals
  • [React - 기초] Hooks
  • [React - 기초] React와 리랜더링
잉여개발자
잉여개발자
풀스택 개발자를 목표로 잉여롭게 개발 공부도 하면서 다양한 취미 생활도 즐기고 있는 잉여 개발자입니다.
  • 잉여개발자
    잉여로운 개발일지
    잉여개발자
    • 분류 전체보기 (789)
      • 개발정보 (36)
      • 개발환경 (7)
      • 개발생활 (19)
      • React (141)
        • 이론 (23)
        • 기능 (12)
        • 실험실 (88)
        • 버그 (6)
        • 패스트캠퍼스 (9)
        • Npm (3)
      • React Native (28)
        • 공통 (6)
        • TypeScript (3)
        • JavaScript (18)
        • 버그 (1)
      • Next.js (30)
        • 이론 (13)
        • 실험실 (13)
        • 버그 (3)
      • Web (35)
      • 알고리즘 (202)
        • 풀이 힌트 (39)
      • JavaScript (47)
      • TypeScript (29)
        • 기초 (27)
        • 실험실 (2)
      • Node.js (13)
        • 이론 (0)
        • 기능 (3)
        • 실험실 (9)
        • 버그 (1)
      • 도커 (4)
      • CCNA (22)
        • 이론 (4)
        • 문제 (18)
      • 취미생활 (167)
        • 잉여로운 칵테일 (2)
        • 잉여의 식물키우기 (130)
        • 잉여로운 여행기 (11)
        • 잉여의 제2외국어 (21)
        • 잉여로운 책장 (2)
      • Java (1)
        • Java의 정석 (1)
      • 꿀팁 공유 (3)
  • 태그

    webpack
    자바스크립트
    네트워크
    CCNA
    CSS
    바질
    영어독학
    다이소
    리얼학습일기
    리얼클래스
    타입스크립트
    redux
    react
    Node.js
    영어회화
    네이버 부스트캠프
    식물
    Babel
    next.js
    바질 키우기
    ReactNative
    리액트
    알고리즘
    덤프
    타일러영어
    javascript
    ChatGPT
    typescript
    Docker
    프로그래머스
  • hELLO· Designed By정상우.v4.10.1
잉여개발자
[React - 기초] Memoization
상단으로

티스토리툴바