입력 폼의 element 값들이 React를 통해 제어되며, 이러한 폼을 제어 컴포넌트라고 한다.
Element들의 값들이 상태로 정의되므로 다른 UI element 또는 이벤트 핸들러에서 값을 읽거나 수정하기 용이해지는 이점이 있다.
function NameForm {
const [state, setState] = useState('');
function handleChange(event) {
setState({ value: event.target.value });
}
function handleSubmit(event) {
event.preventDefault();
Api.submit({ name: state.value });
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={state.value} onChange={handleChange} />
</label>
<button type="submit">제출</button>
</form>
);
}
제어 컴포넌트를 이용하면 리액트스럽게 구현할 수 있다. 리액트 환경에서 작업하다보면 상태 변화에 따라 ui를 변화시키는 작업들을 많이 해왔다. 따라서 form의 input 값들도 상태로 관리하게되면, 코드를 이해하기 쉬워질 것이라고 생각한다.
이를 통해 유효성 검사 결과를 ui에 반영할 수 있고, 또 버튼을 비활성화에서 활성화 상태로 변경하는 등의 구현을 할 수 있다!
React단에서 값을 관리하지않고, DOM에서 바로 값을 가져오는 방식이다.
비제어 컴포넌트로 구현 시 ref를 통해 값에 접근해야한다. 또한 굳이 필요없으면 오로지 event.target.value
로 input 값에 접근할 수도 있다.
따라서 정말 간단한 요구사항의 경우에는 비제어 컴포넌트만으로 구현 가능하다. 하지만 값을 상태로 관리하게됐을 때, 즉 제어 컴포넌트로 구현할 때는 불필요한 리렌더링 문제가 발생할 것이다.
아래처럼 제출하는 시점에 체크 여부를 판단해 요청을 처리하는 경우, 아주 간단해서 제어없이 구현할 수 있다.
만약 이들을 제어 컴포넌트 방식으로 하면, 체크할 때 마다 불필요한 리렌더링이 발생하게된다. 불필요하다고 하는 이유는 submit 핸들러 외에 체크 상태를 필요로하는 element, 이벤트 핸들러 등이 없기 때문이다.
function onSubmit(event) {
const checked = event.target['marketing'];
const checked = event.target['age'];
Api.submit({ checked });
}
return (
<form onSubmit={onSubmit}>
<label>
<input type='checkbox' name='marketing'>
[선택] 마케팅 활용 및 서비스 관련 정보 수신 동의
</label>
<label>
<input type='checkbox' name='age'>
[필수] 만 14세 이상입니다.
</label>
<button type='submit'>제출</button>
</form>
);
하지만 실제 서비스에서는 일반적으로 ux를 고려해 필수 항목이 모두 체크되면 제출버튼을 활성화한다. 이런 경우 비제어 컴포넌트만으로 구현하기에는 복잡할 수 있다.
이 포스팅의 결론에서와 같이, 둘 중 제어 컴포넌트가 항상 더 좋다고 얘기할 수 없으며, 요구사항에 따라 더 효율적인 방식을 선택하는게 best인 것 같다.
그럼에도 일반적으로 요구사항은 간단하지 않아 제어 컴포넌트를 사용하는 것이 효율적이라고 많이들 얘기하는 것 같다고 생각한다.
회원가입 기능을 구현하다가 문뜩 email, password를 상태로 관리해야하나 싶은 생각이 들었고 비제어적으로 구현해보고자 했다.
export default function EmailInput({ setIsValid }: EmailInputProps) {
const onInput = (event: FormEvent<HTMLInputElement>) => {
const { value } = event.target as HTMLInputElement;
const reg = new RegExp('@');
const res = reg.test(value);
setIsValid((isValid) => ({ ...isValid, email: res }));
};
return (
<Styled.EmailInput
onInput={onInput}
name='email'
placeholder='이메일을 입력하세요'
/>
);
}
onInput이벤트가 일어나면 event.target.vlaue
에 접근해서 값을 가져오고, isValid 상태를 변경해준다.
Input값의 상태를 없애고 깔끔하게 구현할 수 있을 거라고 생각했고, 또 불필요한 리렌더링이 발생하지 않아 괜찮은 아이디어라고 생각했다. 😂 하지만 input 값만 상태로 관리하지않은 것이지, ref를 사용하지 않는 한 상태 관리는 필요하게 된다는 걸 깨달았다.
왜냐하면 Input에 입력이 일어나서 유효성 검사 결과가 변경되면 버튼 ui를 변경해주거나, 에러를 출력하는 요구사항이 있었다. 이들은 상태를 이용해서 쉽게 구현할 수 있는 부분이었기 때문이었다.
이러한 이유들로 isValid 상태를 두는 대신 email과 password를 상태로 관리하도록 개선했다.
여담인데 isValid값을 얻게 되면서 컴포넌트 리렌더링이 발생할 때 마다 유효성 검사가 반복될텐데, 불필요한 비용이 낭비된다. 이 부분은 useMemo를 사용해서 리렌더링이일어나도, 불필요한 유효성 검사가 발생하지 않게 개선할 수 있다.
폼 - React(Old version)
Controlled vs Uncontrolled inputs react