HTML 폼 엘리먼트는 폼 엘리먼트 자체가 내부 상태를 가지기 때문에, React의 다른 DOM 엘리먼트와 다르게 동작한다.
예를 들어, 아래의 순수한 HTML에서 아래의 폼은 name을 입력받는다.
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
이 폼은 사용자가 폼을 제출하면 새로운 페이지로 이동하는 기본 HTML 폼 동작을 수행한다. React에서 동일한 동작을 원한다면 그대로 사용하면 된다. 그러나 대부분의 경우, JavaScript 함수로 폼의 제출을 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리하다. 이를 위한 표준 방식은 “제어 컴포넌트 (controlled components)“라고 불리는 기술을 이용하는 것이다.
HTML에서 < input > , < textarea >, < select >와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트를 한다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트된다.
React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 한다.
HTML에서 < textarea > 엘리먼트는 텍스트를 자식으로 정의한다.
<textarea>
나는 text
</textarea>
React에서 < textarea >는 value 어트리뷰트를 대신 사용한다.
이렇게하면 < textarea >를 사용하는 폼은 한 줄 입력을 사용하는 폼과 비슷하게 작성할 수 있다.
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
여기서 주의할 점은 this.state.value 를 생성자에서 초기화하므로 textarea는 일부 텍스트를 가진채 시작되는 점이다.
HTML에서 < select >는 Dropdown 목록을 만든다.
아래의 예제는 지역 목록을 만든 것이다.
<select>
<option value="Seoul">Seoul</option>
<option value="Busan">Busan</option>
<option selected value="Ulsan">Ulsan</option>
</select>
selected 옵션이 있으므로 Busan 옵션이 초기값이 된다. React에서는 selected 어트리뷰트를 사용하는 대신 최상단 select태그에 value 어트리뷰트를 사용한다. 한 곳에서 업데이트만 하면되기 때문에 제어 컴포넌트에서 사용하기 더 편하다.
class RegionForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'Ulsan'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite area is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite area:
<select value={this.state.value} onChange={this.handleChange}>
<option value="Seoul">Seoul</option>
<option value="Busan">Busan</option>
<option value="Ulsan">Ulsan</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
select 태그에 multiple 옵션을 허용한다면 value 어트리뷰트에 배열을 전달할 수 있다.
<select multiple={true} value={['Seoul', 'Busan']}>
제어 컴포넌트는 UI에 입력한 데이터 상태와 저장한 데이터의 상태가 항상 일치하는 것을 알 수 있다. 즉 사용자가 입력하는 모든 데이터가 동기화 된다.
예를들면, input태그 안에 "배고프다"라는 값을 입력한다고 했을때 아래 형식으로 업데이트가 되는데
ㅂ
배
배ㄱ
배고
배고ㅍ
배고프
배고프ㄷ
배고프다
이런식으로 불필요한 단어 입력시에도 값이 갱신되어 버린다. 이는 불필요한 리렌더링, 불필요한 api요청으로 인한 자원 낭비 문제로 연결 될 수 있다.
이러한 불필요한 방법을 막기 위해선 스로틀링이나 디바운싱(throttle&debounce)을 사용할 수 있다.
스로틀링: 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
디바운싱 : 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것