-
[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 방식도 이해하고 있어야할 것이다.
반응형'React' 카테고리의 다른 글
[monorepo] hot reload 적용 :: 마이구미 (0) 2022.09.13 [React] PullToRefresh 구현 :: 마이구미 (0) 2022.06.12 [React] useImperativeHandle :: 마이구미 (0) 2022.04.27 [React] react-router v6 에서 Prompt 구현하기 :: 마이구미 (3) 2022.04.17 [React] 중첩 라우터 언제 사용하는가? :: 마이구미 (0) 2022.03.13