Form Controls

React에 의해 제어되는 폼 컨트롤

HTML 폼 컨트롤 방식

HTML 폼 컨트롤 요소(<input />, <textarea>, <select> 등)는 사용자의 입력 내용을 자체적으로 관리합니다. 각 컨트롤의 value 값을 JavaScript를 사용해 확인해 입력 내용을 읽거나, 쓸 수 있습니다.

const inputEmail = document.querySelector('form input[type="email"]')

// GET: HTML 폼 컨트롤 속성 값 읽기
console.log(inputEmail.value)

// SET: HTML 폼 컨트롤 속성 값 쓰기
inputEmail.value = 'yamoo9@euid.dev'

React 폼 컨트롤 방식

클래스 컴포넌트 상태 정보를 state 속성으로 관리하며 setState() 메서드를 사용해 상태를 업데이트 합니다.

class FormControl extends React.Component {

  state = {
    value: '',
  }

  handleChange = (e) => {
    this.setState({ value: e.target.value })
  }

  render() {
    return (
      <input
        value={this.state.value}
        onChange={this.handleChange}
      />
    )
  }
  
}

라이브 예제

Controlled 컴포넌트

React 컴포넌트는 폼을 통해 입력된 사용자의 값을 제어 할 수 있습니다. React를 통해 값이 관리되는 입력 요소는 컨트롤 컴포넌트(Controlled Component)입니다. 예를 들면 다음의 HTML 폼(<input /> 포함)을 React에 의해 컨트롤 되는 컴포넌트로 변경할 수 있습니다.

<form onsubmit="handleSubmit()">
  <label>
    @이메일: 
    <input type="email" name="email" placeholder="yamoo9@euid.dev" />
  </label>
  <button type="submit">전송</button>
</form>
class Form extends React.Component {

  state = {
    email: ''
  }
  
  handleInput = (e) => {
    this.setState({ email: e.target.value })
  }
  
  handleSubmit = (e) => {
    e.preventDefault()
    console.log(`입력하신 이메일 ${this.state.email}이 성공적으로 입력되었습니다.`)
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          @이메일:
          <input
            type="email"
            name="email"
            value={this.state.email}
            onChange={e => this.handleInput(e)} 
          />
        </label>
        <button type="submit">전송</button>
      </form>
    )
  }
  
}

컨트롤 컴포넌트를 사용하면 모든 state의 업데이트는 연결 된 이벤트 리스너에 의해 처리됩니다. 컨트롤 컴포넌트를 통해 사용자의 입력 내용을 업데이트 하거나, 유효성 검사 수행이 가능합니다. 예를 들어 입력 내용의 앞/뒤에 공백을 제거하고 싶다면 리스너 코드를 다음과 같이 수정합니다.

handleInput = (e) => {
  this.setState({
    email: e.target.value.trim()
  })
}

멀티 이벤트 핸들링

하나 이상의 <input />을 하나의 리스너로 컨트롤 할 수 있습니다. 여러 개의 컨트롤을 제어하기 위해서는 name 속성을 설정해야 합니다. 사용자가 입력한 등록 정보(email, password)를 처리하는 이벤트 리스너 예를 살펴봅니다.

class MultiControlInputs extends React.Component {

  state = {
    // 사용자 등록 정보 객체 (이메일, 패스워드)
    user: {
      email: '',
      password: ''
    }
  }

  // 멀티 이벤트 핸들링 리스너
  handleChange = (e) => {
    // 이벤트 타겟 객체 구조 분해 할당
    const {name, value} = e.target
    // user 상태 업데이트
    this.setState({
      user: {
        ...this.state.user,
        [name]: value 
      }
    })
  }

  render() {
    const { user: { email, password } } = this.state
    
    return (
      <>
        <input
          type="email"
          name="email"
          aria-label="계정 이메일"
          value={email}
          onChange={this.handleChange} />
        <input
          type="password"
          name="password"
          aria-label="계정 패스워드"
          value={password}
          onChange={this.handleChange} />
      </>
    )
  }
  
}

라이브 예제

TextArea 컨트롤

HTML <textarea> 요소는 사용자가 입력한 내용을 자식 텍스트 콘텐츠로 받습니다.

<textarea>오늘 하루를 정리하는 글을 작성해봅시다.</textarea>

하지만 React는 value 속성을 대신 사용합니다. 즉, 한 줄 입력을 사용하는 폼 컨트롤과 비슷하게 작성합니다.

<textarea
  value={this.state.value}
  onChange={this.handleChange}
/>

라이브 예제

Select 컨트롤

HTML <select> 요소는 드롭 다운 메뉴를 화면에 렌더링 하고, 초기 값을 selected 속성을 사용해 처리합니다.

<select class="learning-subject">
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>

반면, React는 value 속성을 사용해 초기 값을 설정합니다.

<select
  className="learning-subject"
  value={this.state.value}
  onChange={this.handleChange}
>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>

라이브 예제

Multiple Select 컨트롤

HTML <select> 요소를 사용해 하나 이상의 아이템을 선택할 수 있는 멀티플 드롭 다운 메뉴를 사용하려면 multiple 속성을 요소에 추가합니다.

<select class="learning-subject" multiple>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>

반면 React에서는 <select> 요소에 다음의 2가지 설정이 요구 됩니다.

  1. multiple 속성 값은 true 불리언(Boolean) 데이터 유형으로 설정

  2. value 속성 값은 배열(Array) 데이터 유형으로 설정

<select
  className="learning-subject"
  multiple={true}
  value={this.state.value}
  onChange={this.handleChange}
>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>

value 초기 값을 배열로 설정한 후, 핸들러 내부에서 사용자의 멀티 선택을 배열 값으로 업데이트 해야 합니다.

state = {
  value: []
}

handleChange(e) {
  // select > option 요소 수집 후 배열 데이터로 변경
  const options = Array.from(e.target.children)
  // 사용자가 선택한 option 필터링
  const selectedOptions = options.filter(option => option.selected)
  // 필터링 된 option.value 값을 아이템으로 하는 새로운 배열 반환
  const selectedOptionsValue = selectedOptions.map(option => option.value)
  // 상태 업데이트
  this.setState({value: selectedOptionsValue});
}

라이브 예제

Uncontrolled 컴포넌트

컨트롤 되지 않은(Uncontrolled) 컴포넌트는 React 외부에서 작동되는 것처럼 처리됩니다. 사용자가 폼 입력 컨트롤 (input, dropdown 등)에 입력하면 업데이트 된 정보가 React에서 별도 처리 과정 없이 요소에 바로 반영됩니다. 즉, React에 의해 "컨트롤 되지 않음"을 의미 합니다.

특별한 경우를 제외하고, 폼 컨트롤은 React에서 제어(Controlled) 하는 것이 권장됩니다.

File 인풋 요소

HTML <input type="file" /> 요소는 하나 이상의 파일을 사용자의 로컬 컴퓨터에서 서버로 업로드 할 때 사용합니다.

<input type="file" />

이 컴포넌트는 프로그래밍 방식으로 값을 설정 할 수 없고, 사용자에 의해 값이 설정되는 특별한 경우로 "컨트롤 할 수 없는 컴포넌트" 입니다. 컨트롤 할 수 없는 컴포넌트는 React가 아닌, DOM 자체에서 데이터를 다뤄야 합니다. (File API 사용)

모든 state 업데이트를 처리하는 이벤트 핸들러 대신, "컨트롤 할 수 없는 컴포넌트"를 제어하기 위해 직접 DOM을 통해 요소의 사용자 입력 값을 가져와 처리해야 합니다. 문제는 "가상 DOM에서 어떻게 실제 DOM에 접근할 것인가?" 입니다.

DOM 노드 참조

ref 속성은 컴포넌트가 렌더링 된 이후의 실제 DOM 노드나 React 요소에 접근해야 할 때 사용하는 방법입니다. 아래 예시는 핸들러에서 파일에 접근하기 위해 DOM 노드의 Ref(참조)를 만드는 방법을 보여주고 있습니다.

참조(Ref.) 생성

ref 속성을 활용하려면 먼저 React.createRef() 메서드를 사용해 참조 객체를 생성해야 합니다. 생성 된 참조(Ref.)는 ref 속성을 통해 DOM 노드 또는 React 요소를 가리키게 됩니다.

class FileInput extends Component {
  
  // 참조 객체 생성
  fileInput = React.createRef(null);

  handleSubmit = (event) => {
    event.preventDefault();
    console.log(`선택된 파일: ${this.fileInput.current.files[0].name}`)
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          업로드:
          {/* 참조 할 노드의 ref 속성에 참조 객체 연결 */}
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">전송</button>
      </form>
    );
  }

}

참조(Ref.) 접근

ref 속성이 설정 된 DOM 요소는 ref.current 속성을 통해 접근할 수 있습니다.

handleSubmit = (event) => {
  event.preventDefault();
  console.log(`선택된 파일: ${this.fileInput.current.files[0].name}`)
}

React v16.8 부터는 useRef() 훅을 사용해 함수형 컴포넌트에서도 ref 속성을 사용할 수 있습니다.

import React, {useRef} from 'react'


const FunctionalComponent = (props) => {
  const divRef = useRef(null)
  
  return (
    <div ref={divRef}> ... </div>
  )
}

이벤트 대상 활용

라이브 예제

Last updated

Was this helpful?