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

yukyung·2021년 5월 26일
4

React

목록 보기
1/3
post-thumbnail

⚪ 제어 컴포넌트

제어 컴포넌트에 대한 공식문서의 설명은 아래와 같다.

제어 컴포넌트는 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.
(중략)
이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.
https://ko.reactjs.org/docs/forms.html#controlled-components

사용자 입력을 기반으로 자신의 state를 관리하고 업데이트 한다,,🤔 어떤 말인지 잘 와닿지 않는다. 한번 코드로 살펴보자.

export default function App() {
  const [input, setInput] = useState("");
  const onChange = (e) => {
    setInput(e.target.value);
  };

  return (
    <div className="App">
      <input onChange={onChange} />
    </div>
  );
}

쉽게 설명하자면, 사용자의 입력을 받는 컴포넌트에 event 객체를 이용해 setState()로 값을 저장하는 방식을 제어 컴포넌트 방식이라 할 수 있겠다. (위에서도 말했다 싶이 React에 의해 값이 제어되므로 제어 컴포넌트라고 부른다.) 제어 컴포넌트는 사용자가 입력한 값과 저장되는 값이 실시간으로 동기화된다.

🔴 비제어 컴포넌트

비제어 컴포넌트는 기존의 바닐라 자바스크립트와 크게 다르지 않은 방식이다. 우리는 바닐라 자바스크립트를 사용할 때 폼을 제출할때 (submit button)을 클릭할 때 요소 내부의 값을 얻어왔다. 비제어 컴포넌트 또한 이와 유사한 방식으로 사용된다.

비제어 컴포넌트 방식을 사용할 땐, 제어 컴포넌트 방식에서 사용한 setState()를 쓰지 않고 ref를 사용해서 값을 얻는다.

export default function App() {
  const inputRef = useRef(); // ref 사용
  const onClick = () => {
    console.log(inputRef.current.value);
  };

  return (
    <div className="App">
      <input ref={inputRef} />
      <button type="submit" onClick={onClick}>
        전송
      </button>
    </div>
  );
}

비제어 컴포넌트는 값이 실시간으로 동기화 되지 않는다. 만약 a와 b라는 컴포넌트가 있을 때, a에 대한 변화를 즉각적으로 b가 영향을 받아야 할때 비제어 컴포넌트는 이런 방식에 대한 대응을 할 수 없다.

제어 컴포넌트의 경우 사용자가 입력을 하는 액션을 취할때마다 리렌더링을 발생시키는 반면, 비제어 컴포넌트는 사용자가 직접 트리거 하기 전까지는 리렌더링을 발생시키지도 않고 값을 동기화 시키지도 않는다.

약간의 보너스: useRef와 리렌더링에 관해

그럼 왜 비제어 컴포넌트를 사용할 땐 useRef를 사용하고, 이러한 useRef는 왜 리렌더링을 발생시키지 않는걸까?

  1. useRef() 는 heap영역에 저장되는 일반적인 자바스크립트 객체이다.
  2. 매번 렌더링할 때 동일한 객체를 제공한다. heap에 저장되어 있기 때문에 어플리케이션이 종료되거나 가비지 컬렉팅될 때 까지, 참조할때마다 같은 메모리 값을 가진다고 할 수 있다.
  3. 값이 변경되어도 리렌더링이 되지 않는다. 같은 메모리 주소를 갖고있기 때문에 자바스크립트의 === 연산이 항상 true 를 반환한다. 즉 변경사항을 감지할 수 없어서 리렌더링을 하지 않는다는 뜻이다.

💬 비제어 컴포넌트와 제어 컴포넌트 - 이미지로 비교하기

제어 컴포넌트

비제어 컴포넌트

사진으로 확인하니 차이점이 와닿는다.

  • 제어 컴포넌트
    제어 컴포넌트의 값은 항상 최신값을 유지한다. 새로운 입력 값이 생길때 마다 상태를 새롭게 갱신한다. 이는 데이터와 UI에서 입력한 값이 항상 동기화됨을 알 수 있다.

  • 비제어 컴포넌트
    필드에서 값을 트리거 해야 값을 얻을 수 있다. 사진에선 [전송] 버튼을 클릭하면 console에 값이 찍힌다. [전송]버튼을 클릭해 트리거 하기 전까지의 값은 변경되지 않는다.

❓ 그럼 제어 컴포넌트는 언제 사용할까

제어 컴포넌트를 사용하기 좋은 방식은 아래 세가지라고 한다.

  • 유효성 검사
  • 유효한 데이터가 없는 경우 전송 버튼의 상태를 disabled로 표시하기
  • 신용카드와 같은 특정 입력 방식 적용하기

하지만 항상 제어 컴포넌트의 방식이 사용하기 좋다는건 아니기 때문에, 이는 개발자가 유연하게 사고해 방식을 선택하는게 맞다고 생각한다.

제어 컴포넌트를 사용할 때 문제점

제어 컴포넌트는 위해서도 언급했다 싶이 UI의 입력한 데이터 상태와 저장한 데이터의 상태가 항상 일치하는 것을 알 수 있다. 그러나 이 말을 다시 생각하자면, 사용자가 입력하는 모든 데이터가 동기화 된다 라는 의미가 된다.
예를들자면, input태그 안에 "안녕하세요"라는 값을 입력하나고 하면,

ㅇ 
아 
안 
안ㄴ 
안녀 
안녕 
안녕ㅎ 
안녕하 
안녕핫 
안녕하 
안녕하세 
안녕하셍 
안녕하세 
안녕하세요 

이런식으로 불필요한 단어 입력시에도 값이 갱신되어 버린다. 이는 불필요한 리렌더링, 불필요한 api요청으로 인한 자원 낭비 문제로도 연결 될 수 있다.

이러한 불필요한 방법을 막기 위해선 스로틀링이나 디바운싱 (throttle&debounce)을 사용할 수 있다.

쓰로틀링: 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
디바운싱: 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa

자세한 예시는 위의 링크를 참고하는 걸 추천한다. lodash에서도 해당 기능을 제공하므로 필요할 경우 사용하는 것도 좋을 것 같다. (직접 사용해본 경험은 없으므로 lodash에 대한 자세한 예시는 검색을 추천한다..)

하지만 일반적으로 모든 form 요소에서 상태의 동기화가 필요한 건 아니고, form 요소가 증가할 수록 모든 컴포넌트에 쓰로틀링이나 디바운싱을 걸기는 힘들다. 만약 값이 트리거 된 이후에만 갱신이 되도 문제가 없다면, ref를 사용하는 방식이 불필요한 렌더링을 방지하는데 더욱 도움이 될 수 있다. 이러한 비제어 컴포넌트를 사용해 렌더링을 최적화 하는 라이브러리가 react-hook-form 이다.

제어 컴포넌트는 되고 비제어 컴포넌트는 안되는 것들

기능제어 컴포넌트비제어 컴포넌트
일회성 정보 검색 (예: 제출)OO
제출 시 값 검증OO
실시간으로 필드값의 유효성 검사OX
조건부로 제출 버튼 비활성화 (disabled)OX
실시간으로 입력 형식 적용하기 (숫자만 가능하게 등)OX
동적 입력OX
  • 즉각적으로, 실시간으로 값에 대한 피드백이 필요하다 > 제어 컴포넌트 사용
  • 즉각적인 피드백이 불필요하고 제출시에만 값이 필요하다, 불필요한 렌더링과 값 동기화가 싫다 > 비제어 컴포넌트 사용

으로 구분지어 사용할 수 있겠다. 위에서도 언급했지만 특정 방법이 옳고 그르다고 하기엔 각각의 용도가 있기 때문에 상황에 맞춰 쓰는게 가장 무난한 방법인듯 싶다.(라고 나는 생각한다.)

🔗 Refer

https://whal.eu/l/B7G1J1eE
https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/

혹시라도 글에 잘못된 내용이 있다면 댓글로 알려주세요!

profile
◟( ˘ ³˘)◞

1개의 댓글

comment-user-thumbnail
2022년 1월 11일

좋은글 감사합니다

답글 달기