[React] 제어 컴포넌트와 비제어 컴포넌트

hyeondoonge·2023년 6월 28일
1

제어 컴포넌트(Controlled Component)

입력 폼의 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에 반영할 수 있고, 또 버튼을 비활성화에서 활성화 상태로 변경하는 등의 구현을 할 수 있다!

비제어 컴포넌트(Controlled Component)

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

0개의 댓글