Skip to content
This page has been auto-translated and may contain errors.View in English

평범한 HTML input에 입력하면 브라우저가 값을 관리합니다. 입력한 내용을 저장하고 커서를 다시 그리는데, 여기에 코드가 필요 없습니다. 이 input의 valuestate에 연결하고 onChange에서 그 state를 업데이트하면, input이 React의 다른 모든 것처럼 동작하기 시작합니다. state가 페이지에 표시되는 내용의 원천이 되고, input이 이를 반영합니다. 이것이 제어된 입력값이며, React가 매 순간 무엇을 표시할지 결정합니다.

여기 텍스트 입력 하나가 있는 간단한 폼이 있습니다:

jsx
function NameForm() {
  const [text, setText] = useState('')

  function handleSubmit(event) {
    event.preventDefault()
    console.log(text)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button>Submit</button>
    </form>
  )
}

input의 valuetext state에서 옵니다. 매번 키를 누를 때마다 onChange가 발생하고, 새로운 값으로 setText를 호출합니다. state와 input은 계속 동기화됩니다. React가 현재 text 값으로 input을 다시 렌더링하기 때문입니다. 기본적으로 폼을 제출하면 페이지가 다시 로드되므로, handleSubmit은 먼저 event.preventDefault()를 호출한 후 앱이 필요한 작업을 합니다. 여기서는 텍스트를 기록하거나 다른 곳의 서버로 보냅니다.

제어된 입력값과 제어되지 않은 입력값

위의 input은 제어된 입력값입니다. 그 값이 React state에 있고 React가 무엇을 표시할지 결정하기 때문입니다. onChange 없이 value를 주면, input이 읽기 전용이 됩니다. React가 매 렌더링마다 같은 값을 계속 칠하기 때문에, 입력한 어떤 것도 state에 도달하지 않습니다. 따라서 필드가 제어될 때는 항상 둘을 함께 사용하세요. input은 또한 제어되지 않을 수 있습니다. DOM이 자신의 값을 추적하고, 필요할 때만 값을 읽어옵니다. 단일 필드의 경우 ref(Refs and the DOM에서 자세히 다룸)를 통해, 또는 전체 폼의 경우 제출 시간에 폼의 FormData를 통해 읽습니다. 제어된 입력값은 매번 키 입력할 때마다 현재 값을 컴포넌트에 제공합니다. 실시간 유효성 검사, 문자 수 세기, 또는 UI의 다른 부분이 의존하는 필드에 필요합니다. 제어되지 않은 입력값은 매번 키 입력에서 렌더링을 건너뛰고, 제출 시간에만 값을 읽습니다. 단순한 필드에서 다른 것이 감시할 필요가 없을 때는 코드가 더 적습니다.

체크박스

체크박스는 같은 패턴을 따르지만, state가 value 대신 checked에 있습니다:

jsx
function Newsletter() {
  const [subscribed, setSubscribed] = useState(false)

  return (
    <label>
      <input
        type="checkbox"
        checked={subscribed}
        onChange={e => setSubscribed(e.target.checked)}
      />
      업데이트 구독
    </label>
  )
}

e.target.checked는 boolean이고, subscribed가 체크박스에 체크 표시가 되어 있는지를 결정합니다. 한 개의 prop, 한 개의 변경 핸들러, 텍스트 입력값과 같은 형태입니다.

선택 드롭다운

<select>value를 받지만, 선택된 option이 아닌 select 자체에 설정됩니다:

jsx
function ColorPicker() {
  const [color, setColor] = useState('red')

  return (
    <select value={color} onChange={e => setColor(e.target.value)}>
      <option value="red">Red</option>
      <option value="green">Green</option>
      <option value="blue">Blue</option>
    </select>
  )
}

selectvalue를 설정하면, React가 일치하는 option을 선택해줍니다. 어떤 option 요소에도 selected 속성을 추가할 필요가 없습니다.

Textarea

HTML에서 textarea는 여는 태그와 닫는 태그 사이에 텍스트를 담습니다. React는 다른 입력값처럼 취급하므로, 텍스트는 value prop을 통해 도착하고 onChange가 동기화를 유지합니다:

jsx
function Feedback() {
  const [message, setMessage] = useState('')

  return (
    <textarea
      value={message}
      onChange={e => setMessage(e.target.value)}
      rows={4}
    />
  )
}

rows는 상자가 시작할 높이를 설정합니다. defaultValue는 여기서 value의 제어되지 않은 동등물입니다. 시작 텍스트를 한 번 설정하고 DOM에서 추적하도록 둡니다.

라디오 버튼

여러 입력값이 하나의 name을 공유하는데, 이것이 브라우저에 이들이 함께 속해 있다고 알립니다. 하나만 선택될 수 있습니다. React에서 그룹은 하나의 state를 공유하고, 각 입력값의 checked는 이에 대해 비교합니다:

jsx
function ShippingSpeed() {
  const [speed, setSpeed] = useState('standard')

  return (
    <fieldset>
      <legend>배송 속도</legend>
      {['standard', 'express', 'overnight'].map(option => (
        <label key={option}>
          <input
            type="radio"
            name="speed"
            value={option}
            checked={speed === option}
            onChange={e => setSpeed(e.target.value)}
          />
          {option}
        </label>
      ))}
    </fieldset>
  )
}

fieldsetlegend는 스크린 리더를 위해 그룹을 레이블합니다. 각 label은 옵션이 배열에서 오기 때문에 key를 받습니다 (리스트와 key).

checked={speed === option}은 정확히 한 옵션에 대해 참이므로, 하나의 state 값이 전체 그룹을 포함합니다. 반면 체크박스는 자신의 boolean을 가집니다.

액션으로 제출하기

<form>action 함수를 가질 수도 있습니다. React는 폼이 제출될 때 이를 호출하고 폼의 값을 담은 FormData 객체를 전달합니다:

jsx
function Signup() {
  function handleSignup(formData) {
    const values = Object.fromEntries(formData)
    console.log(values.email, values.password)
  }

  return (
    <form action={handleSignup}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      <button>가입</button>
    </form>
  )
}

여기서는 event가 없으므로, preventDefault()를 호출할 것도 없습니다. React가 브라우저의 기본 페이지 재로드를 멈춰줍니다.

Object.fromEntries(formData)는 그 FormData를 한 단계로 평범한 객체로 변환합니다. 키는 각 input의 name 속성에서 옵니다. 따라서 폼의 모든 input에 이름이 필요합니다. 여러 input이 이름을 공유하고 함께 전송될 수 있을 때, 예를 들어 체크박스 그룹일 때, 마지막 값만 남으며 formData.getAll이 모두를 읽습니다.

위의 입력값들은 제어되지 않습니다. DOM이 제출할 때까지 각 값을 보관합니다. 이것은 onSubmit에서 사람들을 놀라게 하는 한 가지 동작에 중요합니다. React는 액션이 해결되면 폼을 재설정하고, 이렇게 제어되지 않은 필드들을 비웁니다. 제어된 필드는 state에서 다시 렌더링되어 그 값을 유지합니다.

각 경로는 다양한 종류의 폼에 맞습니다. 액션은 값을 모아서 건네주는 것이 직업인 폼, 보통 서버에, 대기가 실제이고 결과가 UI로 돌아와야 할 때입니다. 값들이 모여서 이름이 붙어옵니다. 제어된 입력값과 함께 onSubmit은 사용자가 입력하는 동안 반응해야 하는 폼에 맞습니다. 실시간 유효성 검사, 문자 수, 또는 필드가 폼의 나머지를 변경하도록 합니다.

버전 참고

폼 액션은 React 19에서 도입되었습니다. React 18 이하에서는 <form action={handleSignup}>이 에러를 던지지 않습니다. 함수는 유효한 속성 값이 아니기 때문에 React가 속성을 버리고 개발 환경에서 경고합니다. 그러면 폼이 현재 URL로 제출되어 페이지가 재로드되고 함수는 실행되지 않습니다. 이 버전들에서 제출은 event.preventDefault()와 함께 onSubmit을 통합니다.

실제 폼이 한 필드에서 멈추는 경우는 드뭅니다. 이름, 이메일, 비밀번호를 묻는 가입 폼은 세 개의 useState 호출을 추적할 수 있지만, 하나의 state 객체와 하나의 변경 핸들러는 자신을 반복하지 않으면서 어떤 수의 필드도 다룹니다:

jsx
function SignupForm() {
  const [values, setValues] = useState({ name: '', email: '', password: '' })

  function handleChange(event) {
    const { name, value } = event.target
    setValues(prev => ({ ...prev, [name]: value }))
  }

  return (
    <form>
      <input name="name" value={values.name} onChange={handleChange} />
      <input name="email" value={values.email} onChange={handleChange} />
      <input type="password" name="password" value={values.password} onChange={handleChange} />
    </form>
  )
}

각 input의 name 속성은 values의 키와 일치합니다. handleChangeevent.target.name을 읽어서 업데이트할 키를 알고, [name]: value가 같은 키 아래에 다시 작성합니다. 폼에 또 다른 필드를 추가하고, 그 input에 일치하는 name을 주면, 기존 핸들러가 이미 이를 다룹니다.

유효성 검사는 두 개의 다른 지점에서 실행될 수 있으며, 이들은 다른 목적을 제공합니다. 변경 시 유효성 검사, handleChange 내부에서 새로운 값을 들어올 때 확인하면, 즉시 피드백이 나옵니다. 비밀번호 강도 미터 같은 것에 유용합니다. 또한 사용자가 아직 입력을 마치지 않은 필드에 에러가 나타날 수 있다는 의미입니다. 제출 시 유효성 검사, 무엇이든 일어나기 전에 제출 핸들러 내부에서 전체 values 객체를 확인하면, 사용자가 완료될 때까지 기다립니다. 이것이 필수 필드와 형식 확인에 더 나은 기본값이 되는 경향이 있습니다. 많은 폼은 둘을 섞습니다. 변경 시 가벼운 검사, 제출 시 전체 통과.

서버와 통신하는 제출은 시간이 걸립니다. 그 대기 중에 클릭 가능한 버튼은 두 번째 클릭을 초대하고 중복 제출이 일어납니다. state에서 submitting 플래그를 추적하고, 요청이 시작되기 전에 설정하고, 참인 동안 버튼을 비활성화합니다:

jsx
function SignupForm() {
  const [values, setValues] = useState({ name: '', email: '', password: '' })
  const [submitting, setSubmitting] = useState(false)

  async function handleSubmit(event) {
    event.preventDefault()
    setSubmitting(true)
    await saveSignup(values)
    setSubmitting(false)
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* 입력값들이 여기에 옵니다 */}
      <button disabled={submitting}>{submitting ? '제출 중...' : '가입'}</button>
    </form>
  )
}

disabled={submitting}은 요청이 진행 중인 동안 버튼을 회색으로 만들고 클릭을 차단한 후, 해결되는 즉시 제어권을 돌려줍니다. 값을 모아서만 보내는 폼은 전체 작업을 action에 전달할 수 있습니다. 여기서 useActionState가 액션을 래핑하고 하나를 직접 연결하지 않고도 그 pending 플래그를 제공합니다.

제어된 입력값의 매번 키 입력은 왕복 여행을 만듭니다. DOM이 이벤트를 발생시키고, 핸들러가 setText를 호출하고, React가 컴포넌트를 다시 렌더링하고, 새로운 value prop이 같은 input에 다시 착지합니다. 순간적으로 느껴지지만, input의 표시된 값은 각 렌더링에서 React에 의해 설정되고 있습니다. DOM 요소는 여기서 자신의 메모리가 없고, 지금 state가 말하는 대로 표시합니다.

그 왕복 여행은 또한 비용입니다. 많은 제어된 필드를 가진 폼은 그 어느 것이든 매번 키 입력에서 전체 컴포넌트를 다시 렌더링합니다. 보통 무시할 만큼 충분히 저렴합니다. 그렇지 않거나, 필드의 값이 제출할 때까지 다른 어떤 것에도 영향을 줄 필요가 없을 때, ref를 가진 제어되지 않은 입력값이 더 간단한 선택입니다:

jsx
function NameForm() {
  const inputRef = useRef(null)

  function handleSubmit(event) {
    event.preventDefault()
    console.log(inputRef.current.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="" />
      <button>제출</button>
    </form>
  )
}

state가 없고, 매번 키 입력에 다시 렌더링이 없습니다. defaultValue는 textarea에서 하는 것과 같은 일을 여기서 합니다. 필드가 완전히 격리되어 있을 때 이를 사용하세요. 사용자가 입력하는 동안 아무것도 다르게 렌더링되지 않습니다.

useActionState는 액션을 래핑하고 액션이 반환한 것과 함께 pending 플래그를 돌려주므로, submitting 플래그와 에러 메시지는 더 이상 직접 연결하는 분리된 state의 조각이 아닙니다. Server Actions과 이 위에 구축된 프레임워크 통합은 이를 더 진행합니다. 액션 자체를 서버에서 실행하여, 이 핸드북이 가는 곳을 넘어갑니다.

JunoState가 입력값을 운영합니다 붙잡을 패턴은 작습니다. input의 value는 state에서 오고, onChange는 그 state를 업데이트합니다. 이것이 텍스트 입력값에 대해 클릭되면, 선택 드롭다운과 textarea에 같은 방식으로 작동하고, checked를 가진 체크박스와 라디오 버튼에도 state를 제공합니다. 제출은 자신의 단계입니다. 폼의 제출은 작성한 함수를 실행하고, 그 함수가 값으로 무엇을 할지 결정합니다.
JunoState가 입력값을 운영합니다 제어된 입력값은 기본값입니다. value (또는 체크박스의 경우 checked)가 state에 연결되고, onChange가 이를 업데이트하고, onSubmitevent.preventDefault()와 함께 제출을 처리합니다. 여러 필드 폼의 경우, 하나의 state 객체와 name으로 키된 handleChange가 분리된 useState 호출의 무더기를 대체합니다. submitting 플래그가 요청이 진행 중인 동안 버튼을 깨끗하게 유지합니다. 폼의 action 함수는 다른 경로입니다. React가 FormData 객체를 전달하고 event를 처리하며, useActionState가 서버가 개입하면 그 submitting 플래그를 인수합니다.
JunoState가 입력값을 운영합니다 제어된 입력값은 React가 매 렌더링에서 DOM의 값을 소유하는 것이며, 이것이 여기서 값을 읽고, 유효성을 검사하거나, 어느 지점에서든 파생시킬 값을 제공하는 것입니다. 그 왕복 여행이 일을 하고 있지 않을 때와 제어되지 않은 ref가 할 것을 알고, 폼의 직업이 값을 모아서 건네주는 것일 때 폼 action에 이를 사용하세요. useActionState가 서버가 개입하면 pending state를 나릅니다.

다음: State 끌어올리기. 두 컴포넌트가 같은 state를 공유해야 할 때입니다.