React React.memo(), useCallback(), useMemo()

·2022년 6월 30일
1

react

목록 보기
13/24
post-thumbnail

React 최적화 하기

리액트는 컴포넌트 관리 라이브러리 입니다. 컴포넌트 탐색하고 변화한 부분을 찾아, 해당 컴포넌트만 변화시키는 것이 가능합니다. 리액트에서는 자체적으로 앱의 성능을 향상시킬 수 있는 몇가지 기능을 가지고 있습니다. 렌더링을 미리 메모라이징 한 후, 불필요한 렌더링을 회피하는 것이 해당 기능의 핵심 포인트입니다.



React.memo()

리액트는 이전과 렌더링 결과를 비교하여 컴포넌트의 갱신을 결정합니다. React.memo()를 통해 랩핑 된 컴포넌트는 이후 앱이 시작할때, 미리 메모라이징 되며, 미리 렌더링 되었던 이전과 컴포넌트가 동일하다면 해당 내용을 지속적으로 재사용합니다.

그렇다면 모든 컴포넌트를 메모라이징 하는게 제일 나을까요? 아쉽게도 그건 아닙니다. 컴포넌트를 메모라이징하는 것은 상당한 비용적인 측면이 뒤따릅니다. 자주 데이터가 새롭게 갱신되는 컴포넌트는 굳이 메모리가 기억할 필요가 있을까요?? 이는 오히려 성능을 악화시키는 요인으로 작용하게 됩니다.

컴포넌트 마다 어떻게 움직일지를 잘 파악하여 적절하게 사용해야합니다.

아, 참! React.memo()는 기존의 라이프 사이클 메서드 중componentShouldUpdate() 함수형 컴포넌트에서 사용하도록 만든 리액트 전용 HOC (High-Order-Component) 입니다.

// app.js
...
...
return (
  <div className="app">
    <h1>Hi there!</h1>
    {showParagraph && <p>This is new!</p>}
    // 일부러 고정된 데이터만 보내고 있다.
    <DemoOutput show={false}></DemoOutput>
    <Button onClick={allowToggleHandler}>Allow Toggle!</Button>
    <Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
  </div>
);


// DemoOutput.js
import React from "react";
import MyParagraph from "./MyParagraph";

const DemoOutput = (props) => {
  console.log("DemoOupput RUNNING");
  return <MyParagraph>{props.show ? "This is new!" : ""}</MyParagraph>;
};

export default React.memo(DemoOutput);

앱 실행이후 동일한 요소를 렌더링하는 컴포넌트에 쓰면 좋습니다.



useCallback()

useCallback() 을 이해하기 위해서는 원시형과 참조형의 데이터 차이를 먼저 알고 있어야 합니다. 아래의 컴포넌트가 새롭게 갱신되었을때 갱신된 toggleParagraphHandler()toggleparagraphHandler() 를 리액트(자바스크립트)는 두 함수를 동일한 함수로 보지 않습니다.
각각 다른 메모리에 저장되어 주소값이 서로 다르기 때문이죠. 따라서 컴포넌트 재평가 마다 메모리에 새로운 toggleparagraphHandler() 함수가 저장이 될테고, 결국 비효율적인 메모리 낭비를 하게 되는 것 입니다.

function App() {
  const [showParagraph, setShowParagraph] = useState(false);
  const [allowToggle, setAllowToggle] = useState(false);

  console.log("APP RUNNING");

  const toggleParagraphHandler = () => {
    if (allowToggle) {
      setShowParagraph((prevShowParagraph) => !prevShowParagraph);
    }
  };

  const allowToggleHandler = () => {
    setAllowToggle(true);
  };

  return (
    <div className="app">
      <h1>Hi there!</h1>
      {showParagraph && <p>This is new!</p>}
      <DemoOutput show={false}></DemoOutput>
      <Button onClick={allowToggleHandler}>Allow Toggle!</Button>
      <Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
    </div>
  );
}

해당 문제를 해결하는 방법은 필요한 함수에 useCallback() 함수를 적용해주면 됩니다. 사용방법은 useEffect()와 같이 두번째 변수로 의존성 인자값을 넣어주면 됩니다. 이제부터 해당 함수는 의존성 인자값이 변화하지 않는 이상 동일한 참조값을 메모라이징하여 사용합니다. 해당 allowToggle 변경되지 않는 한 재호출 되지 않게 됩니다.

const toggleParagraphHandler = useCallback(() => {
  if (allowToggle) {
    setShowParagraph((prevShowParagraph) => !prevShowParagraph);
  }
},[allowToggle]);


useMemo()

useCallback() 특정 함수를 새로 만들지 않고 재사용하기 위해서 사용한다면, useMemo()는 특정 결과값을 재사용할때 사용합니다.
여기 동일한 결과값을 나타내는 함수 a() 가 있다고 해봅시다.

const app = () => {
    const value = a();
    return <div>{value}</div> 
}

function a() {
    return 1;
}

위에서 컴포넌트가 새롭게 갱신될때마다, 동일한 함수 a()를 새로운 주소에 저장하는 불필요한 작업이 이루어진다고 말씀을 드렸죠. 따라서 동일한 값을 메모라이징 하기 위한 목적으로,useMemo()를 사용합니다.
useMemo() 역시 useCallback() 동일하게 의존성 배열 인자를 받으며, 해당 인자가 변화하는 경우에만 값을 새롭게 메모라이징 합니다.

const app = () => {
    const value = useMemo(()=> a(), [user]);
    return <div>{value}</div> 
}


React.memo(), useCallback(), useMemo() 차이점

우선 공통적으로 3가지 기능 모두, 필요 요소를 메모라이징 하여, 리액트 앱의 성능을 한층 더 높이는데 쓰일 수 있습니다. 서로간의 차이점은, React.memo() 컴포넌트 전체를 담당하는 HOC의 영역이고, useCallback()useMemo() 은 이름처럼 React Hooks 에 속합니다.
useCallback() 은 함수를 새롭게 생성하는 것을 방지하며 의존성 인자가 변화하지 않는 이상 동일한 참조값에서 함수를 재사용하도록 해줍니다. useMemo()는 동일한 결과 값을 반복하여 만드는 것을 방지합니다.

useCallback() 은 함수 생성에 필요한 데이터가 많은 경우, useMemo() 값이 동일하면서도 복잡한 연산을 반복해야하는 경우 매우 유용합니다.

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

0개의 댓글