리액트의 Controlled Component와 Uncontrolled Component의 차이점에 대해서 설명해주세요.
Controlled Component는 리액트 상태(state)를 통해 입력 값을 제어하는 컴포넌트를 말합니다. 이 방식에서는 입력 요소의 값(value)을 리액트 상태와 동기화하고, 사용자가 입력을 변경할 때마다 onChange 이벤트 핸들러를 통해 상태를 업데이트합니다. Controlled Component는 값이 리액트의 state로 관리되므로, 입력 시마다 값을 검증하거나, 값을 자유롭게 변경할 수 있으며, 복잡한 폼 로직을 처리하는 데 유용합니다.
Uncontrolled Component는 입력 값을 리액트의 상태로 관리하지 않고, DOM을 통해 입력 값을 제어하는 방식입니다. 즉, 입력 요소의 값은 DOM에서 직접 관리되며, 리액트는 이를 제어하지 않습니다. 이 방식에서는 useRef를 사용해 생성된 참조 객체인 ref를 사용하여 DOM 요소에 직접 접근하여 값을 읽거나 조작합니다. Uncontrolled Component는 리액트 상태 관리에 따른 성능 비용이 없으므로 상대적으로 간단한 폼에서 주로 사용됩니다.
Controlled Component와 Uncontrolled Component는 각각 어떤 상황에서 사용되나요? 🤔
단순한 입력 필드가 포함된 폼에서는 입력 요소의 값을 리액트 상태로 관리할 필요성이 적으므로, Uncontrolled Component를 사용하는 것이 더 간단하고 성능이 좋습니다. 사용자가 제출 버튼을 클릭했을 때만 입력 값을 가져와도 충분한 경우를 예시로 들 수 있습니다.
반면, 값을 입력할 때마다 유효성 검증을 실시간으로 해주어야 하는 경우에는 Controlled Component를 사용해야 합니다.
리액트의 Controlled Component와 Uncontrolled Component
1. 폼 관리의 두 가지 방식
React에서 사용자 입력을 처리하는 폼을 구현할 때, 두 가지 주요 접근 방식이 있습니다: Controlled Components(제어 컴포넌트) 와 Uncontrolled Components(비제어 컴포넌트). 이 두 방식은 폼 데이터를 어떻게 관리하는지에 따라 구분됩니다. 초보자도 쉽게 이해할 수 있도록 각각의 개념과 차이점, 그리고 언제 어떤 방식을 사용해야 하는지 알아보겠습니다.
2. Controlled Component란?
2.1 특징과 작동 방식
Controlled Component는 React의 상태(state)를 "진리의 유일한 원천(Single Source of Truth)"으로 사용하는 방식입니다. 쉽게 말해, 입력 요소(input, textarea, select 등)의 값을 React가 완전히 제어하는 것입니다.
이 방식의 핵심 특징은 다음과 같습니다:
- 모든 폼 데이터는 React의 상태(state)에 저장됩니다.
- 입력 요소의 값(value)은 항상 React 상태와 연결됩니다.
- 사용자가 입력할 때마다 onChange 이벤트 핸들러가 호출되어 상태를 업데이트합니다.
이러한 방식을 "제어(Controlled)"라고 부르는 이유는 React가 입력 요소의 값을 적극적으로 제어하기 때문입니다.
2.2 코드 예시
아래는 간단한 Controlled Component 예시입니다:
import React, { useState } from 'react';
function ControlledForm() {
// React 상태로 입력 값 관리
const [name, setName] = useState('');
const handleChange = (event) => {
setName(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
alert('입력한 이름: ' + name);
};
return (
<form onSubmit={handleSubmit}>
<label>
이름:
<input
type="text"
value={name} // React 상태를 value로 설정
onChange={handleChange} // 입력 변경 시 상태 업데이트
/>
</label>
<button type="submit">제출</button>
</form>
);
}
이 예시에서 input 요소의 value는 항상 React의 name 상태와 동일합니다. 사용자가 입력할 때마다 handleChange 함수가 호출되어 상태를 업데이트하고, 이것이 다시 input의 값으로 반영됩니다.
2.3 장점과 사용 시나리오
Controlled Component의 주요 장점은 다음과 같습니다:
- 실시간 입력 검증: 사용자가 타이핑할 때마다 입력 값을 검증하고 피드백을 제공할 수 있습니다.
- 조건부 제출 버튼 활성화: 폼이 유효할 때만 제출 버튼을 활성화할 수 있습니다.
- 입력 값 형식 지정: 예를 들어, 전화번호에 자동으로 하이픈을 추가하는 등의 형식 지정이 가능합니다.
- 동적 입력 필드: 하나의 입력이 다른 입력에 영향을 미치는 등의 복잡한 폼 로직을 구현할 수 있습니다.
Controlled Component는 다음과 같은 상황에서 특히 유용합니다:
- 실시간 유효성 검사가 필요한 폼
- 다른 UI 요소에 의존하는 입력 필드가 있는 폼
- 동적으로 변하는 복잡한 폼
- 제출 전에 입력 값을 처리해야 하는 경우
3. Uncontrolled Component란?
3.1 특징과 작동 방식
Uncontrolled Component는 폼 데이터를 React 상태가 아닌 DOM 자체에서 관리하는 방식입니다. 이 방식에서는 React가 입력 요소의 값을 직접 제어하지 않으며, 대신 ref를 사용하여 필요할 때 DOM에서 값을 가져옵니다.
이 방식의 핵심 특징은 다음과 같습니다:
- 입력 값은 DOM에 의해 관리됩니다.
- React의 상태(state)를 사용하지 않습니다.
- ref를 사용하여 DOM 노드에 접근하고 값을 읽습니다.
- 폼 제출 시에만 입력 값을 가져오는 경우가 많습니다.
3.2 코드 예시
아래는 간단한 Uncontrolled Component 예시입니다:
import React, { useRef } from 'react';
function UncontrolledForm() {
// ref를 사용하여 DOM 노드에 접근
const nameInputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// 제출 시 ref를 통해 입력 값 가져오기
alert('입력한 이름: ' + nameInputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label>
이름:
<input
type="text"
ref={nameInputRef} // DOM 노드에 대한 참조 설정
defaultValue="" // 초기값 설정 (선택 사항)
/>
</label>
<button type="submit">제출</button>
</form>
);
}
이 예시에서는 React의 상태를 사용하지 않고, useRef 훅을 통해 input 요소에 직접 접근합니다. 사용자가 폼을 제출할 때 nameInputRef.current.value를 통해 현재 입력된 값을 가져옵니다.
3.3 장점과 사용 시나리오
Uncontrolled Component의 주요 장점은 다음과 같습니다:
- 간단한 구현: 적은 코드로 구현할 수 있어 단순한 폼에 적합합니다.
- 성능: 입력할 때마다 렌더링이 발생하지 않아 성능 면에서 효율적일 수 있습니다.
- 기존 코드 통합: 기존의 비-React 코드나 라이브러리와 통합하기 쉽습니다.
- 파일 입력 관리: <input type="file"> 같은 요소는 Uncontrolled 방식으로만 다룰 수 있습니다.
Uncontrolled Component는 다음과 같은 상황에서 주로 사용됩니다:
- 단순한 폼(예: 간단한 로그인 폼)
- 제출 시에만 값을 확인하면 되는 경우
- 파일 업로드와 같이 제어하기 어려운 입력이 포함된 경우
- React 외부 라이브러리와의 통합
4. 비교: 언제 어떤 방식을 사용해야 할까?
Controlled Component와 Uncontrolled Component의 주요 차이점을 비교해보겠습니다:
특성 Controlled Component Uncontrolled Component
데이터 관리 | React 상태(state) | DOM |
코드 복잡성 | 상대적으로 많은 코드 필요 | 적은 코드로 구현 가능 |
실시간 유효성 검사 | 쉽게 구현 가능 | 구현하기 어려움 |
조건부 렌더링 | 상태에 기반하여 쉽게 구현 | 추가 작업 필요 |
초기값 설정 | value prop | defaultValue prop |
성능 | 타이핑마다 리렌더링 발생 가능 | 리렌더링 발생하지 않음 |
사용 사례 | 복잡한 폼, 실시간 검증 필요 | 간단한 폼, 제출 시에만 검증 |
선택 기준
- Controlled Component를 선택해야 할 때: 실시간 유효성 검사, 조건부 제출, 동적 입력 필드 등 복잡한 폼 로직이 필요한 경우
- Uncontrolled Component를 선택해야 할 때: 간단한 폼, 성능이 중요한 경우, 기존 코드와의 통합이 필요한 경우
5. 실전 예제: 회원가입 폼
두 가지 방식의 차이를 더 명확히 이해하기 위해 간단한 회원가입 폼을 두 방식으로 구현해보겠습니다.
5.1 Controlled 방식
import React, { useState } from 'react';
function ControlledSignupForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
// 입력 변경 처리
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
// 실시간 유효성 검사
validateField(name, value);
};
// 특정 필드 유효성 검사
const validateField = (name, value) => {
let tempErrors = { ...errors };
switch (name) {
case 'username':
tempErrors.username = value.length < 3 ? '사용자 이름은 3자 이상이어야 합니다.' : '';
break;
case 'email':
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
tempErrors.email = !emailRegex.test(value) ? '유효한 이메일 주소를 입력하세요.' : '';
break;
case 'password':
tempErrors.password = value.length < 6 ? '비밀번호는 6자 이상이어야 합니다.' : '';
// 비밀번호 확인 필드도 함께 검사
if (formData.confirmPassword) {
tempErrors.confirmPassword =
value !== formData.confirmPassword ? '비밀번호가 일치하지 않습니다.' : '';
}
break;
case 'confirmPassword':
tempErrors.confirmPassword =
value !== formData.password ? '비밀번호가 일치하지 않습니다.' : '';
break;
default:
break;
}
setErrors(tempErrors);
};
// 폼 제출 처리
const handleSubmit = (e) => {
e.preventDefault();
// 모든 필드 유효성 검사
let isValid = true;
let tempErrors = { ...errors };
// 각 필드 검사
Object.keys(formData).forEach(key => {
if (!formData[key]) {
tempErrors[key] = '이 필드는 필수입니다.';
isValid = false;
} else {
validateField(key, formData[key]);
if (tempErrors[key]) isValid = false;
}
});
setErrors(tempErrors);
if (isValid) {
// 유효한 데이터로 회원가입 처리
console.log('회원가입 데이터:', formData);
alert('회원가입이 완료되었습니다!');
}
};
return (
<form onSubmit={handleSubmit}>
<h2>Controlled 회원가입 폼</h2>
<div>
<label>사용자 이름:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <p className="error">{errors.username}</p>}
</div>
<div>
<label>이메일:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <p className="error">{errors.email}</p>}
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <p className="error">{errors.password}</p>}
</div>
<div>
<label>비밀번호 확인:</label>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
/>
{errors.confirmPassword && <p className="error">{errors.confirmPassword}</p>}
</div>
<button type="submit">회원가입</button>
</form>
);
}
이 Controlled 방식의 회원가입 폼은 다음과 같은 장점을 가집니다:
- 사용자가 입력하는 동안 실시간으로 유효성 검사를 수행합니다.
- 비밀번호와, 비밀번호 확인 필드를 비교하는 등의 복잡한 검증이 가능합니다.
- 모든 필드를 하나의 상태 객체로 관리하여 코드 구성이 깔끔합니다.
5.2 Uncontrolled 방식
import React, { useRef } from 'react';
function UncontrolledSignupForm() {
// 각 입력 필드에 대한 ref 생성
const usernameRef = useRef(null);
const emailRef = useRef(null);
const passwordRef = useRef(null);
const confirmPasswordRef = useRef(null);
// 에러 메시지 표시용 ref
const usernameErrorRef = useRef(null);
const emailErrorRef = useRef(null);
const passwordErrorRef = useRef(null);
const confirmPasswordErrorRef = useRef(null);
// 폼 제출 처리
const handleSubmit = (e) => {
e.preventDefault();
// 폼 데이터 수집
const formData = {
username: usernameRef.current.value,
email: emailRef.current.value,
password: passwordRef.current.value,
confirmPassword: confirmPasswordRef.current.value
};
// 유효성 검사
let isValid = true;
// 사용자 이름 검사
if (!formData.username) {
usernameErrorRef.current.textContent = '사용자 이름은 필수입니다.';
isValid = false;
} else if (formData.username.length < 3) {
usernameErrorRef.current.textContent = '사용자 이름은 3자 이상이어야 합니다.';
isValid = false;
} else {
usernameErrorRef.current.textContent = '';
}
// 이메일 검사
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!formData.email) {
emailErrorRef.current.textContent = '이메일은 필수입니다.';
isValid = false;
} else if (!emailRegex.test(formData.email)) {
emailErrorRef.current.textContent = '유효한 이메일 주소를 입력하세요.';
isValid = false;
} else {
emailErrorRef.current.textContent = '';
}
// 비밀번호 검사
if (!formData.password) {
passwordErrorRef.current.textContent = '비밀번호는 필수입니다.';
isValid = false;
} else if (formData.password.length < 6) {
passwordErrorRef.current.textContent = '비밀번호는 6자 이상이어야 합니다.';
isValid = false;
} else {
passwordErrorRef.current.textContent = '';
}
// 비밀번호 확인 검사
if (!formData.confirmPassword) {
confirmPasswordErrorRef.current.textContent = '비밀번호 확인은 필수입니다.';
isValid = false;
} else if (formData.password !== formData.confirmPassword) {
confirmPasswordErrorRef.current.textContent = '비밀번호가 일치하지 않습니다.';
isValid = false;
} else {
confirmPasswordErrorRef.current.textContent = '';
}
if (isValid) {
// 유효한 데이터로 회원가입 처리
console.log('회원가입 데이터:', formData);
alert('회원가입이 완료되었습니다!');
}
};
return (
<form onSubmit={handleSubmit}>
<h2>Uncontrolled 회원가입 폼</h2>
<div>
<label>사용자 이름:</label>
<input
type="text"
ref={usernameRef}
defaultValue=""
/>
<p className="error" ref={usernameErrorRef}></p>
</div>
<div>
<label>이메일:</label>
<input
type="email"
ref={emailRef}
defaultValue=""
/>
<p className="error" ref={emailErrorRef}></p>
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
ref={passwordRef}
defaultValue=""
/>
<p className="error" ref={passwordErrorRef}></p>
</div>
<div>
<label>비밀번호 확인:</label>
<input
type="password"
ref={confirmPasswordRef}
defaultValue=""
/>
<p className="error" ref={confirmPasswordErrorRef}></p>
</div>
<button type="submit">회원가입</button>
</form>
);
}
이 Uncontrolled 방식의 회원가입 폼은 다음과 같은 특징을 가집니다:
- React 상태를 사용하지 않고 DOM에서 직접 값을 가져옵니다.
- 폼 제출 시에만 유효성 검사를 수행합니다.
- 에러 메시지도 React 상태가 아닌 DOM을 통해 관리합니다.
- Controlled 방식에 비해 코드가 더 복잡해질 수 있지만, 렌더링 성능은 더 좋을 수 있습니다.
6. 결론
Controlled Component와 Uncontrolled Component는 각각 장단점이 있으며, 상황에 따라 적절한 방식을 선택하는 것이 중요합니다.
Controlled Component는:
- 실시간 피드백과 유효성 검사가 필요한 복잡한 폼에 적합합니다.
- React의 데이터 흐름 철학에 더 잘 맞습니다.
- 폼 상태를 완전히 제어할 수 있어 복잡한 UI 상호작용을 구현하기 쉽습니다.
Uncontrolled Component는:
- 간단한 폼, 특히 제출 시에만 값을 확인하면 되는 경우에 적합합니다.
- 코드가 더 간결할 수 있으며, 성능 면에서 이점이 있을 수 있습니다.
- 파일 입력과 같은 특정 타입의 입력을 처리할 때 필요합니다.
어떤 방식을 선택하든, 일관성을 유지하는 것이 중요합니다. 하나의 폼 내에서 두 방식을 혼합하여 사용하면 코드가 혼란스러워질 수 있으므로, 가능하면 한 가지 방식을 선택하여 일관되게 사용하는 것이 좋습니다.
리액트에서 폼을 다루는 것은 웹 애플리케이션 개발에서 매우 중요한 부분입니다. Controlled Component와 Uncontrolled Component의 개념을 이해하고 적절히 활용함으로써, 더 효율적이고 유지보수하기 쉬운 폼을 구현할 수 있습니다.
'1일 1CS(Computer Science)' 카테고리의 다른 글
인덱스와 PK(Primary Key)의 관계 (0) | 2025.04.03 |
---|---|
데이터베이스 인덱스에 대해서 설명해주세요. (0) | 2025.04.03 |
리액트의 Props와 State (0) | 2025.04.03 |
이벤트 루프에 대해서 설명해주세요. (0) | 2025.03.28 |
TanStack Query: staleTime과 gcTime (0) | 2025.03.27 |