폼 데이터 양방향 바인딩은 항상 필요한가?
폼 데이터 양방향 바인딩은 항상 필요한가?
우리는 유저에게 입력값을 받아 입력된 값을 저장하거나 수정하고자 할때 Form 을 사용한다.
React, Vue 등으로 Form 을 다루고자 할때에는 해당값을 바인딩해서 제어하고 싶어하는 경향이 있다. 프레임워크의 방식대로 사고하다보면 자연스레 해당 값을 상태로서 제어하고 싶어하기 때문 이다.
가령 nickname 을 입력받는 Form 을 React 로 Controlled 하게 작성해보자.
Controlled Form
const ReactForm = () => {
const [nickname, setNickname] = React.useState() // 1. nickname 의 상태
return (
<form>
<label>
nickname:
<input
type="text"
name="nickname"
value={nickname}
onChange= {e => setNickname(e.target.value)} // 2. change 마다 username 동기화
/>
</label>
<button type="submit">Join the room</button>
{/* 3. username 상태가 동기화 되어 업데이트 된다. */}
<p>nickname is : {nickname}</p>
</form>
)
}
ReactDOM.render(<ReactForm />, document.querySelector("#app"))
- 1번에서 nickname 의 상태를 생성하였고
- 2번에서 입력값과 상태의 동기화를 위하여 이벤트를 생성하였다.
- 3번은 불필요하나, 상태가 실시간으로 동기화된다는것을 보여주고자 추가하였다.
nickname 상태에 입력값이 항상 동기화 되므로 우리는 아래와 같이 submit 이벤트를 처리하여,
유저가 submit 을 발생시켰을 때에 nickname 을 원하는 바 대로 처리할 수 있다.
가령 다음과 같이 말이다.
const ReactForm = () => {
const [nickname, setNickname] = React.useState()
const handleSubmit = event => {
event.preventDefault()
alert(nickname) // 입력받은 nickname 으로 하고싶은 일을 한다.
}
return (
// ...
)
}
handleSubmit 을 통해 입력받은 nickname 값을 원하는 시점에 처리할 수 있다.
이제 반문해보자.
onChange 를 왜 사용하였는가?
nickname 과 input.nickname.value 의 value 를 동기화 하기 위해서이다.
input.nickname.value가 변경되는 시점에 nickname 이 왜 매번 동기화 되어야하는가?
submit 이벤트가 발생하는 시점에 nickname 이 준비되어 있어야 하기 때문이다.
위와 같은 이유 때문에 양방향 바인딩이 필요하다.
위와 같은 양방향 바인딩은 React 의 세계에서는 자주쓰이나, 다음과 같은 비용을 지불해야 할 수 있다.
가령 입력을 받아야하는 필드가 추가되었고, 그에 따라 nickname 과 유사한 phoneNo, age 등 여러 상태들이 생겨났다고 가정해보자.
Form 하위에 TextField 등 서브컴포넌트가 생겨났다고 가정해보자.
ReactForm 는 특정 상태가 업데이트 될때에 해당 상태를 양방향 바인딩하기 위해서 리렌더를 시도할것이다.
리렌더가 필요하다면 React 는 diffing check 를 해가며 변경되어야 하는 요소만 DOM 에 업데이트 시킬 것이다.
성능을 챙기고 싶다면 개발자는 이를 도와주기 위해서 useMemo 등을 이용하여 각 TextField 가
해당 TextField 에 해당하는 상태가 업데이트 될때만 리렌더링 하도록 하는 코드를 작성할 것이다.
위와 같은 작업은 필요한가? Submit 과 같이 사용자가 값을 제출하였을때만 각 필드들의 값이 필요할때에는 필요 없을 수 있다.
우리가 애써 React 의 상태로 관리하려 한 값은 이미 DOM 의 input.nickname.value 에 실시간으로 바인딩되어있다.
그렇다면 이제는 uncontrolled 하게 관리해보자.
UnControlled Form
React 에서 소개하는 비제어 컴포넌트 는 요약해보면 상태를 만들지도 않고 관리하지도 않는것이다. 단지 해당 데이터가 필요할때에 해당 값을 읽어오면 된다.
값이 바인딩된 nickname Form 을 아무런 상태도 없는 순진무구한 상태로 되돌려보자.
const ReactForm = () => {
return (
<form>
<label>
nickname:
<input
type="text"
name="nickname"
/>
</label>
<button type="submit">Join the room</button>
</form>
)}
그리고 useCallback 을 이용하여 submit 이벤트 핸들러를 작성한다.
const ReactForm = () => {
const handleSubmit = React.useCallback(event => {
event.preventDefault()
alert('submit')
}, [])
return (
<form onSubmit={handleSubmit}>
// ...
<form>
)}
그리고 handleSubmit 에서 input 에 접근하여 value 를 가져오면 된다.
현재 공식문서에는 useRef 를 사용하여 input.nickname.value 를 가져오나,
나는 훌륭한 Web API 인 FormData 를 이용하여 value 를 가져올 것이다.
const getFormData = formEl => {
const formData = new FormData(formEl);
const result = {};
for (const [key, value] of formData.entries())
Object.assign(result, { [key]: value });
return result;
};
const ReactForm = () => {
const handleSubmit = React.useCallback(event => {
const { nickname } = getFormData(event.currentTarget)
event.preventDefault()
alert(nickname)
}, [])
return (
<form onSubmit={handleSubmit}>
// ...
<form>
)}
이제 nickname 을 입력 후 submit 을 동작하여보면 alert 에 입력한 nickname 값이 담겨서 출력되는것을 볼 수 있다.
정리
UnControlled Form 은 상태를 양방향 바인딩 하는 방식에 비해서 성능상의 이점과, 작성의 편의를 제공하나 입력 즉시 validation 을 실행하여 잘못된 입력값이 있음을 알려주고자하는 기능을 구현하고자 할때에는 적절 치 않을 수 있다.
하지만 무작정 모든 값을 프레임워크의 상태 값으로 관리하는것보다는 더 나은 선택 일 수 있다는것을 기억하자.
- 상태를 양방향 바인딩할때는, 양방향 바인딩이 꼭 필요한 행위인지 반문하여 보자.
- 양방향 바인딩이 필요없다면 양방향 바인딩을 하지 않으면 된다.
- UnControlled 하게 input 에 기본 값을 바인딩하고자 할때에는
defaultValue를 사용하자. - 복잡한 Form 을 UnControlled 하게 관리하고자 한다면 react-hook-form 모듈을 활용하는것도 좋은 대안이 될 수 있다.