리액트 메모이제이션

김범식·2023년 8월 8일
0

React

목록 보기
2/3
post-thumbnail

⭐ 리액트 메모이제이션

리액트의 메모이 제이션은 함수 컴포넌트 내에서 불필요한 재계산을 방지하고 컴포넌트의 성능을 최적화 하기 위한 기술이다.

  • React.memo
  • useMemo
  • useCallback

다음과 같은 함수를 활용하여 메모이제이션을 구현할 수 있다.



⭐ Virtual DOM


먼저 메모이제이션이 왜 필요한지 알아보자

React의 랜더링 과정은 위 그림과 같다.

  • 처음 랜더링시 ReactVirtual DOM을 생성한다.
  • 이후 상태(state) 나 속성(props)이 변경될 때마다 새로운 Virtual DOM을 생성한다.
  • 변경된 Virtual DOM 과 이전 Virtual DOM을 비교하여 변경된 부분을 찾아낸다
  • 변경된 부분을 실제 DOM에 반영하며 이것을 확정(Commit) 이라고 한다.
  • 실제 DOM을 직접 조작하는 대신 변경된 부분만 최소한의 작업으로 업데이트한다.
  • 확정(Commit) 단계가 끝나면 브라우저는 실제 DOM을 업데이트하고 변경된 내용이 화면에 반영된다.

이처럼 virtual DOM을 사용하면 실제 DOM을 조작하는것 보다 효율적으로 화면을 랜더링 할 수 있다.

여기서 문제는 사소한 변경(state, props)이 생기기라도 하면 virtual DOM을 처음부터 다시 그린다는 것이다. 모든 컴포넌트가 변경될 일이 없기 때문에 , React에서는 컴포넌트를 메모이제이션 하여 불필요한 리랜더링을 방지 할 수 있다.

예를들어 부모컴포넌트의 사소한 변경에 자식컴포넌트들이 전부다 재 랜더링되는건 상당한 낭비라고 할 수 있다.



⭐ React.memo


const MyComponent = React.memo((props) => {
  // props를 이용한 컴포넌트 렌더링
});

// 사용법 예시
<MyComponent prop1={value1} prop2={value2} />

react.memo의 사용은 컴포넌트 전체를 감싸면서 시작된다. React.memo로 감싼 컴포넌트는 props가 변경되지 않는다면 해당컴포넌트는 다시 virtual DOM을 그릴 때 다시 만들지 않고 메모이제이션한 컴포넌트를 재 사용하게 된다.



⭐ useMemo


const memoizedValue = useMemo(() => {
  // 계산할 값 또는 함수
}, [dependency1, dependency2]);

useMemo는 특정 값의 계산을 메모이제이션하여 의존성이 변경되지 않는 한 이전에 계산한 결과를 재사용한다.

📌 불필요한 재계산 생략

useMemo는 주로 계산이 오래걸리는 작업의 결과를 보존할 때 사용한다. 그렇지 않으면 컴포넌트를 재 랜더링할 때마다 계산을 다시 해야하기 때문이다.

import React, { useState, useMemo } from 'react';

const SumCalculator = ({ n }) => {
  // 1부터 n까지의 합을 계산하는 함수
  const calculateSum = (n) => {
    console.log('Calculating sum...');
    let sum = 0;
    for (let i = 1; i <= n; i++) {
      sum += i;
    }
    return sum;
  };

  // useMemo를 사용하여 결과를 메모이제이션
  const sum = useMemo(() => calculateSum(n), [n]);

  return (
    <div>
      <p>1부터 {n}까지의 합: {sum}</p>
    </div>
  );
};

export default SumCalculator;

다음 코드는 1~n까지의 합을 계산하는 함수의 값을 메모이제이션을 사용하여 재사용하고 있다. 만약 useMemo를 사용하지 않는다면 해당 컴포넌트를 사용할 때마다 SumCalculator함수를 내부 로직이 다시 수행될 것이다.

위의 예시에서는 n의 값이 변경되지 않는이상 sum은 메모이제이션된 값을 사용하게 된다.



⭐ useCallback


const memoizedCallback = useCallback(() => {
  // 콜백 함수 로직
}, [dependency1, dependency2]);

React에서 컴포넌트가 리랜더링 될 때 , 함수 컴포넌트는 내부에 선언된 모든 함수를 새로 생성하게 된다.

📌 불필요한 리랜더링 방지

부모 컴포넌트가 리랜더링되면 자식 컴포넌트도 함께 리랜더링 된다. 이 때 자식컴포넌트의 함수가 불필요하게 리랜더링 되는것을 방지하기 위해 사용한다.

📌 자식 컴포넌트에 전달하는 콜백함수 재생성 방지

컴포넌트는 stateprops가 변경되면 리랜더링하게 되어있다.

그럴 때마다 컴포넌트에 모든 함수를 재생성하는데 만약 그 재생성 함수가 자식컴포넌트의 props로 전달하는 함수라면 이전 함수와 형태는 동일하지만 다른 객체로 판단하기 때문에 자식 컴포넌트는 불필요하게 리랜더링이 되고 만다.

다음 예시는 useCallback을 사용하는 방법을 보여주고 있다.

자식 컴포넌트

import React from 'react';

// React.memo를 통해 컴포넌트 전체를 메모이제이션 했다. 
const ChildComponent = React.memo(({ onClick }) => {
  console.log('ChildComponent rendered');

  return (
    <button onClick={onClick}>Click me</button>
  );
});

export default ChildComponent;

자식 컴포넌트는 리랜더링하지 않기 위해 React.memo까지 사용했다.

useCallback을 사용하지 않은 부모 컴포넌트

import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [count, setCount] = useState(0);

// ParentComponent가 리랜더링 될때마다 handleClick은 재생성된다. 
  const handleClick = function(){
    setCount((prevCount) => prevCount + 1);
  }

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <p>Count: {count}</p>
    </div>
  );
};

export default ParentComponent;

다음은 useCallback을 사용하지 않은 부모 컴포넌트의 예시이다. 자식 컴포넌트에 있는 button을 누를 때마다 count의 값은 변경되고 ParentComponent리랜더링 된다. 이때 handleClick도 재생성하게 된다. 새로 생성된 함수는 이전함수와 형태는 같지만 다른함수로 취급하기 때문에 해당 handleClick 함수를 props로 전달 받은 ChildComponent는 똑같 형태의 함수를 내려받았음에도 재랜더링되는 참사가 발생한다.

useCallback을 사용한 부모 컴포넌트

import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [count, setCount] = useState(0);

 // 자식에게 넘겨줄 콜백함수를 useCallback을 사용해 메모이제이션 했다
  const handleClick = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <p>Count: {count}</p>
    </div>
  );
};

export default ParentComponent;

useCallback을 사용했기 때문에 부모컴포넌트가 재랜더링 되더라도 ChildComponent는 항상 같은 handleClick 함수를 받게 된다 때문에 ParentComponent가 리랜더링 되더라도 React.memo를 사용한 ChildComponent는 리랜더링 되지 않고 메모이제이션된 값을 사용할 수 있다.


이런 메모이제이션도 주의할 점이 있다. 자주 변경되는 컴포넌트나 함수에 메모이제이션을 사용하게 되면 계속해서 메모이제이션(캐싱)하는 비용이 발생하기 때문에 주의해야한다.

profile
frontend developer

1개의 댓글

comment-user-thumbnail
2023년 8월 8일

유익한 글이었습니다.

답글 달기