
컴포넌트를 순수하게 유지하기
React는 내가 작성하는 모든 컴포넌트를 순수 함수라고 가정한다.
즉, 같은 입력이 주어지면 컴포넌트는 언제나 같은 JSX를 반환해야 한다는 전제를 가지고 동작한다.
이 개념을 이해하는 것이 React 렌더링 방식과 성능 최적화를 이해하는 데 중요한 출발점이었다.
순수성: 수식으로서의 컴포넌트
function Recipe({ drinkers }) {
return (
<ol>
<li>Boil {drinkers} cups of water.</li>
<li>
Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.
</li>
<li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
</ol>
);
}
이 컴포넌트는 drinkers라는 입력값(props)에만 의존해서 JSX를 반환한다.
같은 drinkers가 주어지면 언제 렌더링되든 결과는 항상 같았다.
- 외부 변수를 변경하지 않았다
- props만 사용해 결과를 계산했다
- 수식처럼 예측 가능했다 ✅
이런 컴포넌트를 순수한 컴포넌트라고 할 수 있었다.
사이드 이펙트: 의도하지 않은 결과
let guest = 0;
function Cup() {
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
이 코드는 컴포넌트 외부에 있는 변수를 변경하고 있었다.
렌더링이 언제, 몇 번 발생하느냐에 따라 결과가 달라지기 때문에 예측 불가능한 컴포넌트가 된다.
- 렌더링 순서에 의존한다
- 동일한 입력에도 결과가 달라진다
- React의 렌더링 가정이 깨진다 ❌
props를 사용해 순수하게 만들기
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
guest 값을 props로 전달하도록 수정하면,
이 컴포넌트는 오직 입력값에만 의존해 JSX를 반환하게 된다.
이제 이 컴포넌트는:
- 외부 상태를 변경하지 않는다
- 같은 입력 → 같은 출력이 보장된다
- 순수한 컴포넌트가 된다 ✨
사이드 이펙트는 어디에 두어야 할까?
화면 업데이트, 애니메이션 시작, 데이터 변경과 같은 작업들은 사이드 이펙트에 해당한다.
이런 작업들은 보통 다음 위치에 두는 것이 적절했다.
- ✅ 이벤트 핸들러
(렌더링 중에는 실행되지 않기 때문에 순수할 필요가 없다)
<button onClick={handleClick}>Click</button>
- ⚠️ useEffect
다른 선택지가 없을 때만 사용하는 최후의 수단
렌더링 로직과 사이드 이펙트를 분리하는 것이 React 설계 철학에 맞았다.
React는 왜 순수성을 신경 쓸까?
React가 순수성을 전제로 설계된 이유는 명확했다.
- 입력이 바뀌지 않은 컴포넌트는 렌더링을 건너뛸 수 있다
- 렌더링 도중 데이터가 바뀌면, 무의미한 작업을 중단하고 다시 시작할 수 있다
- 동시성 렌더링에서도 안전하다 ⚡
즉, 순수성은 성능 최적화와 안정성을 가능하게 한다.
🧩 챌린지 2 of 3 : 망가진 프로필 고치기
두 개의 Profile 컴포넌트를 나란히 렌더링했을 때,
한쪽을 조작하면 다른 쪽까지 영향을 받는 버그가 발생했다.
문제 코드
import Panel from './Panel.js';
import { getImageUrl } from './utils.js';
let currentPerson;
export default function Profile({ person }) {
currentPerson = person;
return (
<Panel>
<Header />
<Avatar />
</Panel>
)
}
function Header() {
return <h1>{currentPerson.name}</h1>;
}
function Avatar() {
return (
<img
className="avatar"
src={getImageUrl(currentPerson)}
alt={currentPerson.name}
width={50}
height={50}
/>
);
}
문제의 원인은 currentPerson이 전역 변수라는 점이었다.
고친 코드
import Panel from './Panel.js';
import { getImageUrl } from './utils.js';
export default function Profile({ person }) {
return (
<Panel>
<Header person={person} />
<Avatar person={person} />
</Panel>
)
}
function Header({ person }) {
return <h1>{person.name}</h1>;
}
function Avatar({ person }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={50}
height={50}
/>
);
}
각 컴포넌트가 필요한 데이터를 props로 명시적으로 전달하도록 수정했다.
왜 이게 문제였을까?
전역 변수의 문제점 ❌
- React는 전역 변수를 추적하지 않는다
- 렌더링 기준이 흔들린다
- 동시 렌더링에서 안전하지 않다
currentPerson = person;
이 코드는 React 렌더링 시스템 밖에서 실행된다.
props는 뭐가 다른가?
<Header person={person} />
<Avatar person={person} />
이 코드는 React에게 이렇게 알려준다.
“이 컴포넌트는 이 데이터에 의존한다”
그래서 React는:
- 언제 다시 렌더링해야 하는지 알고
- 렌더링 시점의 값을 고정하며
- 트리 전체를 일관된 기준으로 렌더링한다
비유하자면 👇
- ❌ 전역 변수
→ 공용 화이트보드 (누가 언제 바꿀지 모름) - ✅ props
→ 각자 받은 출력물 (남이 못 건드림)
첫 번째 코드는 우연히 동작하는 코드였고,
두 번째 코드는 의도적으로 안전한 코드였다.
✍️ 요약
- 컴포넌트는 순수해야 한다
- 렌더링 전에 존재했던 값을 변경하지 않는다
- 같은 입력이면 항상 같은 JSX를 반환한다
- 렌더링 순서에 의존하면 안 된다
- props, state, context는 읽기 전용 입력값으로 취급해야 한다
- 변경이 필요하다면 이벤트 핸들러에서 처리한다
- useEffect는 정말 필요할 때만 사용한다
순수 함수를 작성하는 연습은 조금 까다롭지만,
React 패러다임의 핵심이라는 점을 체감할 수 있었다.