
🔍 Control Props 패턴이란?
이전 글에서 Compound Component 패턴에 대해 알아봤다.
컴파운드 컴포넌트 패턴은 부모와 자식 컴포넌트가 자연스럽게 협력하는 구조를 만들기 위한 패턴이었다.
Control Props 패턴은 컴포넌트가 자체 상태를 가질 수도 있고, 부모가 상태를 제어할 수도 있게 만드는 패턴이다.
input, select, modal, toggle 등 상태를 외부에서 완전히 제어할 수 있게 해야 하는 컴포넌트에서 사용된다.
🧩 예제 코드
이전 글에서 활용한 예제 코드를 수정하여 실습해 보자.
- Toggle.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
type ToggleContextType = {
on: boolean; // 불 켜짐/꺼짐 상태
toggle: () => void; // 상태를 바꾸는 함수
};
// context 공간을 만듦
const ToggleContext = createContext<ToggleContextType | null>(null);
type ToggleProps = {
children: ReactNode;
on?: boolean; // 외부 상태 (control props)
onToggle?: () => void; // 외부 toggle 핸들러
};
// 부모 컴포넌트
// 상태를 만들고 자식 컴포넌트에게 context로 전달
const Toggle = ({ children, on: controlledOn, onToggle }: ToggleProps) => {
const [uncontrolledOn, setUncontrolledOn] = useState(false);
const isControlled = controlledOn !== undefined;
const on = isControlled ? controlledOn : uncontrolledOn;
const toggle = () => {
if (isControlled) {
onToggle?.();
} else {
setUncontrolledOn(prev => !prev);
}
};
// 자식 컴포넌트들이 context 데이터에 접근 가능하도록 함
return <ToggleContext.Provider value={{ on, toggle }}>{children}</ToggleContext.Provider>;
};
// 자식 컴포넌트: On
const ToggleOn = ({ children }: { children: ReactNode }) => {
const context = useContext(ToggleContext); // context 가져옴
if (!context) throw new Error('Toggle.On must be used within <Toggle>');
return context.on ? <>{children}</> : null;
};
Toggle.On = ToggleOn;
// 자식 컴포넌트: Off
const ToggleOff = ({ children }: { children: ReactNode }) => {
const context = useContext(ToggleContext);
if (!context) throw new Error('Toggle.Off must be used within <Toggle>');
return !context.on ? <>{children}</> : null;
};
Toggle.Off = ToggleOff;
// 자식 컴포넌트: Button
const ToggleButton = () => {
const context = useContext(ToggleContext);
if (!context) throw new Error('Toggle.Button must be used within <Toggle>');
return <button onClick={context.toggle}>토글 전환</button>;
};
Toggle.Button = ToggleButton;
export default Toggle;
- ControlledExample.tsx
import { useState } from 'react';
import Toggle from './Toggle';
const ControlledExample = () => {
const [isOn, setIsOn] = useState(false);
return (
<div style={{ padding: 20 }}>
<h2>Control Props 패턴 실험</h2>
<Toggle
on={isOn} // 부모 상태 넘김
onToggle={() => setIsOn(prev => !prev)} // 부모 상태 제어
>
<Toggle.On>🔆 켜짐 (부모 제어)</Toggle.On>
<Toggle.Off>🌙 꺼짐 (부모 제어)</Toggle.Off>
<Toggle.Button />
</Toggle>
</div>
);
};
export default ControlledExample;
🧩 정리
패턴 | 해결하는 문제 | 상태 관리 | UI 구조 |
Compound Component | 부모-자식 컴포넌트 API 깔끔하게 구성 | 부모 내부에서 관리 | 부모와 자식이 Context로 연결 |
Control Props | 상태를 부모가 직접 제어 가능하게 함 | 부모 또는 외부에서 제어 | 상태 제어와 UI 분리 |
Control Props 패턴은 부모가 상태를 넘기면 내부 상태를 무시하고 부모 상태로 동작하고, 부모가 상태를 넘기지 않으면 내부 상태를 사용한다.
🕹️ Controlled vs Uncontrolled
타입 | 상태 제어 | 설명 |
Controlled | 부모(외부)가 상태 제어 | 부모가 value와 onChange를 넘김 |
Uncontrolled | 컴포넌트 내부에서 상태 제어 | 부모가 상태 신경 안 씀 |
▶️ Controlled 특징
<input value={text} onChange={(e) => setText(e.target.value)} />
- 모든 상태를 부모에서 관리 가능하여 상태를 추적할 수 있다
- 값 변경 시 로직 추가가 가능하여 검증, 포맷팅 등이 가능하다
- Redux, Form 관리, validation 등 외부 동기화가 가능하다
- 복잡한 상태 처리에 강하여 대규모 앱에 적합하다
▶️ Uncontrolled 특징
<input defaultValue="hello" />
- 부모가 상태 관리를 안 해도 돼서 사용하기 쉽다
- value, onChange를 넘기지 않아도 돼서 코드가 짧다
- 빠르게 구현 가능하여 작은 앱, 빠른 프로토타입에 유리하다
- 상태가 외부로 퍼지지 않아 부모 리렌더링 영향을 덜 받는다
작은 앱/빠른 개발은 Uncontrolled, 대규모 앱/상태 추적/검증 필요는 Controlled를 쓰면 좋다.
✏️ 결론
Control Props 패턴은 내부적으론 Controlled 방식으로 동작하지만, 사용하는 입장에서는 Uncontrolled인 것처럼 사용할 수 있는 인터페이스를 제공하는 방식인 것 같다.
그래서 실무에서도 이 패턴을 많이 이용하는 것 같다.
초보 사용자도 쉽게 쓰고, 고급 사용자는 완전히 제어하도록 할 수 있기 때문이다.
토글 예제 코드의 경우에는 값(on)이 주어지면 Controlled, 값이 없으면 Uncontrolled를 사용한다.
디자인패턴들을 실습해 보면서 드는 생각이 처음엔 어떤 디자인 패턴인지 모르고 사용하는 경우가 많은 것 같다.
정처기 공부하면서 디자인 패턴 설명 봤을 때는 추상적이라 잘 와닿지 않아서 뭔지 모르고 외웠었는데, 실습 코드랑 연결해서 보니 이해된다.
심지어 내가 개발할 때 사용해 본 방식인데 그게 어떤 디자인 패턴인지 명칭을 모르고 사용한 경우가 많아 충격이었다.
어댑터, 전략, 옵저버, 싱글톤, 퍼싸드 등.. 내가 이미 알고 있던 것들인데... 정처기 책에서 설명을 봤을 때는 그게 그거인 줄 몰랐다.
아는 게 연결되고 명확해지니까 디자인 패턴 공부 재밌다.
'Library & Runtime > React' 카테고리의 다른 글
[React] Compound Component 패턴 + Context 이해하기 (0) | 2025.05.06 |
---|---|
[React] Virtual Scroll(가상 스크롤)로 렌더링 최적화하기 (react-window) (0) | 2025.05.05 |
[React] 계산/렌더링 최적화: useMemo + React.memo 실험 (0) | 2025.05.04 |
[React] 무거운 연산 최적화 : 일반 계산 vs useMemo (0) | 2025.05.03 |