IT

[영어로 배우는 React] 9. 폼

생각파워 2022. 4. 20. 18:05

공식 사이트 : https://reactjs.org/docs/forms.html

 

 

HTML 폼 앨리먼트는 리액트의 DOM 앨리먼트와 약간 다르게 작동하기 때문에, 폼 앨리먼트는 자연적으로 몇몇 내부 상태를 유지합니다. 아래 예를 보면, 이 폼에서 순수한 HTML은 단일 이름을 받아들입니다. 

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

위의 폼은 유저가 폼을 서브밋했을 때, 새 페이지로 이동하는 기본 HTML 폼을 가지고 있습니다. 리액트에서 이 동작을 수행하길 원한다면, 그냥 동작합니다. 그러나 대부분의 케이스에서, 폼을 제출하고, 사용자가 입력한 데이터에 액세스할 수 있는 자바스크립트 함수가 있다면 편리합니다. 이걸 이루기 위한 표준적인 기술을 "콘트롤 된 컴포넌트" 라고 부릅니다. 

 

 

제어된 컴포넌트

HTML에서 <input>,<textarea>,<select>와 같은 폼 앨리먼트는 일반적으로 자신의 상태를 유지하고, 사용자의 입력을 기초로 업데이트합니다. 리액트에서 변경가능한 상태는 일반적으로 컴포넌트의 상태 요소로 유지됩니다. 그래서 setState()메소드로만 업데이트 할 수 있습니다. 우리는 리액트 상태가 "진실의 단일 소스"가 되도록 두가지를 조합할 수 있습니다. 그리고 폼으로 랜더링되는 리액트 컴포넌트 또한 사용자 입력 후에 어떤 작업이 발생시킬지 컨트롤 할 수 있습니다. 리액트에 의해 이런방식으로 리액트에 의해 값이 컨트롤되는 입력폼 앨리먼트를 제어된 컴포넌트라고 부릅니다. 예를 들어, 우리가 앞선 예제에서 폼이 제출될 때, 이름이 로그되게 만들길 원한다면, 제어된 컴포넌트로 폼을 쓸 수 있습니다. 

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);
    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 값이 될 것입니다. handleChange는 리액트 상태를 업데이트하기 위해, 모든 키 스트로크에 대해 실행되기 때문에, 출력된 값은 사용자의 타이핑에 의해 업데이트 될 것입니다. 키보드 자판 하나 칠때마다 this.state.value값이 계속 업데이트 되는 것이죠. 제어된 컴포넌트로  인해 input의 값은 항상 리액트 state의 상태에 따라 결정됩니다. 이것은 약간의 코드를 타이핑하는 동안 다른 UI 앨리먼트로 값을 전달하거나, 다른 이벤트 핸들러로부터 값이 리셋될 수 있다는 것을 의미합니다. 

 

 

textarea 태그

HTML에서 <textarea> 앨리먼트는 그 자식에 의해 text 값을 결정합니다. 

<textarea>
  Hello there, this is some text in a text area
</textarea>

리액트에서 <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가 약간의 텍스트를 가지고 시작한다는 것입니다.

 

 

select 태그

HTML에서 <select> 태그는 드롭다운 리스트를 만듭니다. 아래와 같이 맛있는 것의 리스트를 만드는거죠.

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

selected 속성 때문에 Coconut이 기본적으로 선택되어 있습니다. 리액트에서는 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 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 태그에서 다수선택을 허용하면, value 속성으로 배열을 전달할 수 있습니다. 

<select multiple={true} value={['B', 'C']}> 

 

 

file input 태그

HTML에서 <input type="file">태그는 파일을 서버로 업로드하거나 FileAPI를 통해 자바스크립트에서 조작을 위해, 스토리지에서 파일을 하나 또는 여러개를 선택할 수 있게 합니다. 

<input type="file" />

그렇기 때문에 그것의 value 값은 읽기전용이고, 리액트에서 컨트롤되지 않는 컴포넌트 입니다. 이것은 이후에 다른 컨트롤되지 않는 컴포는트와 함께 이후 문서에서 논의될 것입니다. 

 

 

 

다중입력 다루기

여러개의 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 태그가 두개가 있고, input 태그의 값이 변경되면 handleInputChange() 함수를 호출하게 돼 있습니다.  handleInputChange() 함수가 input 태그의 name을 체크해서 서로 다른 작업을 처리하고 있습니다. 또 주의해서 볼 부분은 주어진 input name으로 state 키를 업데이트 하기 위해, ES6의 '계산된 프로퍼티 명' 문법을 사용한 부분입니다. 

this.setState({
  [name]: value});

ES5 문법에서는 아래와 같이 표기합니다. 

var partialState = {};
partialState[name] = value;
this.setState(partialState);

또한, setState() 메소드가 자동적으로 부분적인 state를 현재 state로 병합하기 때문에, 변경된 부분으로만 호출하면 됩니다.

 

 

컨트롤 된 input Null 값

컨트롤 된 컴포넌트에서 value값으로 구현하는 것은 사용자가 바라지 않는 변경으로 부터 사용자를 보호합니다. 만약 value값을 구체화하고, <input>이 수정가능한 경우, value값을 undefined나 null로 세팅할 수 있습니다. 다음 예제가 이문제를 나타낸 코드입니다(<input>이 처음에는 잠겨있다가 잠시후 수정가능해졌습니다.)

ReactDOM.createRoot(mountNode).render(<input value="hi" />);

setTimeout(function() {
  ReactDOM.createRoot(mountNode).render(<input value={null} />);
}, 1000);

 

컨트롤된 컴포넌트의 대안

컨트롤 된 컴포넌트를 사용하는 것이 가끔 장황해보일수 있습니다. 보통 데이터가 변경될수 있는 모든 상황에 대해 이벤트 핸들러를 작성해야 하고, 그것을 리액트 컴포넌트로 전달해야 되기 때문입니다. 이것은 이미 존재하는 리액트 코드를 변경할 때나 리액트 애플리케이션을 리액트 라이브러리가 아닌것으로 통합할 때 특히 성가실 수 있습니다. 이런 상황들에서는 컨트롤되지 않은 컴포넌트나 input 폼을 대체할수 있는 기술을 사용하길 원할 수 있습니다.  

 

 

완전히 독립된 해결책

만약 검증, 방문한 필드들의 자취 유지, 폼 제출 제어를 위한 완전한 솔루션을 찾는다면, Formik가 하나의 선택지가 될 것입니다. 그러나 Formik는 컨트롤된 컴포넌트와 state관리의 원칙위에 만들어졌기 때문에, 그 원칙을 배우는 것을 등한시 해서는 안됩니다. 

 

 

 

 

 

반응형