React공식문서-Form
대부분 경우에 폼을 구현하는데 제어 컴포넌트를 사용하는 것이 좋습니다. 제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어집니다.
input
, textarea
, select
등의 form 요소들은 태그 그 자체들로 사용자의 입력을 기반으로 데이터를 가지고 있습니다. 이러한 폼 태그들을 리액트에서도 그대로 사용을 할 수 있습니다.
아래 예시를 보겠습니다. 예시는 label과 input태그를 가지고 있습니다.
import React from 'react'
export default function SimpleForm() {
const handleChange = (e) => {
console.log(e.target.value)
}
return (
<>
<label>닉네임: </label>
<input type="text" name="nickname" onChange={handleChange} />
</>
)
}
이 컴포넌트를 리액트에서 한번 렌더링해보도록 하겠습니다.
아래와 같이 input태그에 값이 찍힐 때마다 onChange이벤트에 의해 콘솔에 값이 찍히고 있는 것을 확인할 수 있습니다
import React from 'react'
export default function SimpleForm() {
const handleChange = (e) => {
console.log(e.target.value)
}
//현재 위치에서는 input의 value(e.target.value)를 가져와 사용할 수 없습니다.
const handleSubmit = (e) => {
//form태그 내의 다른 input들의 값을 가져와 사용하기가 어렵습니다.
}
return (
<form onSubmit={handleSubmit}>
<label>닉네임: </label>
<input type="text" name="nickname" onChange={handleChange} />
<input type="submit" value="제출" />
</form>
)
}
따라서 위 예제처럼 input태그에서 value를 관리하기 때문에 리액트 코드 내의 원하는 위치에서 input창의 e.target.value
를 가져와 사용하기가 조금 힘든 상황입니다.
마찬가지로 input[type=submit]
버튼이 눌렸을 때 form
태그의 onSubmit에 걸려있는 handleSubmit함수 내부에서 input태그들이 가지고 있는 값을 가져와 사용을 하기가 어렵습니다.
리액트에서 useState 훅을 이용하는 등 값들을 관리할 수 있는 작업이 필요합니다.
HTML에서 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.
하지만 리액트 내에서 form태그들을 사용했을 경우 DOM에서도 관리를 하고, React에서도 관리를 하는 상황이 되어버립니다.
그렇기 때문에 React안에서 html의 폼 엘레먼트까지 제어할 수 있도록 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합합니다.
이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.
(제어 컴포넌트의 경우 value어트리뷰트를 기반으로 하고 있습니다.)
import React, {useState} from 'react'
export default function SimpleForm() {
const [nickname, setNickName] = useState('') //1.
const handleChange = (e) => {
setNickName(e.target.value) ✅//setState에서 e.target.value을 지정하면
}
const handleSubmit = (e) => {
//submit의 기본 기능이 동작하지 않도록 제어
e.preventDefault()
//✅3. 위에서 setState로 관리하고 있기때문에 여기서도 input값을 가져와 사용할 수 있습니다.
alert(nickname)
}
return (
<form onSubmit={handleSubmit}>
<label>닉네임: </label>
<input
type="text"
name="nickname"
onChange={handleChange}
value={nickname} //✅2.
/>
<input type="submit" value="제출" />
</form>
)
}
여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name 어트리뷰트를 추가하고 event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택할 수 있게 해줍니다.
import React, {useState} from 'react'
export default function SimpleForm() {
const [nickname, setNickName] = useState('')
const [password, setPassWord] = useState('')
const handleChange = (e) => {
//e.target.name이 nickname일수도, password일 수도 있으므로
if(e.target.name === "nickname") return setNickName(e.target.value)
return setPassWord(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault();
alert(`nickname: ${nickname}, password: ${password} `)
}
return (
<form onSubmit={handleSubmit}>
<label>닉네임: </label>
<input
type="text"
name="nickname"
onChange={handleChange}
value={nickname}
/>
<label>패스워드: </label>
<input
type="text"
name="password"
onChange={handleChange}
value={password}
/>
<input type="submit" value="제출" />
</form>
)
}
그런데 만약 여러개의 input 태그를 다뤄야하는 경우 state값도 늘어나게 됩니다. 마찬가지로 if구문도 계속 늘어나게 될 것입니다.
따라서 이를 아래와 같이 각각의 state로 만드는 것이 아닌 하나의 객체로 관리함으로써 조금 더 효율적으로 사용을 할 수 있습니다.
import React, {useState} from 'react'
export default function SimpleForm() {
//✅각각의 state로 만드는 것이 아닌 하나의 객체로 관리를 해줍니다.
const [userInputs, setUserInputs] = useState({
nickname: '',
password: ''
})
const handleChange = (e) => { //✅
setUserInputs({
...userInputs,
[e.target.name] : e.target.value
})
}
const handleSubmit = (e) => {
e.preventDefault();
const { nickname, password } = userInputs //✅
alert(`nickname: ${nickname}, password: ${password} `)
}
return (
<form onSubmit={handleSubmit}>
<label>닉네임: </label>
<input
type="text"
name="nickname"
onChange={handleChange}
value={userInputs.nickname}
/>
<br/>
<label>패스워드: </label>
<input
type="text"
name="password"
onChange={handleChange}
value={userInputs.password}
/>
<input type="submit" value="제출" />
</form>
)
}
이와 같이 데이터를 변경할 수 있는 모든 방법에 대해 이벤트 핸들러를 작성하고 React에 연결한 후 모든 입력 상태를 연결해 제어를 합니다.
제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어집니다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어집니다.
DOM자체에서 다룬다면, input의 값들을 handlechange함수가 아닌 그 외부에서 다루려면 어떻게해야하는지에 대한 문제를 해결해야합니다. 이를 위해 ref 방법을 제공합니다.
//클래스 컴포넌트
import {createRef} from "react"
//함수 컴포넌트
import {useRef} from "react"
ref를 사용하는 방법은 위와 같이 클래스 컴포넌트와 함수 컴포넌트가 조금 다릅니다.
App.js
import React from "react"
export default function UnControlledForm() {
const handleChange = (e) => {
console.log(e.target.value)
}
return (
<>
<label>닉네임 :</label>
<input type="text" name="nickname" onChange={handleChange}/>
</>
)
}
UnControlledForm.js
//함수에서는 useRef로 가져올 수 있습니다.
import React, { useRef } from "react"
//ref활용 => 비제어 컴포넌트로 From을 다뤄보자
export default function UnControlledForm() {
const inputRef = useRef() //✅
const handleChange = (e) => {
console.log(e.target.value)
}
console.log(inputRef)
console.log(inputRef.current)
return (
<>
<label>닉네임 :</label>
<input type="text" name="nickname" onChange={handleChange} ref={inputRef}✅/>
</>
)
}
useRef로 만든 객체에는 Current라는 키를 가지고 있는 것을 확인할 수 있습니다. 이렇게 만든 ref객체를 ref={inputRef}
태그에 다음과 같이 붙이는 경우 current가 가르키는 값에 해당 태그가 들어가게 됩니다.
이번에는 한번 form태그를 붙인 후 handleSubmit함수를 통해 input태그의 값을 한번 가져올 수 있는지 확인해보도록 하겠습니다.
//ref활용 => 비제어 컴포넌트로 From을 다뤄보자
export default function UnControlledForm() {
const inputRef = useRef()
const handleSubmit = (e) => {
e.preventDefault();
alert(inputRef.current.value)
}
console.log(inputRef)
return (
<form onSubmit={handleSubmit}>✅
<label>닉네임 :</label>
<input
type="text"
name="nickname"
ref={inputRef}
/>
<input type="submit" value="제출" />
</form>
)
}
수정한 값이 올바르게 뜨는 것을 나오는 것을 확인할 수 있습니다. 이렇게 비제어 컴포넌트 방식으로, DOM에 있는 값을 가져와 사용을 할 수 있습니다.