🔎 React 공식문서 자료
React_폼
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 state를 신뢰 가능한 단일 출처(single source of truth)
로 만들어 두 요소를 결합할 수 있다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어한다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트(controlled component)
라고 한다.
⚙️ 예시 코드
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
// ✨ preventDefault()
// - 어떤 이벤트를 명시적으로 처리하지 않은 경우,
// 해당 이벤트에 대한 사용자 에이전트의 기본 동작을 실행하지 않도록 저장
// - 이벤트 흐름의 어떤 흐름에서 preventDefault()를 실행하면 이벤트 취소
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
value
어트리뷰트는 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value
가 되고 React state는 신뢰 가능한 단일 출처(single source of truth)가 된다. React state를 업데이트하기 위해 모든 키 입력에서 handleChange
가 동작하기 때문에 사용자가 입력할 때 보여지는 값이 업데이트된다.
제어 컴포넌트로 사용하면, input의 값은 항상 React state에 의해 결정된다. 코드를 조금 더 작성해야 한다는 의미이지만, 다른 UI 엘리먼트에 input의 값을 전달하거나 다른 이벤트 핸들러에서 값을 재설정할 수 있다.
HTML에서 <textarea>
엘리먼트는 텍스트를 자식으로 정의한다.
<textarea>
Hello there, this is some text in a text area
</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>
는 드롭 다운 목록을 만든다. 예를 들어, 이 HTML은 과일 드롭 다운 목록을 만든다.
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<!-- selected ▶ 기본값으로 시작 (선택) -->
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
selected
옵션이 있으므로 Coconut 옵션이 초기값이 되는 점을 주의해야 한다. React에서는 selected
어트리뷰트를 사용하는 대신 최상단 select
태그에 value
어트리뷰트를 사용한다. 한 곳에서 업데이트만 하면 되기 때문에 제어 컴포넌트에서 사용하기 더 편한다.
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
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 flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
// select ▶ HTML의 selected와 같음 (기본값 선택)
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
전반적으로 <input type="text">
, <textarea>
및 <select>
모두 매우 비슷하게 동작한다. 모두 제어 컴포넌트를 구현하는데 value
어트리뷰트를 허용한다.
🛑 주의
select
태그에 multiple 옵션을 허용한다면 value
어트리뷰트에 배열을 전달할 수 있다.// B와 C를 동시에 선택 가능
<select multiple={true} value={['B', 'C']}>
HTML에서 <input type="file">
는 사용자가 하나 이상의 파일을 자신의 장치 저장소에서 서버로 업로드하거나 File API
를 통해 JavaScript로 조작할 수 있다.
<!-- ⚙️ file-->
<!-- 1. 선택한 모든 파일을 나열하는 FileList 객체 -->
<!-- 2. multiple 특성을 지정하지 않았다면 두 개 이상의 파일을 포함하지 않음 -->
<input type="file" />
값이 읽기 전용이기 때문에 React에서는 비제어 컴포넌트
이다.
여러 input
엘리먼트를 제어해야할 때, 각 엘리먼트에 name
어트리뷰트를 추가하고 event.target.name
값을 통해 핸들러가 어떤 작업을 할 지 선택할 수 있게 한다.
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
주어진 input
태그의 name에 일치하는 state를 업데이트하기 위해 ES6의 computed property name
구문을 사용한다.
this.setState({
[name]: value
});
ES5 코드는 다음과 같다.
var partialState = {};
partialState[name] = value;
this.setState(partialState);
또한, setState()
는 자동적으로 현재 state에 일부 state를 병합하기 때문에 바뀐 부분에 대해서만 호출하면 된다.
✨ 객체 초기자(computed property name)
{중괄호}
로 묶인 0개 이상의 객체의 프로퍼티명과 관련 값의 쌍을 콤마로 구분한 목록이다.제어 컴포넌트에 value
prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없다. value
를 설정했는데 여전히 수정할 수 있다면 실수로 value
를 undefined
나 null
로 설정했을 수 있다.
ReactDOM.render(<input value="hi" />, mountNode);
// 1초 뒤에 입력 가능
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
데이터를 변경할 수있는 모든 방법에 대해 이벤트 핸들러를 작성하고 React 컴포넌트를 통해 모든 입력 상태를 연결해야 하기 때문에 때로는 제어 컴포넌트를 사용하게 지루할 수 있다.
특히, 기존의 코드 베이스를 React로 변경하고자 할 때나 React가 아닌 라이브러리와 React 애플리케이션을 통합하고자 할 때 짜증날 수 있다.
이러한 경우에 입력 폼을 구현하기 위한 대체 기술인 비제어 컴포넌트
를 사용한다.
유효성 검사, 방문한 필드 추적 및 폼 제출 처리와 같은 완벽한 해결을 원한다면 Formik
이 대중적인 선택중 하나이다. 그러나 Formik
은 제어 컴포넌트 및 state 관리에 기초하기 때문에 공부를 해야 한다.