리액트는 폼(Form) 요소의 데이터를 관리하는 방식에 따라 제어 컴포넌트와 비제어 컴포넌트로 구분한다.
폼 요소의 값을 리액트의 state가 관리하는 방식이다.
state가 단일 데이터 출처가 되며 폼 입력 요소의 value 속성은 state에 의해 결정된다. 사용자의 입력이 발생하면 onChange 이벤트를 통해 state를 업데이트한다.
import { useState } from 'react';
export default function Controlled() {
const [name, setName] = useState('');
return (
<label>
이름
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
);
}
사용자가 input에 값을 입력하면 onChange 이벤트가 발생하고 setName(e.target.value)를 통해 state가 업데이트된다. 이후 state 변경에 따른 컴포넌트 리렌더링이 발생하며 input의 value가 새로운 state 값으로 갱신되는 방식이다.
1) 실시간 유효성 검사
if(name.length < 2) {
// 경고 메시지 표시
}
2) 입력값 기반 UI 변경
{/* 입력값이 없으면 버튼 비활성화 */}
<button disabled={!name}>제출</button>
3) 입력 형식 강제
<input
type="text"
value={name}
onChange={(e) =>
setName(
// 숫자만 입력 허용
e.target.value.replace(/[^0-9]/g, '')
)
}
/>
폼 데이터를 DOM이 직접 관리하는 방식이다.
즉 입력값을 state로 매번 관리하지 않고 필요할 때만 가져오는 방식이다. 리액트에서는 보통 ref를 사용해 DOM 요소에 직접 접근하여 값을 읽는다.
import { useRef } from 'react';
export default function Uncontrolled() {
const inputRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
const value = inputRef.current?.value ?? '';
alert('입력값: ' + value);
}
return (
<form onSubmit={handleSubmit}>
<label>
이름
<input
type="text"
defaultValue="BlueCool"
ref={inputRef}
/>
</label>
<button type="submit">등록</button>
</form>
);
}
폼 데이터의 출처가 state가 아닌 DOM이 되며 value 대신 defaultValue를 사용한다. 필요할때만 ref를 통해 input 요소에 직접 접근하여 값을 가져온다. 입력 시 state 변경이 없기 때문에 리렌더링이 발생하지 않는다.
1) 파일 입력
리액트에서는 일반적으로 제어 컴포넌트 사용이 권장되지만 파일 입력의 경우 브라우저 보안 정책 때문에 value를 직접 제어할 수 없다.
import { useRef } from 'react';
function FileInput() {
const ref = useRef(null);
return (
<div>
<input type="file" ref={ref} />
<button
onClick={() => {
if (ref.current) ref.current.value = '';
}}
>
초기화
</button>
</div>
);
}
