• [React] Controlled vs Uncontrolled :: 마이구미
    React 2022. 6. 1. 12:17
    반응형
    이 글은 React 의 contorlled component, uncontrolled component 를 다룬다.
    아래 예제 코드를 확인하고, 글 내용을 이해와 리마인드에 도움이 될 것이다.
    예제 - https://codesandbox.io/s/controlled-vs-uncontrolled-10rgwj

     

     

    어디서 접할 수 있는가?

    React 를 사용하면 Controlled Components, Uncontrolled Components 와 같은 용어를 들어본 적이 있을 것이다.

    이 용어들은 Form 요소와 관련이 있다.

    이 용어들은 공식 문서에서도 설명되어 있다.

    그리고 UI 프레임 워크에서도 uncontrolled, controlled 예제를 따로 명시해서 제공하기도 한다.

     

    꼭 알아야하는가?

    이것들을 이해하고 있지 않더라도, React 사용에 치명적인 문제는 없다.

    하지만 관련 내용 및 이슈를 맞이할 확률이 낮다고 볼 수는 없다.

    하나의 예로는 다음과 같은 경고들을 맞이할 수도 있고, Form 관련 작업에서 혼란을 초래할 수 있다.

     

    Warning: A component is changing an uncontrolled input to be controlled...
    Warning: Input contains an input of type text with both value and defaultValue props...

     

    (경고 메시지들은 하단에서 하나의 챕터로 다룬다.)

     

    Controlled? Uncontrolled?

    우선 Form 요소에 대해 살펴보자.

    우리가 알고 있는 Form 요소들은 <input>, <textarea>, <select> 가 존재한다.

     

    <form>
      <input type="text" name="id" />
    </form>

     

    일반적으로 form 요소들은 위와 같이 단순하게 코드를 작성하여 사용할 수 있다.

    그러면 입력창이 브라우저에 노출되고 사용자 입력에 따라 값이 업데이트 된다.

    정리하면 HTML input 스스로 State 를 유지하고 사용자 입력에 따라 State 가 업데이트된다는 것이다.

     

    위 코드를 React 에서 사용하면 어떻게 되는가?

     

    const ReactComponent = () => {
      return (
        <form>
          <input type="text" name="id" />
        </form>
      )
    }

     

    똑같이 잘 동작한다.

    하지만 우리가 알다시피 React 에서는 컴포넌트 자체적으로 State 를 유지보수하고 필요하다면 자식 컴포넌트에 props 로 내려준다.

     

    const Parent = () => {
      const [value, setValue] = useState('');
     
      const update = () => { setValue('new') } 
     
      return (
        <div>
          {value}
          <Child value={value} />
        </div>
      )
    }

     

    그렇다면 React 컴포넌트에서 HTML form 요소들을 사용한다는 의미는 이렇게 설명할 수 있을 것이다.

     

    React 컴포넌트에서도 State 를 가지고 있고, HTML form 요소에서도 State 를 가지고 있는 상황이다.

     

    이는 "single source of truth" 원칙을 위배하게 된다.

    이를 위해 State 를 React State 하나로 사용함으로써, 두 가지 State 가 결합하여 위배된 원칙을 해소하는 것이다.

    이러한 방식이 React 에서는 "controlled component" 라고 부르는 것이다.

    반대로 "uncontrolled componentns" 는 React State 가 아닌 HTML State 를 사용하는 것이라고 보면 된다.

     

    두 가지 방식을 코드로 표현하면 다음과 같다.

     

     

    Controlled 방식

    controlled 방식은 React State(useState) 로 input 의 value 가 관리된다.

    input 요소의 onChange 콜백함수에서 setState 를 통해 State 가 업데이트된다.

    업데이트된 State 는 랜더링을 통해 input 의 value 속성에 들어가게된다.

     

    const ControlledExample = () => {
      const [first, setFirst] = useState("first");
      const [second, setSecond] = useState("second");
    
      const handleChangeFirst = (e: React.ChangeEvent<HTMLInputElement>) => {
        const val = e.target.value;
        setFirst(val);
      };
    
      const handleChangeSecond = (e: React.ChangeEvent<HTMLInputElement>) => {
        const val = e.target.value;
        setSecond(val);
      };
    
      const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        console.log("first 👉️", first);
        console.log("second 👉️", second);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input type={"text"} value={first} onChange={handleChangeFirst} />
          <input type={"text"} value={second} onChange={handleChangeSecond} />
          <button type="submit">Submit</button>
        </form>
      );
    };
    
    export default ControlledExample;

     

    이 방식은 React State 로 관리되기 때문에 우리가 알고 있는 흔한 그냥 React 방식이다.

    State 를 컴포넌트가 가지고 있기에, 실시간 데이터 체크 등처럼 많은 것이 열려있다.

    그래서 React 스럽다라고 표현할 수 있다.

     

    단점을 뽑자면, 각각 input 에 대한 useState 와 Handler 를 선언해줘야하는 것이다.
    즉, input 이 n 개 라면 각 useState 와 Handler는 n 개로 늘어날 수 있다.

    그리고 리랜더링이 State 가 업데이트할 때마다 일어나게 된다.

     

    Uncontrolled 방식

    uncontrolled 방식은 React State(useState) 가 아닌 HTML State 로 관리된다.

    그 결과 controlled 방식처럼 State 와 State 를 업데이트하기 위한 작업이 필요없게 된다.

    (useRef 를 사용할 수 있도 있음. 전체 예제 코드 참고)

     

    const UncontrolledExample = () => {
      const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        console.log("---Uncontrolled submit---");
        console.log("first 👉️", e.target.first.value);
        console.log("second 👉️", e.target.second.value);
    
        e.target.reset();
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input name={"first"} type={"text"} defaultValue="first" />
          <input name={"second"} type={"text"} defaultValue="second" />
          <button type="submit">Submit</button>
        </form>
      );
    };

     

    이 방식은 우리가 알고 있는 전통적인 HTML 방식이다.

    코드만 보면 controlled 방식보다 간단한 코드를 볼 수 있다.

    input 이 더 늘어나더라도 코드는 controlled 처럼 늘어나지 않는다.

    리랜더링 측면에서도 React State 를 사용하지 않기 때문에 리랜더링 영향이 없게 된다.

     

    Submit 시점에서만 값을 가져오면 되는 등 단순한 시나리오라면 이러한 방식도 나쁘지 않아 보인다.

    하지만 controlled 방식과는 달리 State 를 직접 가지고 있지 않기에, 기능적으로 많은 것이 닫혀있다.

     

    Warning 은 언제 뜨는가?

    React 자체에서 controlled, uncontrolled 를 분류했다는 것은 다른 의미로 하나의 방식을 추구해야한다는 것이다.

    즉, 언급되었던 Waring 문구 같은 것은 controlled, uncontrolled 를 혼합해서 사용했다고 예측할 때 발생한다.

     

    Warning: Input contains an input of type text with both value and defaultValue props...

    => input[type='text'] 에서 value 와 defaultValue 가 모두 포함되었어.

     

    위 경고는 다음과 같은 코드에서 발생된다. 

     

    <input value={value} onChange={handleChange} defaultValue={'initial'} />

     

    defaultValue 는 uncontrolled 방식에서 초기 값을 위해 사용된다.

    controlled 에서는 초기 값은 어떻게 되는가?

    React State 로 관리되기에 초기 값이나 업데이트된 값이나 모두 하나의 State 로 관리된다.

    결과적으로 defaultValue 는 controlled 방식에서 무의미한 속성이고, uncontrolled 를 위한 속성이다.

     

    Warning: A component is changing an uncontrolled input to be controlled...

    => 컴포넌트가 uncontrolled input 을 controlled input 으로 변경하려고 해.

     

    위 경고는 다음과 같은 코드에서 발생된다. 

     

    const Component = () => {
      const [value, setValue] = useState()
    
      useEffect(() => {
        setValue('')
      }, [])
    
      const handleChange = (e) => { setValue(e.target.value) }
      
      return (
        <input type="text" value={value} onChange={handleChange} />
      )
    }

     

     

    우선 input 의 value 속성을 사용한다는 것은 controlled 방식을 의미한다.

    useState() 선언 당시 아무것도 할당하지 않아서 value 의 초기값은 undefined 이다.

    input 에 undefined 가 전달된다.

     

    사실 우리는 controlled 방식을 원했지만, React 입장에서는 undefined 를 보고 input 에 대해서 uncontrolled 라고 인지한다.

    그 후 useEffect 로 인해 마운트 시점에 State 는 undefined 가 아닌 빈값으로 할당되어 다시 input 에 전달한다.

    그러면 React 에서는 uncontrolled 라고 인식하고 있었는데 controlled 방식으로 변경하려는 시도를 감지해서 위와 같은 경고 메시지가 뜨는 것이다.

     

    위에서 언급한대로 대부분 React 스러운 controlled 방식으로 사용하게 될 것이다.

    하지만 uncontrolled 방식도 이해하고 있어야할 것이다.

    반응형

    댓글

Designed by Tistory.