Controlled 컴포넌트와 UnControlled 컴포넌트 차이점

2023년 4월 12일

Controlled Component

HTML에서 <input>, <textarea>, <select>와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다. 우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있습니다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.

출처 : https://ko.legacy.reactjs.org/docs/forms.html#controlled-components

공식문서에서 위와 같은 설명을 해주고 있습니다. 쉽게 말해 입력폼에 state를 통해 관리를 하고 제어하는 것이 Controlled Component입니다.

아마도 흔히 React로 로그인, 회원가입, 게시글, 댓글 등 입력할 수 있는 컴포넌트를 구현할 때, <input>를 활용해서 useState를 통해 값을 입력받고, 유효성 검사, 서버에 전달 등 기능을 구현합니다.

UnControlled Component

비제어 컴포넌트란 무엇인가?

자료를 찾아보니 전통적인 방식이라고 많이 표현하는 것을 보았는데, React같은 라이브러리나 Next.js같은 프레임워크 등이 등장하기 전에는 JavaScriptDOM을 직접 조작하여 우리가 원하는 기능을 만들었습니다.

React에서는 ref를 이용해서 직접 DOM을 조작할 수 있습니다.

쉽게 말해 React에서 ref로 컴포넌트를 조작하면 비제어 컴포넌트가 되는 것이죠.

제어 컴포넌트와 비제어 컴포넌트의 차이점

그렇다면 제어 컴포넌트와 비제어 컴포넌트의 차이점은 무엇일까?

제어가 가능하고 제어가 불가능 하다는 의미가 무엇일까?

위에서 말했던 제어 컴포넌트는 state 상태값을 가지고 컴포넌트를 제어합니다.

React에서 렌더링이 되는 조건을 보면, 부모 요소가 변경될때, 상태값이 변경될 때 입니다.

State가 변경되면서 해당 컴포넌트의 데이터와 State의 데이터가 동기화 되면서, 웹에서도 사용자가 어떤 행동을 하는지 알 수 있습니다.

반대로 ref를 통해 직접 DOM을 조작한다면, 웹은 사용자가 어떤 동작을 하는지 알 수 없습니다. 만약 ID를 입력 받는 <Input> 컴포넌트를 사용해서 <Button을 누르면 데이터가 전송된다고 했을 때, 버튼을 누르기 전까지 웹에서는 사용자가 어떤 값을 입력했는지 알 수 없는 것이죠.

그렇기 때문에 비제어 컴포넌트라고 합니다.

제어 컴포넌트와 비제어 컴포넌트의 장단점

장점

제어 컴포넌트 제어 컴포넌트는 주로 유효성 검사를 할 때 사용합니다. 우리가 이메일, ID, 비밀번호 등 사용자가 입력한 값들을 즉각적으로 판단하고 사용자에게 보여주려면, 사용자가 입력한 값과 State값을 동기화 시켜 바로바로 검사를 해주는 것이 좋겠죠.

비제어 컴포넌트 비제어 컴포넌트는 제어 컴포넌트 처럼 입력한 값을 즉각적으로 판단하지 않고, 필요할 때 입력값을 추출할 경우에 사용합니다. ID나 비밀번호 등 중요한 정보들은 즉각적으로 유효성검사를 해서 사용자에게 바로바로 피드백을 줘야하지만, 만약 자기소개같은 정보는 굳이 데이터와 상태를 동기화 시켜 입력받을 필요는 없기 때문입니다.

단점

제어 컴포넌트 제어 컴포넌트의 단점은 단어 하나하나 입력할 때 마다 값이 갱신되어 불필요하게 렌더링이 되어 성능 측면에 좋지 않고, 만약 바로바로 API호출이 된다면, 자원 낭비가 되겠죠.

const Test = () => {
  const [id, setId] = useState("")
  console.log(id)
  return (
    <div>
      </input onChange={setId}>
    </div>
  )
}

이렇게 <Input>으로 waterHumanB를 입력한다고 할 때 console.log를 보면

w
wa
wat
wate
water
waterH
waterHu
waterHum
waterHuma
waterHuman
waterHumanB

이런 식으로 모든 값을 받아오게 되면서 계속 계속 렌더링이 될겁니다.

이를 방지하기 위해서는 ThrottleDebounce를 사용하면 됩니다.

쓰로틀Throttle : 연속된 이벤트를 호출하지 않고 일정 시간 간격으로 이벤트가 최대한 한번만 호출하게 합니다. ex) 무한 스크롤 UI구현 같은 이벤트에 사용합니다. 디바운싱Debounce : 연속된 이벤트를 호출하지 않고 일정 시간이 경과한 이후 한번만 호출 합니다. ex) 입력 필드 자동완성, 버튼 중복 클릭 방지 처리에 유용합니다.

비제어 컴포넌트

비제어 컴포넌트의 단점은 제어 컴포넌트의 장점의 반대라고 생각하면 됩니다. 입력된 데이터를 상태값과 동기화 되지 않기 때문에, 유효성 검사 같은 기능은 구현할 수 없겠죠.

입력 받는 값을 알 수 없으니, 테스트나 디버깅에도 어렵습니다.

제어 컴포넌트와 비제어 컴포넌트를 적절히 사용해서, 단점들을 보안해서 사용하면 될거 같아요.

마지막으로 표를 보면 간단하게 정리를 했습니다.

특징비제어제어
일회성 값 검색(예: 제출 시)
제출 시 유효성 검사
즉각적인 필드 유효성 검사
조건부 제출 버튼 비활성화
입력 형식 적용
하나의 데이터에 대한 여러 입력
동적 입력

그리고 무조건 제어, 비제어 나누지 않고, 언제든지 비제어 컴포넌트를 제어 컴포넌트로 마이그레이션 가능합니다.

뭐든지 상황에 맞게 기능을 구현하는게 정답인거 같네요.

회고

처음 제어, 비제어 컴포넌트라는 용어를 들었을 때 아무생각도 들지 않았고 단순히 state사용과 ref사용 이정도로 끝냈었다.

생각해 보면, 사용자의 정보를 얻고 유효성 검사를 했을 때 무조건 state를 사용해서 관리를 했고, 무한 스크롤 같은 UI를 구현할 때는 ref로 구현을 했었다.

무의식 적으로 이렇게 하는게 편하고, 그렇게 했으니까? 이런 생각들로 구현을 했었는데,

이제는 좀 더 내가 왜 이렇게 구현했는지, 의미를 알 수 있고, <Input>으로 입력된 데이터를 처리할 때, 어떤 방식이 좀 더 효율적인지 알게 되었다.

아직도 부족하지만, 나의 코드가 어떤 의미이고, 왜 이렇게 했는지 조금은 설명이 더 할 수 있을 것 같다.

참고 자료