메모이제이션 및 리렌더링 최적화
React.memo·useMemo·useCallback의 적용 조건을 구분하고 측정된 렌더링 비용이 있을 때만 메모이제이션합니다.
웹 애플리케이션의 성능은 초기 로딩 속도만으로 정해지지 않으며, 사용자 상호작용에 대한 응답성(responsiveness)과 UI 갱신의 부드러움도 핵심 지표입니다. React 기반의 Next.js에서 이 응답성을 해치는 대표적인 원인 가운데 하나가 불필요한 다시 렌더링(re-rendering)입니다. 렌더링 횟수뿐 아니라 각 렌더링의 비용을 측정해 병목을 찾아야 하며, 다시 렌더링하더라도 결과가 같으면 실제 DOM은 바뀌지 않을 수 있습니다. 이 글에서는 불필요한 다시 렌더링을 막고 성능을 높이는 핵심 기법인 메모이제이션(memoization)을 알아보고, Next.js에서 React.memo, useMemo, useCallback을 효과적으로 쓰는 방법을 살펴봅니다.
리렌더링과 성능 문제
다음과 같은 변화는 React의 렌더링을 일으킬 수 있습니다. 다만 같은 상태값으로 업데이트하거나 최적화가 적용되면 일부 작업이 생략될 수도 있습니다. 컴포넌트의 로컬 상태(useState)가 바뀌면 그 컴포넌트와 자식 컴포넌트들이 다시 렌더링됩니다. 부모에게서 받은 props가 바뀔 때도, 부모 컴포넌트가 다시 렌더링될 때도 자식이 다시 렌더링될 수 있으며, props가 같다는 사실만으로 언제나 생략되지는 않습니다. 컴포넌트가 구독하는 Context의 값이 바뀌면 그 Context를 쓰는 모든 컴포넌트가 다시 렌더링됩니다. 클래스 컴포넌트의 forceUpdate()도 렌더링을 일으키지만 권장되지 않습니다.
문제는 props가 실제로 바뀌지 않았는데도 부모가 다시 렌더링되는 바람에 자식까지 불필요하게 다시 렌더링되는 경우입니다. 이런 불필요한 렌더링이 많아질수록 가상 DOM 비교(reconciliation)와 실제 DOM 갱신 과정에 부담이 생겨 애플리케이션의 응답성이 떨어질 수 있습니다.
메모이제이션(Memoization)이란?
메모이제이션은 함수의 결과값을 저장(캐싱)해 두었다가 같은 입력이 다시 들어오면 함수를 다시 실행하지 않고 저장된 결과를 곧바로 돌려주는 최적화 기법입니다. 이를 통해 불필요한 계산을 줄여 성능을 높일 수 있습니다. React에서는 이 원리로 컴포넌트의 렌더링, 계산 결과, 콜백 참조를 각각 다시 씁니다.
이 글의 예제는 React Compiler의 자동 메모이제이션을 쓰지 않는 수동 최적화입니다. Next.js 16에서는 next.config의 reactCompiler 옵션으로 React Compiler를 켤 수 있으며, 켜면 많은 경우 컴파일러가 컴포넌트와 값을 자동으로 메모이제이션합니다. 이 옵션은 기본으로 꺼져 있습니다.
React.memo로 컴포넌트 리렌더링 방지
React.memo는 컴포넌트를 감싸, props가 같을 때 부모의 렌더링에 따른 자식의 렌더링을 생략할 수 있게 하는 최적화 도구입니다. 렌더링 생략을 보장하는 장치는 아닙니다. 기본 비교는 각 prop에 Object.is를 적용하며, 자식 자신의 state나 구독한 Context가 바뀌면 그와 별도로 렌더링이 일어날 수 있습니다. React.memo는 순수한(pure) 함수형 컴포넌트이면서, props는 자주 바뀌지 않는데 부모의 렌더링 때문에 불필요하게 자주 렌더링되는 경우에 효과적입니다.
개발 모드의 Strict Mode는 렌더링과 계산을 추가로 호출할 수 있고 캐시도 버려질 수 있으므로, 콘솔에 찍히는 줄 수를 고정된 기대값으로 삼지 않습니다. 먼저 매초 상태가 바뀌어 다시 렌더링되는 부모 컴포넌트 src/app/memo-test/page.tsx를 만듭니다.
"use client"; // 클라이언트 컴포넌트
import { useCallback, useEffect, useState } from 'react';
import MemoizedChild from '@/components/MemoizedChild'; // 메모이제이션된 자식 컴포넌트
import RegularChild from '@/components/RegularChild'; // 일반 자식 컴포넌트
export default function MemoTestPage() {
const [count, setCount] = useState(0);
const [text, setText] = useState('Hello');
useEffect(() => {
const interval = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000); // 1초마다 count 업데이트 (부모 리렌더링 유발)
return () => clearInterval(interval);
}, []);
const memoizedValue = 'Memoized Value'; // 문자열 값은 렌더 사이에도 동일함
const memoizedCallback = useCallback(() => {
console.log('Memoized Callback executed');
}, []);
console.log('부모 컴포넌트 리렌더링:', count);
return (
<div style={{ padding: '20px', maxWidth: '800px', margin: '20px auto', border: '1px solid #ff9800', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)' }}>
<h1 style={{ color: '#ff9800', textAlign: 'center', marginBottom: '20px' }}>메모이제이션 테스트</h1>
<p style={{ textAlign: 'center', fontSize: '1.2em' }}>
부모 카운트: <span style={{ fontWeight: 'bold' }}>{count}</span>
</p>
<div style={{ display: 'flex', justifyContent: 'space-around', marginTop: '30px' }}>
<div style={{ flex: 1, padding: '15px', border: '1px solid #ccc', margin: '10px', borderRadius: '8px', backgroundColor: '#e9ffe9' }}>
<h2>일반 자식 컴포넌트</h2>
<RegularChild data={text} /> {/* data prop이 변하지 않음 */}
</div>
<div style={{ flex: 1, padding: '15px', border: '1px solid #ccc', margin: '10px', borderRadius: '8px', backgroundColor: '#e0f7fa' }}>
<h2>메모이제이션된 자식</h2>
<MemoizedChild
data={memoizedValue}
onClick={memoizedCallback}
/>
</div>
</div>
<button
onClick={() => setText(prev => prev === 'Hello' ? 'World' : 'Hello')}
style={{ display: 'block', margin: '20px auto', padding: '10px 20px', backgroundColor: '#6200ea', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
텍스트 변경 (일반 자식 props 변경)
</button>
</div>
);
}부모는 setInterval로 1초마다 count를 늘려 스스로 다시 렌더링되고, 그때마다 두 자식에게 props를 다시 넘깁니다. 일반 자식은 text를 받고, 메모이제이션한 자식은 문자열 상수 memoizedValue와 useCallback으로 만든 memoizedCallback을 받습니다. 문자열은 원시값이라 렌더링마다 같은 값으로 비교되고, 의존성 배열이 빈 useCallback은 첫 렌더링의 함수 참조를 계속 돌려줍니다. 이제 메모이제이션한 자식 컴포넌트 src/components/MemoizedChild.tsx를 만듭니다.
import React from 'react';
interface MemoizedChildProps {
data: string;
onClick: () => void;
}
// props가 같으면 부모 렌더에 따른 자식 렌더를 생략할 수 있도록 래핑
const MemoizedChild = React.memo(function MemoizedChild({ data, onClick }: MemoizedChildProps) {
console.log('메모이제이션된 자식 컴포넌트 리렌더링');
return (
<div style={{ border: '1px dashed #007bff', padding: '10px', marginTop: '10px' }}>
<p>데이터: <span style={{ fontWeight: 'bold', color: '#007bff' }}>{data}</span></p>
<button onClick={onClick} style={{ padding: '8px 12px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
클릭 (Memoized)
</button>
<p style={{ fontSize: '0.8em', color: '#555', marginTop: '10px' }}>
(같은 데이터와 onClick 참조를 유지해 부모 렌더에 따른 재사용을 돕습니다)
</p>
</div>
);
});
export default MemoizedChild;React.memo는 새 props를 이전 props와 하나씩 Object.is로 비교해, 모두 같을 때만 그 자식의 렌더링을 건너뛸 수 있게 합니다. 그래서 부모가 onClick에 useCallback 없이 매번 새 화살표 함수를 넘겼다면, 함수 내용이 같아도 참조가 달라 비교에 실패합니다. 비교할 일반 자식 컴포넌트 src/components/RegularChild.tsx도 만듭니다. 이 컴포넌트는 React.memo로 감싸지 않았다는 점만 다르며, 렌더링될 때마다 콘솔에 기록을 남깁니다.
interface RegularChildProps {
data: string;
}
function RegularChild({ data }: RegularChildProps) {
console.log('일반 자식 컴포넌트 리렌더링');
return (
<div style={{ border: '1px dashed #28a745', padding: '10px', marginTop: '10px' }}>
<p>데이터: <span style={{ fontWeight: 'bold', color: '#28a745' }}>{data}</span></p>
<p style={{ fontSize: '0.8em', color: '#555', marginTop: '10px' }}>
(부모 카운트가 변경될 때마다 리렌더링됨)
</p>
</div>
);
}
export default RegularChild;count의 타이머 업데이트는 부모의 렌더링을 요청합니다. 일반 자식은 기본적으로 함께 렌더링되고, 메모이제이션한 자식은 같은 문자열과 콜백 참조를 다시 쓰므로 렌더링을 생략할 조건을 갖춥니다. 콘솔로 이 차이를 살펴보되, 타이머의 정확한 간격이나 처음 한 번만 출력된다는 점을 보장으로 해석하지 않습니다.
useMemo를 사용한 값 메모이제이션
useMemo 훅은 계산 비용이 큰 값을 메모이제이션해, 의존성 배열(deps)의 값이 바뀔 때만 다시 계산하게 합니다. 렌더링할 때마다 반복되는 복잡한 계산, 예를 들어 큰 배열을 거르거나 정렬하거나 복잡한 객체를 만드는 작업을 최적화하는 데 유용합니다.
다음은 부모 컴포넌트에서 바꿀 부분입니다. 기존 import에 useMemo를 추가하고 생략된 코드도 그대로 둡니다. 10개 원소의 계산은 동작을 보여 주려는 것일 뿐, 성능 병목으로 측정된 작업은 아닙니다.
// ... (기존 임포트 및 상태) ...
export default function MemoTestPage() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
useEffect(() => {
const interval = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
// 1. useMemo를 사용하여 계산 결과 메모이제이션
// items가 같고 캐시가 유지되면 이전 계산 결과를 재사용합니다.
const expensiveResult = useMemo(() => {
console.log('복잡한 계산 실행 (useMemo)');
// 실제로는 여기에 복잡한 연산이 들어감
return items.filter(item => item % 2 === 0).reduce((acc, curr) => acc + curr, 0);
}, [items]);
console.log('부모 컴포넌트 리렌더링:', count);
return (
<div style={{ /* ... 스타일 */ }}>
{/* ... (기존 UI) ... */}
<h2 style={{ textAlign: 'center', marginTop: '30px' }}>useMemo 테스트</h2>
<p style={{ textAlign: 'center' }}>
복잡한 계산 결과: <span style={{ fontWeight: 'bold', color: '#8800ff' }}>{expensiveResult}</span>
</p>
<button
onClick={() => setItems(prev => [...prev, prev.length + 1])}
style={{ display: 'block', margin: '20px auto', padding: '10px 20px', backgroundColor: '#6200ea', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
아이템 추가 (useMemo 재계산 유발)
</button>
</div>
);
}count만 바뀌면 items의 참조가 유지되므로 계산 결과를 다시 쓸 수 있습니다. 버튼은 새 배열을 만들므로 다시 계산할 대상이 됩니다. 이 코드로 계산하면 처음의 짝수 합은 30이고, 11을 추가해도 합은 30입니다. 결과값이 같다는 사실만으로 계산을 생략했다고 판단하지 않습니다.
useCallback을 사용한 함수 메모이제이션
useCallback 훅은 의존성이 유지되는 동안 이전의 함수 참조를 돌려줍니다. 렌더링마다 새 함수 표현식이 만들어지는 일 자체를 막지는 않습니다. 이는 자식 컴포넌트에 콜백 함수를 props로 넘길 때 특히 중요합니다. 함수 인스턴스가 계속 새로 만들어지면 React.memo로 감싼 자식 컴포넌트가 props가 바뀌었다고 판단해 불필요하게 다시 렌더링될 수 있기 때문입니다. 그래서 useCallback은 React.memo로 최적화한 자식 컴포넌트에 콜백 함수를 props로 넘길 때 씁니다.
// ... (기존 임포트 및 상태) ...
export default function MemoTestPage() {
// ... (기존 count, items 상태) ...
// 1. useCallback을 사용하여 콜백 함수 메모이제이션
// 의존성이 유지되면 useCallback은 캐시된 함수 참조를 반환합니다.
const handleClick = useCallback(() => {
console.log('버튼이 클릭되었습니다.');
// 여기서는 count 상태를 사용하지 않으므로 의존성 배열에 count를 넣을 필요 없음
}, []);
console.log('부모 컴포넌트 리렌더링:', count);
return (
<div style={{ /* ... 스타일 */ }}>
{/* ... (기존 UI) ... */}
<div style={{ flex: 1, padding: '15px', border: '1px solid #ccc', margin: '10px', borderRadius: '8px', backgroundColor: '#e0f7fa' }}>
<h2>메모이제이션된 자식</h2>
<MemoizedChild
data="Memoized Value"
onClick={handleClick}
/>
</div>
{/* ... (나머지 UI) ... */}
</div>
);
}src/components/MemoizedChild.tsx는 그대로 씁니다. MemoizedChild에 넘기는 handleClick의 참조가 유지되면, 같은 data와 함께 props 비교를 통과해 부모의 렌더링에 따른 자식의 렌더링을 생략할 수 있습니다.
NEXT · 12.4
메모이제이션은 컴포넌트·값·함수 참조를 구분합니다
본문에서는 count가 바뀌어도 같은 props를 전달하는 자식, items를 의존성으로 계산하는 합계, 빈 의존성으로 참조를 재사용하는 콜백을 비교합니다.
| 대상 | 재사용 조건과 효과 | 남는 작업 또는 경계 |
|---|---|---|
| 컴포넌트 | memo: 각 prop이 Object.is로 같으면 부모 렌더에 따른 자식 렌더를 생략할 수 있습니다. | 자식의 state·context 변경은 별개입니다. 렌더 생략 자체가 보장은 아닙니다. |
| 계산값 | useMemo: 의존성이 같고 캐시가 유지되면 계산 결과를 재사용합니다. | 첫 계산과 의존성 비교는 남습니다. React가 캐시를 폐기할 수도 있습니다. |
| 콜백 | useCallback: 의존성이 같고 캐시가 유지되면 같은 함수 참조를 반환합니다. | 새 함수 표현식의 생성은 막지 않습니다. 콜백을 호출하는 것도 아닙니다. |
- 컴포넌트
- 재사용 조건과 효과:
memo: 각 prop이Object.is로 같으면 부모 렌더에 따른 자식 렌더를 생략할 수 있습니다.남는 작업 또는 경계: 자식의 state·context 변경은 별개입니다. 렌더 생략 자체가 보장은 아닙니다. - 계산값
- 재사용 조건과 효과:
useMemo: 의존성이 같고 캐시가 유지되면 계산 결과를 재사용합니다.남는 작업 또는 경계: 첫 계산과 의존성 비교는 남습니다. React가 캐시를 폐기할 수도 있습니다. - 콜백
- 재사용 조건과 효과:
useCallback: 의존성이 같고 캐시가 유지되면 같은 함수 참조를 반환합니다.남는 작업 또는 경계: 새 함수 표현식의 생성은 막지 않습니다. 콜백을 호출하는 것도 아닙니다.
items에 11을 더하면 배열 참조가 바뀌어 합계를 다시 계산해도 짝수 합계는 30입니다. 결과가 같다는 사실만으로 계산 생략을 판단하지 않습니다. 세 도구의 캐시는 성능 최적화이며 프로그램의 정확성을 맡기는 저장소가 아닙니다.
위 표는 메모이제이션이 무엇을 다시 쓰는지 세 대상으로 나누어 비교합니다. 컴포넌트에 쓰는 memo는 각 prop이 Object.is로 같으면 부모의 렌더링에 따른 자식의 렌더링을 생략할 수 있지만, 자식의 state나 Context 변경은 별개이고 렌더링 생략 자체가 보장되지는 않습니다. 계산값에 쓰는 useMemo는 의존성이 같고 캐시가 유지되면 계산 결과를 다시 쓰지만, 첫 계산과 의존성 비교는 남으며 React가 캐시를 버릴 수도 있습니다. 콜백에 쓰는 useCallback은 의존성이 같고 캐시가 유지되면 같은 함수 참조를 돌려주지만, 새 함수 표현식의 생성을 막거나 콜백을 호출하지는 않습니다.
표의 아래 설명처럼 items에 11을 더하면 배열 참조가 바뀌어 합계를 다시 계산하더라도 짝수 합계는 30입니다. 세 도구의 캐시는 성능 최적화일 뿐, 프로그램이 올바르게 동작하는지를 맡기는 저장소가 아닙니다.
메모이제이션 사용 시 고려사항 및 팁
메모이제이션을 적용하기 전에 먼저 서버 컴포넌트와 클라이언트 컴포넌트를 구분해야 합니다. React.memo, useMemo, useCallback은 상태나 상호작용으로 다시 렌더링되는 클라이언트 컴포넌트("use client")의 최적화 도구입니다. 서버 컴포넌트도 요청, 탐색, 재검증 때 서버에서 다시 실행될 수 있지만 클라이언트 상태 훅을 쓰지 않습니다. 따라서 어느 쪽에서 렌더링되는지를 먼저 정하고, 클라이언트 렌더링 비용을 측정한 뒤 이 도구를 적용합니다.
메모이제이션을 남용하지 않습니다. 메모이제이션은 성능 최적화에 도움이 되지만, 메모리 사용과 비교 연산이라는 비용도 있습니다. 모든 컴포넌트, 값, 함수에 무분별하게 적용하면 오히려 성능이 떨어질 수 있으므로 필요한 곳에만 씁니다. 적용하기 전과 후에 React DevTools Profiler나 Chrome Lighthouse 같은 프로파일링 도구로 실제 성능이 나아졌는지 측정하는 것이 중요합니다.
useMemo와 useCallback의 의존성 배열은 정확히 지정해야 합니다. 의존성이 빠지면 오래된(stale) 값을 쓰거나 React.memo가 기대한 대로 동작하지 않을 수 있고, 필요 없는 의존성을 넣으면 메모이제이션이 너무 자주 무효화되어 최적화 효과가 사라지거나 오히려 나빠질 수 있습니다.
객체와 배열의 비교도 주의합니다. React.memo는 props를 얕게 비교하므로, 객체나 배열을 넘길 때 내용이 같아도 참조가 달라지면 바뀌었다고 판단해 다시 렌더링합니다. 이 경우 useMemo나 useCallback으로 참조를 유지하거나, React.memo의 두 번째 인자로 사용자 정의 비교 함수를 넘겨야 할 수 있습니다.
실제로 적용할 때는 부모가 왜 다시 렌더링되었는지, 자식에게 넘기는 props의 참조가 안정적인지, 자식의 렌더링 비용이 충분히 큰지를 차례로 확인하면 좋습니다. 메모이제이션은 렌더링 비용이 확인된 구간에만 제한적으로 적용하고, 적용 전후를 프로파일러로 비교해 효과를 확인하세요.
더 깊이: React Compiler를 켜면 달라지는 것
React Compiler는 빌드할 때 코드를 분석해 메모이제이션을 자동으로 넣는 도구입니다. React 문서의 React Compiler 소개에 따르면 이 자동 메모이제이션은 이미 있는 컴포넌트를 다시 렌더링할 때의 성능에 초점을 두며, 부모 하나가 바뀌었을 때 아래 컴포넌트들이 줄줄이 다시 렌더링되는 것과 컴포넌트 안의 비싼 계산을 다시 하는 것을 건너뛰게 합니다. 메모이제이션의 대상은 React 컴포넌트와 훅이며 모든 함수가 아닙니다. 컴파일러는 React의 규칙(Rules of React)을 이해하고 동작하므로, 기존 코드가 그 규칙을 얼마나 잘 지켰는지가 도입의 난이도를 정합니다.
Next.js 16에서 reactCompiler 옵션은 실험 기능에서 정식 옵션이 되었지만 기본으로 꺼져 있습니다. 컴파일러가 Babel 플러그인(babel-plugin-react-compiler)으로 실행되므로 켜면 개발과 빌드의 컴파일 시간이 늘어날 수 있고, Next.js는 JSX나 훅이 있는 파일에만 컴파일러를 적용하는 SWC 최적화로 이 비용을 줄입니다. 한꺼번에 켜기 부담스럽다면 compilationMode: 'annotation'으로 두고, 컴파일러를 적용할 컴포넌트나 훅에만 "use memo" 지시어를 붙일 수 있으며, 반대로 "use no memo"로 특정 컴포넌트를 뺄 수도 있습니다.
이 글에서 직접 쓴 useMemo, useCallback, React.memo는 컴파일러를 켠 뒤에도 그대로 동작합니다. React 문서는 새 코드에서는 컴파일러에 맡기고 정밀한 제어가 필요한 곳에만 이 훅들을 쓰되, 기존 코드의 메모이제이션은 지우면 컴파일 결과가 달라질 수 있으므로 그대로 두거나 충분히 테스트한 뒤 지우라고 권합니다.
정리
- 렌더링은 state, props, 부모의 렌더링, Context 변경으로 일어나며, 성능 문제는 비용이 큰 불필요한 렌더링에서 생깁니다.
React.memo는 각 prop이Object.is로 같을 때 부모에 따른 자식의 렌더링을 생략할 수 있게 하지만, 생략을 보장하지는 않습니다.useMemo는 의존성이 같으면 계산 결과를,useCallback은 함수 참조를 다시 쓰며, 의존성은Object.is로 비교하므로 배열과 객체는 내용이 같아도 참조가 다르면 바뀐 것으로 봅니다.- 세 도구는 클라이언트 컴포넌트의 최적화 도구이며, Next.js 16에서는
reactCompiler옵션으로 자동 메모이제이션을 켤 수도 있습니다. - 메모이제이션은 측정한 병목에만 적용하고, 적용 전후를 프로파일러로 비교합니다.
연습 문제
1. 값과 참조를 따로 확인하기
useMemo 예제의 버튼을 연달아 두 번 누르면, 각 클릭 뒤의 짝수 합은 어떻게 될까요? 값은 그대로인 복사 배열을 새로 만들어 setItems([...items])에 넘겼다면 의존성도 같다고 볼 수 있을까요? 다른 최적화나 캐시 폐기는 없다고 가정합니다.
해설 보기
첫 클릭은 11을 추가하므로 합이 30, 둘째 클릭은 12를 추가하므로 30 + 12 = 42입니다. 매번 새 배열을 만들기 때문에 의존성을 비교하면 이전 배열과 같지 않아 다시 계산 대상이 됩니다. 내용이 같은 복사 배열도 참조가 다르므로 Object.is 비교는 false입니다. 계산 결과가 같다고 의존성이 같았다는 뜻은 아닙니다.
2. 콜백이 count를 읽게 바꾸기
useCallback 예제의 handleClick이 console.log(count)로 현재 카운트를 출력하도록 바꾸면서, 의존성 배열은 [] 그대로 두었습니다. 버튼을 눌렀을 때 어떤 값이 찍힐까요? 어떻게 고쳐야 할까요?
해설 보기
함수가 처음 만들어질 때의 count인 0이 계속 찍힙니다. 빈 의존성 배열 때문에 useCallback이 처음 렌더링의 함수를 계속 돌려주고, 그 함수는 처음 렌더링의 count를 가둬 두기 때문입니다. 의존성 배열에 count를 넣어 [count]로 고치면 최신 값이 찍히지만, count가 매초 바뀌므로 함수 참조도 매초 바뀌어 MemoizedChild의 렌더링 생략 효과는 사라집니다. 함수를 다시 쓰는 최적화와 함수가 읽는 값의 최신성을 함께 확인해야 합니다.
3. 객체 prop을 넘기는 memo 자식
MemoizedChild에 data 대신 style={{ color: 'blue' }}처럼 객체 prop을 렌더링마다 새로 만들어 넘기도록 바꿨습니다. 부모가 매초 다시 렌더링될 때 MemoizedChild의 렌더링은 생략될까요?
해설 보기
생략되지 않습니다. { color: 'blue' }는 렌더링할 때마다 새 객체가 되므로, 내용이 같아도 Object.is 비교에서 이전 객체와 다르다고 판단됩니다. 참조를 유지하려면 객체를 컴포넌트 바깥의 상수로 빼거나, useMemo(() => ({ color: 'blue' }), [])로 만들어 넘깁니다.
메모이제이션 도구의 정확한 동작은 React 문서의 memo, useMemo, useCallback에서 확인할 수 있습니다.
이것으로 12장을 마칩니다. 12장에서는 이미지와 폰트 최적화, 코드 분할과 지연 로딩, 메모이제이션으로 Next.js 애플리케이션의 성능을 높이는 방법을 정리했습니다. 다음 장의 정적 메타데이터 설정부터는 검색 엔진과 공유 미리보기를 위한 메타데이터를 설정하는 방법을 알아봅니다.