[TL;DR]
- memo: 컴포넌트의
props
가 동일할 때 컴포넌트를 리렌더링 시키지 않도록 최적화- useMemo:
의존성 배열
(dependencies) 내의 값들이 동일할 때 다시 계산하지 않도록 최적화
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
는 주로 계산량이 많은 함수의 결과를 메모이제이션(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>;
}
위 코드에서 의존성 배열 안에 있는 todos
나 filter
가 변경되지 않으면 filteredTodos
는 다시 계산되지 않으며, 이전의 메모된 값을 재사용하게 됩니다.
memo
와 useMemo
는 각각 props와 의존성 배열의 값이 변했는지 판단하기 위해 JavaScript의 Object.is
메서드를 사용합니다. Object.is
는 값의 동등성을 비교하는 방법으로, 이전 렌더링에서 사용된 값과 현재 렌더링에서 전달된 값이 같은지를 판단합니다.
Object.is
는 일반적인 ===
비교와 유사하지만, 몇 가지 차이점이 있습니다.
예를 들어, Object.is
는 NaN
을 서로 같은 값으로 취급하고, -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가 원시값이 아닐 경우 성능 최적화의 이점이 사라질 수 있다는 것입니다. 원시값은 숫자, 문자열, 불리언과 같이 변하지 않는 값입니다. 하지만 배열이나 객체 같은 참조값(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
: 컴포넌트 자체의 리렌더링을 방지하는 역할. 부모 컴포넌트가 리렌더링되더라도, 자식 컴포넌트의 props가 변하지 않으면 자식 컴포넌트의 렌더링을 건너뛰어 성능을 개선.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