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
같은 프레임워크 등이 등장하기 전에는 JavaScript
로 DOM
을 직접 조작하여 우리가 원하는 기능을 만들었습니다.
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
이런 식으로 모든 값을 받아오게 되면서 계속 계속 렌더링이 될겁니다.
이를 방지하기 위해서는 Throttle
과 Debounce
를 사용하면 됩니다.
쓰로틀
Throttle
: 연속된 이벤트를 호출하지 않고 일정 시간 간격으로 이벤트가 최대한 한번만 호출하게 합니다. ex) 무한 스크롤 UI구현 같은 이벤트에 사용합니다. 디바운싱Debounce
: 연속된 이벤트를 호출하지 않고 일정 시간이 경과한 이후 한번만 호출 합니다. ex) 입력 필드 자동완성, 버튼 중복 클릭 방지 처리에 유용합니다.
비제어 컴포넌트
비제어 컴포넌트의 단점은 제어 컴포넌트의 장점의 반대라고 생각하면 됩니다. 입력된 데이터를 상태값과 동기화 되지 않기 때문에, 유효성 검사 같은 기능은 구현할 수 없겠죠.
입력 받는 값을 알 수 없으니, 테스트나 디버깅에도 어렵습니다.
제어 컴포넌트와 비제어 컴포넌트를 적절히 사용해서, 단점들을 보안해서 사용하면 될거 같아요.
마지막으로 표를 보면 간단하게 정리를 했습니다.
특징 | 비제어 | 제어 |
---|---|---|
일회성 값 검색(예: 제출 시) | ✅ | ✅ |
제출 시 유효성 검사 | ✅ | ✅ |
즉각적인 필드 유효성 검사 | ❌ | ✅ |
조건부 제출 버튼 비활성화 | ❌ | ✅ |
입력 형식 적용 | ❌ | ✅ |
하나의 데이터에 대한 여러 입력 | ❌ | ✅ |
동적 입력 | ❌ | ✅ |
그리고 무조건 제어, 비제어 나누지 않고, 언제든지 비제어 컴포넌트를 제어 컴포넌트로 마이그레이션 가능합니다.
뭐든지 상황에 맞게 기능을 구현하는게 정답인거 같네요.
회고
처음 제어, 비제어 컴포넌트라는 용어를 들었을 때 아무생각도 들지 않았고 단순히 state
사용과 ref
사용 이정도로 끝냈었다.
생각해 보면, 사용자의 정보를 얻고 유효성 검사를 했을 때 무조건 state
를 사용해서 관리를 했고, 무한 스크롤 같은 UI를 구현할 때는 ref
로 구현을 했었다.
무의식 적으로 이렇게 하는게 편하고, 그렇게 했으니까? 이런 생각들로 구현을 했었는데,
이제는 좀 더 내가 왜 이렇게 구현했는지, 의미를 알 수 있고, <Input>
으로 입력된 데이터를 처리할 때, 어떤 방식이 좀 더 효율적인지 알게 되었다.
아직도 부족하지만, 나의 코드가 어떤 의미이고, 왜 이렇게 했는지 조금은 설명이 더 할 수 있을 것 같다.