
목차
React를 개발하다 보면 이런 경험 한 번쯤 해보시지 않으셨나요?
버튼 하나 눌렀을 뿐인데 화면에 있는 컴포넌트가 전부 다시 렌더링 된다?! 🤯
사실 화면에 눈에 띄는 변화가 없어도, 부모가 리렌더링되면 자식도 덩달아 리렌더링되는 경우가 많아요. 이럴 때 성능을 지켜주는 것이 바로 순수 컴포넌트(Pure Component) 예요. 이름은 단순하지만 원리를 제대로 이해하고 쓰면 앱 퍼포먼스를 크게 개선할 수 있어요.
순수 컴포넌트란?
순수 컴포넌트(Pure Component)는 동일한 state
와 props
가 주어졌을 때 항상 동일한 UI를 렌더링하는 컴포넌트예요.
state
나props
가 바뀌면 → 리렌더링state
나props
가 그대로면 → 그냥 재활용
즉, "입력이 변하지 않으면 다시 그리지 않는 컴포넌트" 에요.
// UserProfile.tsx
// 순수 컴포넌트 ✅
import { memo } from 'react';
interface UserPropfileProps {
name: string;
age: number;
}
const UserProfile = memo(
({ name, age }: UserPropfileProps) => {
return (
<div>
<p>이름: {name}</p>
<p>나이: {age}</p>
</div>
);
}
);
어떻게 이게 가능할까?
React의 순수 컴포넌트는 이전 렌더의 state
와 props
를 현재 값과 얕게 비교해요. 얕은 비교는 1단계만 비교한다는 뜻이에요.
- 원시 타입(문자열/숫자/불리언): 값 자체 비교 → 값이 같으면 동일하다고 간주
- 참조 타입(객체/배열/함수): “내용”이 아니라 참조(주소)만 비교 → 참조가 같아야 동일
'Park' === 'Park' // true
32 === 32 // true
const userA = {
name: "Jeonghwan"
};
const userB = {
name: "Jeonghwan"
};
userA === userB // false (내용 같아도 서로 다른 객체 참조)
원시 타입
props
는 얕은 비교로 충분히 최적화되고, 객체/배열/함수props
는 참조가 바뀌면 변한 것으로 간주돼요.
코드 예시
Before: 불필요한 리렌더링 발생
// UserProfile.tsx
// 순수 컴포넌트 ❌
import { memo } from 'react';
interface UserPropfileProps {
name: string;
age: number;
}
export const UserProfile = ({ name, age }: UserPropfileProps) => {
return (
<div>
<p>이름: {name}</p>
<p>나이: {age}</p>
</div>
);
)
// MyPage.tsx
import { useState } from 'react';
import { UserPropfile } from './UserProfile';
function MyPage() {
const [count, setCount] = useState(0);
return (
<div>
<button type="button" onClick={() => setCount((prev) => prev + 1)}>+</button>
<UserProfile name="Jeonghwan" age={32} /> {/* 순수 컴포넌트 ❌ */}
</div>
);
}
count
만 변해도UserProfile
이 계속 리렌더링 돼요.
After: React.memo
로 최적화하여 순수 컴포넌트로 선언
// UserProfile.tsx
// 순수 컴포넌트 ✅
import { memo } from 'react';
interface UserPropfileProps {
name: string;
age: number;
}
const UserProfile = memo(
({ name, age }: UserPropfileProps) => {
return (
<div>
<p>이름: {name}</p>
<p>나이: {age}</p>
</div>
);
}
);
// MyPage.tsx
import { useState } from 'react';
import { UserPropfile } from './UserProfile';
function MyPage() {
const [count, setCount] = useState(0);
return (
<div>
<button type="button" onClick={() => setCount((prev) => prev + 1)}>+</button>
<UserProfile name="Jeonghwan" age={32} /> {/* 순수 컴포넌트 ✅ */}
</div>
);
}
name
,age
가 원시 타입이므로 값이 같으면count
가 변하더라도 리렌더링을 건너뛰어요.
참조 타입 props를 넘기면 의미가 없을까?
다음과 같이 원시 타입이 아닌 참조 타입 props
를 순수 컴포넌트에 넘기면 의미가 없을까요?
// UserProfile.tsx
// 순수 컴포넌트 ✅
import { memo } from 'react';
interface UserPropfileProps {
hobbies: string[];
}
const UserProfile = memo(
({ name, age }: UserPropfileProps) => {
return <div>{hobbies.join(', ')}</div>;
}
);
무조건 의미 없는 것은 아니고, “어떻게 전달하느냐” 에 달려 있어요.
매 렌더마다 새 참조를 전달하는 경우(최적화 실패❌)
// MyPage.tsx
import { useState } from 'react';
import { UserPropfile } from './UserProfile';
function MyPage() {
const [count, setCount] = useState(0);
const hobbies = ['축구', '독서'];
return (
<div>
<button type="button" onClick={() => setCount((prev) => prev + 1)}>+</button>
<UserProfile hobbies={hobbies} /> {/* 순수 컴포넌트 ✅ */}
</div>
);
}
hobbies
배열은 렌더 때마다 새 배열이라 참조가 매번 달라져요. 결국React.memo
가 “변경됨”으로 판단하여 리렌더링 발생해요.물론
hobbies
와 같이 변하지 않는 상수 데이터는 컴포넌트 함수 바깥에 선언하는 것이 가장 깔끔해요. 이렇게 하면 매 렌더마다 새로 생성되는 문제를 방지할 수 있어요. 다만 위 코드는 순수 컴포넌트 활용을 설명하기 위한 예시일 뿐이에요.
참조 고정(안정화)해 전달 (최적화 성공✅)
// MyPage.tsx
import { useState, useMemo } from 'react';
import { UserPropfile } from './UserProfile';
function MyPage() {
const [count, setCount] = useState(0);
const hobbies = useMemo(() => ['축구', '독서'], []);
return (
<div>
<button type="button" onClick={() => setCount((prev) => prev + 1)}>+</button>
<UserProfile hobbies={hobbies} /> {/* 순수 컴포넌트 ✅ */}
</div>
);
}
useMemo
와useCallback
을 활용하면 참조를 안정화시킬 수 있어서React.memo
최적화가 효과적으로 작동해요.
순수 컴포넌트는 항상 좋은 것일까?
순수 컴포넌트가 항상 좋은 건 아니에요. React.memo
나 PureComponent
는 props가 바뀌지 않으면 리렌더링을 막아주지만, 그 자체로도 이전 props
와 새로운 props
를 비교하는 비용이 들어가요. 그래서 아주 가벼운 컴포넌트나, 변화가 잦은 컴포넌트라면 굳이 쓰지 않는 게 더 효율적일 때도 있어요.
특히 props
에 배열이나 객체, 함수를 매번 새로 만들어 전달하면 참조가 계속 바뀌어서 결국 매번 리렌더링이 발생하기 때문에 memo
를 써도 큰 의미가 없어요. 이를 해결하려면 useMemo
와 useCallback
으로 참조를 고정해줘야 하는데, 이마저도 과하면 코드만 복잡해질 수 있어요.
따라서 순수 컴포넌트는 무조건 적용하는 게 아니라, 렌더링 비용이 크거나 반복적으로 사용되는데 실제 변화는 드문 컴포넌트에 한정해서 쓰는 게 좋아요. 한마디로, “언제나”가 아니라 “알맞을 때만” 쓰는 게 성능 최적화의 정석이에요.
언제 쓰면 좋은가? (체크리스트)
- 렌더 비용이 크거나, 자주 쓰이는 리스트 아이템/테이블 행 ✅
props
가 잘 변하지 않는 정적 UI (카드, 아바타, 배지 등) ✅- 부모 컴포넌트가 자주 리렌더링되지만, 자식 컴포넌트는 값이 그대로인 경우 ✅
마무리
순수 컴포넌트는 React에서 성능 최적화를 시작할 때 가장 먼저 떠올릴 수 있는 방법이에요. 하지만 무조건 순수 컴포넌트로 만드는 것이 좋은 게 아니라, 렌더링 비용이 크고 실제로는 자주 변하지 않는 컴포넌트에 선택적으로 적용해야 진짜 효과를 볼 수 있어요. 결국 핵심은 "언제 적용하는 것이 효과적인지" 판단할 수 있는 안목이라고 생각해요.
이 글을 통해 순수 컴포넌트의 동작 원리와 한계를 이해하고, 여러분의 프로젝트에 상황에 맞게 최적화를 적용해 보세요.