[React] memo, useMemo 의 차이

오다혜·2024년 10월 13일
0
post-custom-banner

[TL;DR]

  • memo: 컴포넌트의 props 가 동일할 때 컴포넌트를 리렌더링 시키지 않도록 최적화
  • useMemo: 의존성 배열(dependencies) 내의 값들이 동일할 때 다시 계산하지 않도록 최적화

memo


React.memo 를 사용하면 컴포넌트의 props가 변경되지 않은 경우에 리렌더링하지 않도록 해줍니다.

컴포넌트를 memo로 감싸면, props가 변하지 않는 한 해당 컴포넌트가 리렌더링되지 않습니다. React는 기본적으로 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 자동으로 리렌더링하는데, memo를 사용하면 props가 동일한 경우 렌더링을 건너뛰어 성능을 최적화할 수 있습니다.

예를 들어, 다음 코드에서 Greeting 컴포넌트는 부모 컴포넌트가 리렌더링되어도 name이라는 props가 동일한 값이라면 다시 렌더링되지 않습니다.

import { memo } from 'react';

const Greeting = memo(function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
});

useMemo


useMemo는 주로 계산량이 많은 함수의 결과를 메모이제이션(memoization)하는 데 사용됩니다.

React 는 컴포넌트가 렌더링될 때마다 내부 함수를 다시 실행하기 때문에, 그 함수가 복잡한 계산을 포함할 경우 성능 저하가 발생할 수 있습니다. 이를 방지하기 위해 useMemo는 특정 값이 변경되지 않는 한 이전에 계산된 값을 반환하여 불필요한 재계산을 피합니다.

예를 들어, 큰 배열을 필터링하는 경우 useMemo를 사용해 성능을 최적화할 수 있습니다.

import { useMemo } from 'react';

function TodoList({ todos, filter }) {
  const filteredTodos = useMemo(() => {
    return todos.filter(todo => todo.status === filter);
  }, [**todos, filter**]);

  return <ul>{filteredTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}

위 코드에서 의존성 배열 안에 있는 todosfilter가 변경되지 않으면 filteredTodos는 다시 계산되지 않으며, 이전의 메모된 값을 재사용하게 됩니다.

memo, useMemo 사용 시 유의할 점


Object.is 로 비교

memouseMemo는 각각 props와 의존성 배열의 값이 변했는지 판단하기 위해 JavaScript의 Object.is 메서드를 사용합니다. Object.is는 값의 동등성을 비교하는 방법으로, 이전 렌더링에서 사용된 값과 현재 렌더링에서 전달된 값이 같은지를 판단합니다.

Object.is는 일반적인 === 비교와 유사하지만, 몇 가지 차이점이 있습니다.
예를 들어, Object.isNaN을 서로 같은 값으로 취급하고, -0+0을 구분합니다. 이런 특성 때문에 Object.is는 React가 값을 좀 더 정확하게 비교할 수 있도록 도와줍니다. React는 이 Object.is 비교 방법을 사용하여 props나 의존성 배열에서 값이 변경되었는지 감지하고, 변경되지 않았다면 재렌더링을 방지합니다.

// ===
console.log(NaN === NaN) // false
console.log(-0 === +0) // true

// Object.is
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(-0, +0)) // false

이 때 비교하는 대상은 직전 렌더링 시 계산한 값입니다. 때문에 바로 이전에 계산한 값과 달라진다면 무조건 다시 계산을 하게 됩니다.

⭐️ memo에 전달되는 props가 원시값이 아닐 때

memo를 사용할 때 주의할 점은, props가 원시값이 아닐 경우 성능 최적화의 이점이 사라질 수 있다는 것입니다. 원시값은 숫자, 문자열, 불리언과 같이 변하지 않는 값입니다. 하지만 배열이나 객체 같은 참조값(Reference Type)은 매번 새롭게 생성되기 때문에 Object.is 비교에서 다른 값으로 인식됩니다.

예를 들어, 아래 코드에서는 memo가 제대로 동작하지 않을 수 있습니다.

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

const MyComponent = memo(function MyComponent({ obj }) {
  console.log('MyComponent rendered');
  return <div>{obj.name}</div>;
});

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

  // 새로운 객체가 매번 생성됨
  const obj = { name: 'React' };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MyComponent obj={obj} />
    </div>
  );
};

export default ParentComponent;

위 코드에서 obj는 매번 새롭게 생성된 객체이므로, 부모 컴포넌트가 리렌더링될 때마다 obj의 참조값이 달라져 memo는 항상 새로운 값으로 인식하고, 결국 MyComponent가 다시 렌더링됩니다. 따라서, 객체나 배열이 props로 전달될 때는 memo의 이점이 거의 없어질 수 있습니다.

memo와 useMemo의 차이점

  • memo: 컴포넌트 자체의 리렌더링을 방지하는 역할. 부모 컴포넌트가 리렌더링되더라도, 자식 컴포넌트의 props가 변하지 않으면 자식 컴포넌트의 렌더링을 건너뛰어 성능을 개선.
  • useMemo: 컴포넌트 내부에서 계산 비용이 높은 값을 메모이제이션하는 데 사용. 값이 변경되지 않는 한 이전에 계산된 값을 재사용하여 불필요한 계산을 방지.

예제로 알아보는 memo / useMemo

memo 사용 예시

import { memo, useState } from 'react';

const ChildComponent = memo(function ChildComponent({ count }) {
  console.log('ChildComponent rendered');
  return <div>{count}</div>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent count={count2} />
    </div>
  );
}

위 예시에서 ParentComponent가 버튼을 클릭해 상태를 업데이트했지만, count2가 바뀌지 않았으므로 ChildComponent의 props 는 바뀌지 않은 것으로 간주됩니다. 따라서 동일한 props가 전달되므로 memo 덕분에 렌더링이 일어나지 않습니다.

useMemo 사용 예시

import React, { useMemo } from 'react';

function ExpensiveCalculation() {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  return result;
}

function MyComponent({ value }) {
  const calculatedValue = useMemo(() => ExpensiveCalculation(), [value]);

  return <div>{calculatedValue}</div>;
}

useMemo를 사용하지 않는다면 MyComponent가 리렌더링될 때마다 ExpensiveCalculation 함수가 다시 실행됩니다. useMemo는 이 계산을 캐싱하여, value가 변경되지 않으면 이전 결과를 재사용하게 만듭니다.

후기

memo, useMemo 가 어떻게 다른지 정확하게 알게 되었습니다. 특히, memo 를 사용해도 memoization 이 안 되는 경우가 있었는데 원시값이 아니라 객체나 배열을 넘겨서 렌더링 시에 계속 새로운 값으로 인식되기 때문이라는 것을 알게 되었습니다.

참고 문서

https://ko.react.dev/reference/react/useMemo
https://ko.react.dev/reference/react/memo
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is

profile
프론트엔드에 백엔드 한 스푼 🥄
post-custom-banner

0개의 댓글