왜 이 내용을 정리하나
React.memo로 감싼 자식 컴포넌트는 props가 이전과 같으면 렌더링을 건너뛴다. 그래서 자식 컴포넌트 최적화를 하려고 React.memo를 붙여 두었는데도 성능이 개선되지 않는 경우가 생긴다. 이때 원인 중 하나가 부모 컴포넌트 안에서 객체 리터럴을 매번 새로 만들어 props로 넘기는 습관이다. 이 노트에서는 인라인 객체 props가 실제로 자식의 리렌더링을 유발하는지 console.count로 세어 확인하는 방법을 정리해 본다.
개념 설명
JavaScript에서 객체는 참조 타입이다. 내용이 똑같은 두 객체라도 {a: 1}과 {a: 1}은 서로 다른 메모리 위치를 가리키므로 === 비교에서 false가 나온다. React.memo는 props를 얕은 비교(shallow compare)로 검사하기 때문에, 부모가 리렌더링될 때마다 새 객체를 만들면 내용이 같아도 props가 바뀐 것으로 판단하고 자식을 다시 렌더링한다.
이 동작을 막으려면 객체를 useMemo로 감싸 참조를 고정하거나, 객체를 컴포넌트 바깥 상수로 옮기면 된다. 어느 쪽이든 자식이 받는 객체의 참조가 리렌더링 사이에 유지되어야 React.memo가 효과를 낸다.
코드 예제
아래 예제는 부모 컴포넌트가 카운터 state를 바꿀 때마다 자식 컴포넌트의 렌더링 횟수를 console.count로 기록한다. 자식은 React.memo로 감싸져 있고, 부모는 style 객체를 인라인으로 만들어 넘긴다.
javascript
import React, { useState, memo } from 'react';
const Child = memo(function Child({ style }) {
console.count('Child 렌더링');
return
;
});
export default function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 매 렌더링마다 새 객체가 만들어진다
const style = { color: 'navy', fontSize: 16 };
return (
부모 카운트: {count}
setText(e.target.value)} />
);
}
이 코드에서 버튼을 누르거나 입력창에 글자를 칠 때마다 부모가 리렌더링된다. 그때마다 style이 새 객체가 되므로 Child의 console.count 값이 함께 올라간다. 즉 Child가 props 내용과 무관하게 매번 다시 렌더링되는 것을 콘솔 숫자로 확인할 수 있다.
이번에는 style을 useMemo로 감싸 참조를 고정해 본다. 의존성 배열이 비어 있으므로 객체는 처음 한 번만 만들어지고 이후에는 같은 참조가 전달된다.
javascript
import React, { useState, useMemo, memo } from 'react';
const Child = memo(function Child({ style }) {
console.count('Child 렌더링');
return
;
});
export default function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 참조를 고정해 리렌더링 사이에 같은 객체를 유지한다
const style = useMemo(() => ({ color: 'navy', fontSize: 16 }), []);
return (
부모 카운트: {count}
setText(e.target.value)} />
);
}
이 버전에서는 버튼과 입력창을 조작해 부모가 리렌더링되어도 Child의 console.count 값이 처음 한 번 이후 늘어나지 않는다. 부모의 state는 바뀌었지만 Child가 받는 style의 참조가 같으므로 React.memo가 렌더링을 건너뛴 것이다. 두 예제의 콘솔 숫자를 비교하면 인라인 객체가 리렌더링을 유발한다는 점이 드러난다.
헷갈리는 점
첫째, React.memo는 props의 깊은 내용을 비교하지 않는다. 객체 안의 값이 같아도 참조가 다르면 바뀐 것으로 본다. 그래서 객체 내용을 JSON 문자열로 바꿔 비교하는 식의 우회는 보통 권장되지 않는다.
둘째, 객체를 useMemo로 감쌌다고 항상 빨라지는 것은 아니다. 메모이제이션 자체에도 비용이 들고, 자식이 원래 가벼운 컴포넌트라면 React.memo와 useMemo를 함께 쓰는 이득이 작다. console.count로 실제 렌더링 횟수가 줄었는지 먼저 확인하는 편이 좋다.
셋째, 의존성 배열에 값이 들어가면 그 값이 바뀔 때마다 객체가 새로 만들어진다. 예를 들어 useMemo(() => ({ color }), [color])처럼 쓰면 color가 바뀔 때만 새 객체가 생기므로 목적에 맞게 의존성을 골라야 한다.
정리
React.memo로 감싼 자식에 인라인 객체를 props로 넘기면, 부모가 리렌더링될 때마다 객체 참조가 바뀌어 자식도 함께 다시 렌더링된다. console.count를 자식 안에 두면 이 횟수를 숫자로 확인할 수 있다. 객체를 useMemo로 감싸거나 컴포넌트 바깥 상수로 빼면 참조가 유지되어 리렌더링이 막힌다. 이 노트에서는 인라인 객체 props의 참조 문제를 console.count 실험으로 확인하고, 해결 방법과 주의점을 정리해 봤다.
#React #React.memo #useMemo #리렌더링 #성능최적화
'React' 카테고리의 다른 글
| useReducer에서 정의되지 않은 액션을 dispatch했을 때 default 분기와 상태 유지 확인 (0) | 2026.10.29 |
|---|---|
| React 상태 복사 후 중첩 객체 갱신 시 형제 필드가 유지되는지 console.log로 확인하기 (0) | 2026.10.29 |
| React createContext로 테마 값 전달하고 3단계 아래 자식에서 useContext로 읽기 (0) | 2026.10.28 |
| React 커스텀 훅 useCounter로 컴포넌트별 독립 상태 확인하기 (0) | 2026.10.27 |
| React useRef의 current 값을 바꿔도 렌더링이 일어나지 않는지 확인하기 (0) | 2026.10.26 |