
리액트 세계에서 순수함수는 뗄래야 뗄 수가 없다. 항상 같은 상황에서 같은 반응을 보여준다는 확신은 곧 디버깅과 유지 보수성을 이끈다. 이런 순수함수의 특성이 리액트의 핵심 철학이 된 이유는 무엇일까? 복잡한 UI 개발 환경에서 순수함수는 어떻게 빛을 발할까?
"(👨🏻🏫 : 리액트 공식문서 내에서도 항상 순수성에 대한 중요성을 강조한답니다. 그 이유에 대해서 같이 알아볼까요?)"
리액트 공식 문서에서는 다음과 같이 말한다:
“Keeping Components Pure”

출처: React 공식 문서 - 컴포넌트 순수성 유지하기
리액트에서 정의하는 순수성은 전통적인 함수형 프로그래밍에서의 순수 함수 본질은 같으나 세부적인 개념은 약간 다릅니다. 리액트에서는 컴포넌트가 다음 조건을 만족하면 순수하다고 본다:
주로 참고한 공식 문서: https://react.dev/learn/keeping-components-pure
리액트 컴포넌트가 순수함수처럼 동작한다면, 같은 props와 state에 대해 항상 같은 UI를 렌더링한다. 이는 UI의 예측 가능성을 크게 높인다.
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>
);
}
export default function App() {
return (
<section>
<h1>Spiced Chai Recipe</h1>
<h2>For two</h2>
<Recipe drinkers={2} />
<h2>For a gathering</h2>
<Recipe drinkers={4} />
</section>
);
}
drinkers에 따라 Input이 같다면 출력되는 Ouput인 컴포넌트가 동일하다.
순수 컴포넌트는 외부 상태에 의존하지 않기 때문에 테스트하기 매우 쉽다. 특정 props를 전달하고 예상되는 출력을 확인하는 것만으로 충분하다.
컴포넌트가 순수하다면, 버그가 발생했을 때 입력(props와 state)만 확인하면 된다. 외부 요인을 고려할 필요가 없어 디버깅 과정이 크게 단순화된다.
"(👨🏻🏫 : 디버깅이 쉬워진다는 것만으로도 순수함수는 사랑받을 자격이 있답니다! 밤새 버그 찾느라 고생해보신 분들은 공감하실 거예요. 😅)"
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}
이를 랜더링 하면 다음과 같이 나온다. 같은<Cup/>이라는 컴포넌트를 랜더링해도, 컴포넌트는 다르게 나온다.
"(👨🏻🏫 : 변수도 매번 바뀌니까 이게 순수함수인가? 라는 헷갈린 점들이 조금은 해소 되셨나요? 예측 가능한 컴포넌트와 예측 불가능한 컴포넌트에 대해서는 동일한 prop 과 state를 넣었는데 동일한 Output이 나오나? 로 확인해볼 수 있습니다. )"
리액트의 렌더링 프로세스는 크게 렌더 단계와 커밋 단계로 나뉜다. 렌더 단계에서 리액트는 컴포넌트를 순수 함수처럼 취급하며, 이전 렌더링과 결과를 비교한다. 이 때, Strict Mode 를 설정해두었다면, 리액트의 컴포넌트들이 순수함수임이 보장되어, 이 과정이 매끄럽게 진행된다.
컴포넌트가 순수하다면, 리액트는 불필요한 렌더링을 건너뛸 수 있다. 같은 입력에 대해 항상 같은 출력이 나온다는 것을 알기 때문에, 입력이 변경되지 않았다면 이전 결과를 재사용할 수 있다.
React.memo를 사용한 최적화 예시는 다음과 같습니다:
// 데이터 테이블의 행 컴포넌트 최적화
const DataRow = React.memo(
function DataRow({ data }) {
console.log("Row rendering");
return <tr><td>{data.id}</td><td>{data.value}</td></tr>;
});
// 사용자 목록 컴포넌트 최적화
const UserList = React.memo(
function UserList({ users, filterCriteria }) {
const filteredAndSortedUsers = React.useMemo(() => {
const filteredUsers = users.filter(user => user.age > filterCriteria.minAge);
return filteredUsers.sort((a, b) => a.age - b.age);
}, [users, filterCriteria]);
return (
<div className="user-list">
{filteredAndSortedUsers.map(user => (
<div key={user.id}>
<h4>{user.name}</h4>
<p>Age: {user.age}</p>
</div>
))}
</div>
);
});
// Todo 리스트 컴포넌트 최적화
const Todo = React.memo(function Todo({ list }) {
console.log("Todo component rendered");
return (
<ul>
{list.map((item) => (
<TodoItem key={item.id} item={item} />
))}
</ul>
);
});
이러한 예시들은 React.memo를 사용하여 부모 컴포넌트가 리렌더링될 때 props가 변경되지 않은 자식 컴포넌트의 불필요한 리렌더링을 방지합니다. 특히 리스트 렌더링, 데이터 테이블, 필터링된 목록과 같이 데이터를 표시하는 컴포넌트에서 유용합니다.
리액트의 엄격 모드(Strict Mode)는 컴포넌트의 순수성을 검증하는 데 도움을 준다. 개발 모드에서 컴포넌트를 두 번 렌더링하여 부수 효과를 찾아내는 방식이다.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
"(👨🏻🏫 : 엄격 모드는 마치 엄한 부모처럼 여러분의 컴포넌트가 순수한지 철저히 검사한답니다. 덕분에 더 좋은 코드를 작성하게 되죠! 또한
React.memo는 오해할 수 있어서 말씀드리지만, 순수 컴포넌트를 정의하는 것이 아니라, 이미 순수한 컴포넌트의 성능을 최적화하는 데 사용됩니다.memo는 컴포넌트의 props가 변경되지 않았다면 리렌더링을 건너뛰게 해주는 고차 컴포넌트(HOC)입니다. 순수 컴포넌트는memo사용 여부와 관계없이, 동일한 입력(props)에 대해 항상 동일한 출력(JSX)을 반환하고 부수 효과가 없는 컴포넌트를 의미합니다.)"
순수 컴포넌트는 상태 변화가 예측 가능하게 이루어진다. 이는 복잡한 애플리케이션에서도 상태 관리를 용이하게 만든다.
리액트의 동시성 모드(Concurrent Mode)는 순수 컴포넌트를 기반으로 한다. 컴포넌트가 순수하다면, 리액트는 동시성 모드를 사용할 수 있는데, 이는 리액트가 렌더링 작업을 중단, 재개, 심지어 폐기할 수 있게 해주는 기능입니다. 이는 브라우저의 메인 스레드를 차단하지 않고 백그라운드에서 컴포넌트 트리의 여러 버전을 준비할 수 있게 해줍니다.
순수 컴포넌트는 부수 효과가 없기 때문에 예상치 못한 버그가 발생할 가능성이 크게 줄어든다. 이는 전체 애플리케이션의 안정성을 높인다.
// 비순수 컴포넌트 예시 (피해야 함)
function BadComponent() {
// 🚫 렌더링 중 직접 DOM 조작
document.title = 'Updated Page';
return Hello World;
}
// 순수한 접근 방식
function GoodComponent() {
// ✅ 부수 효과를 useEffect로 분리 (다 다음 글에서 다뤄볼게요)
React.useEffect(() => {
document.title = 'Updated Page';
}, []);
return Hello World;
}
"(👨🏻🏫 : 순수 컴포넌트는 마치 든든한 팀원과 같아요. 자기 일만 책임지고, 다른 사람의 일을 방해하지 않죠. 그런 팀원이 많을수록 프로젝트는 성공하기 마련이랍니다! 다음에는
함수형 컴포넌트가 순수성과 왜 더 가까운가?에 대해서 배워볼게요)"
🙇🏻 글 내에 틀린 점, 오탈자, 비판, 공감 등 모두 적어주셔도 됩니다. 감사합니다..! 🙇🏻