React로 개발을 하다 보면 사용자의 입력을 받아야 하는 상황이 자주 생긴다. 이메일 입력, 게시글 작성, 설문 응답처럼 폼을 사용하는 기능을 구현할 때가 많다. React는 선언적인 특성을 가지고 있어서 기존 HTML 폼을 다루는 방식과는 다른 접근이 필요하다.
폼을 다루는 방식은 크게 Uncontrolled Components와 Controlled Components로 나눌 수 있다.
Uncontrolled component
Uncontrolled Components는 React가 직접 상태를 제어하지 않는 폼 요소를 의미한다. 일반적인 HTML 폼처럼 DOM이 직접 폼 데이터를 관리하며, React는 필요할 때만 ref를 통해 값을 읽어오는 방식이다.
기본적인 Uncontrolled Component는 아래과 같이 구현할 수 있다.
const UncontrolledForm = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (inputRef.current) {
console.log('제출된 값:', inputRef.current.value);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
defaultValue="초기값"
/>
<button type="submit">Submit</button>
</form>
);
};
이 코드에서는 input 요소의 값을 React state로 관리하지 않는다. 대신 DOM이 값을 직접 관리해 defaultValue를 통해 초기값만 설정할 수 있다. 폼이 제출될 때는 useRef를 통해 생성한 ref 객체로 DOM 노드에 직접 접근해서 현재 값을 읽어온다.
Uncontrolled Components는 간단한 폼을 구현할 때는 유용하게 쓸 수 있다. 매 입력마다 상태를 업데이트하고 리렌더링할 필요가 없어서 성능상 이점이 있다. 하지만 실시간으로 입력값을 검증하거나 입력값에 따라 UI를 동적으로 변경하는 등의 경우에는 React가 폼의 상태를 완전히 제어하는 Controlled Components가 더 적합한 선택이 될 수 있다.
Controlled component
먼저 간단한 Controlled Component로 시작해보자. 사용자의 입력을 어떻게 다루는지 단계별로 살펴보자.
const ControlledForm = () => {
const [value, setValue] = useState('');
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
return (
<form>
<input type="text" onChange={handleChange} />
<button>Submit</button>
</form>
);
};
이 컴포넌트를 브라우저에서 실행하고 입력 필드에 "React"를 입력하면 아래와 같은 결과를 볼 수 있다.
R
Re
Rea
Reac
React
handleChange 함수는 입력값이 변경될 때마다 실행된다. 사용자가 한 글자를 입력할 때마다 함수가 호출되면서 현재까지 입력된 값을 보여주는 것이다.
이제 Form 제출도 처리해보자. 사용자가 Submit 버튼을 클릭했을 때 입력값을 사용할 수 있게 만들어야 한다.
Form 제출 처리
이제 사용자가 입력한 값을 저장하고 폼을 제출할 때 활용하는 방법을 알아보자. 먼저 입력값을 저장할 state를 추가한다.
const ControlledForm = () => {
const [value, setValue] = useState<string>('');
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleSubmit = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log('제출된 값:', value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
};
폼 제출을 처리하는 것은 입력값 변경을 감지하는 것과 비슷하다. 둘 다 브라우저에서 발생하는 이벤트를 처리하는 방식이기 때문이다. e.preventDefault()
를 호출해서 폼 제출 시 브라우저의 기본 동작인 페이지 새로고침을 방지하고, 우리가 원하는 동작을 수행할 수 있다.
여러 입력 필드 다루기
지금까지는 하나의 입력 필드만 다뤘다. 하지만 실제로는 여러 개의 입력 필드를 다뤄야 하는 경우가 많다. 조금 과장되었긴 하지만, 차이를 쉽게 확인할 수 있게 사용자의 다양한 정보를 입력받는 폼을 만들어보자.
const UncontrolledForm = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [address, setAddress] = useState('');
const [city, setCity] = useState('');
const [state, setState] = useState('');
const [zipCode, setZipCode] = useState('');
const handleFirstNameChange = (e: ChangeEvent<HTMLInputElement>) => {
setFirstName(e.target.value);
};
const handleLastNameChange = (e: ChangeEvent<HTMLInputElement>) => {
setLastName(e.target.value);
};
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
const handlePhoneChange = (e: ChangeEvent<HTMLInputElement>) => {
setPhone(e.target.value);
};
const handleAddressChange = (e: ChangeEvent<HTMLInputElement>) => {
setAddress(e.target.value);
};
const handleCityChange = (e: ChangeEvent<HTMLInputElement>) => {
setCity(e.target.value);
};
const handleStateChange = (e: ChangeEvent<HTMLInputElement>) => {
setState(e.target.value);
};
const handleZipCodeChange = (e: ChangeEvent<HTMLInputElement>) => {
setZipCode(e.target.value);
};
const handleSubmit = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log('제출된 정보:', {
firstName,
lastName,
email,
phone,
address,
city,
state,
zipCode
});
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleFirstNameChange} placeholder="이름" />
<input type="text" onChange={handleLastNameChange} placeholder="성" />
<input type="email" onChange={handleEmailChange} placeholder="이메일" />
<input type="tel" onChange={handlePhoneChange} placeholder="전화번호" />
<input type="text" onChange={handleAddressChange} placeholder="주소" />
<input type="text" onChange={handleCityChange} placeholder="도시" />
<input type="text" onChange={handleStateChange} placeholder="시/도" />
<input type="text" onChange={handleZipCodeChange} placeholder="우편번호" />
<button type="submit">Submit</button>
</form>
);
};
이렇게 각 필드마다 state와 핸들러를 따로 만들 수 있다. 하지만 코드로 볼 수 있다시피 입력 필드가 추가될수록 코드가 복잡해진다.
아래처럼 하나의 객체로 여러 필드의 값을 관리하는 방법으로 개선할 수 있다.
const UncontrolledForm = () => {
const [values, setValues] = useState({
firstName: '',
lastName: '',
email: '',
phone: '',
address: '',
city: '',
state: '',
zipCode: ''
});
const handleChange = ({ target: { name, value } }: ChangeEvent<HTMLInputElement>) => {
setValues({
...values,
[name]: value
});
};
const handleSubmit = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log('제출된 정보:', values);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="firstName"
onChange={handleChange}
placeholder="이름"
/>
<input
type="text"
name="lastName"
onChange={handleChange}
placeholder="성"
/>
<input
type="email"
name="email"
onChange={handleChange}
placeholder="이메일"
/>
<input
type="tel"
name="phone"
onChange={handleChange}
placeholder="전화번호"
/>
<input
type="text"
name="address"
onChange={handleChange}
placeholder="주소"
/>
<input
type="text"
name="city"
onChange={handleChange}
placeholder="도시"
/>
<input
type="text"
name="state"
onChange={handleChange}
placeholder="시/도"
/>
<input
type="text"
name="zipCode"
onChange={handleChange}
placeholder="우편번호"
/>
<button type="submit">Submit</button>
</form>
);
};
이 방식에서는 각 입력 필드에 name
속성을 주고, 이것을 객체의 키로 사용한다. handleChange
함수는 이벤트가 발생한 필드의 name
을 보고 어떤 값을 업데이트할지 결정한다. 이렇게 하면 필드가 더 추가되더라도 새로운 핸들러를 만들 필요 없이 values
객체에 필드만 추가하면 된다. 코드도 더 깔끔해지고 관리하기도 쉬워진다.
결론
지금까지 React에서 폼을 다루는 두 가지 방식을 살펴봤다. Uncontrolled Component는 DOM이 폼 데이터를 직접 관리하는 방식이다. ref를 통해 값을 가져올 수 있어서 간단한 폼을 구현할 때 유용하다. 반면 Controlled Component는 React가 폼의 상태를 직접 제어하는 방식이다.
처음에는 각각의 입력 필드를 별도의 state로 관리하는 방식으로 시작할 수 있다. 하지만 폼이 복잡해지고 입력 필드가 많아지면, 하나의 객체로 상태를 관리하는 방식이 더 효율적이다. name 속성과 하나의 이벤트 핸들러로 여러 입력 필드를 처리할 수 있어 코드가 간결해지고 확장성도 좋아진다.
Uncontrolled Component는 간단한 폼에는 유용할 수 있지만, 복잡한 상호작용이 필요한 경우에는 Controlled Component를 사용하는 것이 좋다. 두 방식을 상황에 따라 혼합해서 사용할 수도 있지만, 코드의 일관성을 유지하는 것이 중요하다.
'Web, Front-end > React' 카테고리의 다른 글
[React] React의 이벤트 시스템 이해하기 (1) | 2025.01.29 |
---|---|
[React] useEffect의 내부적인 동작 (3) | 2024.12.13 |
[React] 하나의 index.ts에서 import 및 export 하기 (0) | 2024.05.09 |
[React] 컴포넌트 분리의 중요성 (0) | 2024.02.22 |
[React] 컴포넌트의 생명 주기(Life Cycle) (0) | 2024.02.18 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!