React 개발을 하다보면 form과 input 등 입력 요소 태그를 사용할 일이 빈번히 일어납니다. 현재 진행하고 있는 프로젝트에서도 매우 많은 form을 사용하고 있습니다.
해당 요소에 접근해 값을 변경하면 state 변경에 의해 리렌더링이 일어나는 경우를 자주 확인할 수 있는데 이런 form 요소 컴포넌트에 대해 밀접한 관련이 있는 제어 컴포넌트, 비제어 컴포넌트를 알아보려 합니다.
HTML에서 주로 사용되는 Form 관련 tag들은 input
, textarea
, select
등이 있습니다. 사용자가 특정 값을 입력하거나 선택해서 변경할 수 있는 태그들입니다.
const form = document.querySelector("form");
const onSubmit = (e) => {
e.preventDefault();
console.log(e.target.elements["input"].value);
console.log(e.target.elements["textarea"].value);
console.log(e.target.elements["select"].value);
};
위 코드를 보면 submit event를 감지해서 각각의 태그에 value attribute
를 통해서 값을 가져올 수 있습니다.
여기서 value attribute
는 사용자가 입력한 값을 가져올 수 있는데 조금 다르게 해석해보면 사용자가 입력한 값이 value attribute에 저장된다고 볼 수 있습니다.
value attribute는 DOM에 존재하기 때문에 사용자가 입력한 값은 DOM에 저장
된다고 볼 수 있습니다.
신뢰 가능한 단일 출처
는 간단하게 하나의 상태는 한 곳에만 있어야 함을 의미합니다. input 태그에서 onChange로 state 값을 변경하면 입력하는 값들이 실시간으로 콘솔에 찍히는걸 확인할 수 있습니다.
그렇기에 value attribute는 항상 최신화된 값을 가지고 있기 때문에 해당 값을 가지고 다른 작업을 할 수 있는 다시 말하면 신뢰가 가능한 상태입니다.
또한 input tag의 value attribute는 유일무이한 하나의 값이기에 여기서 value attribute는 신뢰 가능한 단일 출처임을 알 수 있습니다.
신뢰 가능한 단일 출처가 중요한 이유가 무엇인지 확인해보겠습니다.
const form = document.querySelector("form");
let inputValue = "";
let textareaValue = "";
let selectValue = "";
const onSubmit = (e) => {
e.preventDefault();
inputValue = e.target.elements["input"].value;
textareaValue = e.target.elements["textarea"].value;
selectValue = e.target.elements["select"].value;
};
위 코드는 각 태그의 value attribute를 let 변수를 활용해 할당해준 코드입니다.
이렇게 되면 input 태그의 상태는 value attribute
와 let 변수
두 개의 출처로 나뉘게 됩니다.
그러면 둘 중 하나의 값이 변경되더라도 자동으로 동기화가 되지 않기 때문에 값의 차이를 가지게 되고 이러면 항상 최신화된 값으로 다른 작업을 수행할 수 없게 됩니다. 신뢰 가능한 단일 출처가 중요하게 여겨지는 이유는 이런 문제점을 방지하기 위해서입니다.
React에서 해당 문제점을 해결하기 위해 도입하게 된 개념이 제어 컴포넌트
입니다.
매번 value attribute를 가져오기에는 번거롭고 변수에 할당해서 사용하기에는 위에서 언급한 신뢰 가능한 단일 출처의 문제점이 존재했기에 제어 컴포넌트를 활용해서 문제점을 해결했습니다.
HTML에서
input
,textarea
,select
와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며setState
에 의해 업데이트됩니다.React state를 "신뢰 가능한 단일 출처"로 만들어 두 요소를 결합할 수 있습니다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 `제어 컴포넌트(controlled component)라고 합니다.
https://ko.legacy.reactjs.org/docs/forms.html#controlled-components
제어 컴포넌트
는 공식 문서에 위와 같이 명시되어 있습니다. 저 글만 읽어보았을때는 명확히 이해하기 힘든 개념일 수 있습니다.
사용자의 입력을 받는 컴포넌트에 event 객체를 이용해 setState
로 값을 저장하는 방식을 제어 컴포넌트라고 지칭합니다. React에 의해 값이 제어되기 때문에 제어 컴포넌트라고 지칭하며 사용자가 입력한 값과 저장되는 값이 실시간으로 동기화되는 특징을 가지고 있습니다.
다음 코드를 보면서 설명을 보시면 이해를 훨씬 쉽게 할 수 있습니다.
import { useState } from "react"
const App = () => {
const [name, setName] = useState("");
return (
<form>
<label>name : </label>
<input
value={name}
onChange={(e) => {
setName(e.target.value);
}}
placeholder="이름을 입력하세요"
/>
</form>
)
}
코드를 보면 name state
를 onChange 이벤트가 발생할 때마다 setState
를 해주고
그 name을 value attribute
에 할당을 해주고 있습니다.
value attribute와 state를 결합해서 name state를 신뢰 가능한 단일 출처
로 만들어주는겁니다.
이렇게 form에 사용자 입력값을 React가 제어하기 때문에 위 코드는 제어 컴포넌트의 대표적인 예시 코드가 될 수 있습니다.
대부분 경우에서 폼을 구현하는데에는 제어 컴포넌트를 사용하는 것이 좋습니다. 제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어집니다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어집니다.
모든 state 업데이트에 대한 이벤트 핸들러를 작성하는 대신 비제어 컴포넌트를 만들려면 ref를 사용하여 DOM에서 폼 값을 가져올 수 있습니다.
https://ko.legacy.reactjs.org/docs/uncontrolled-components.html
비제어 컴포넌트
는 제어 컴포넌트와 달리 state에 의해 값이 관리 및 변경되지 않는 컴포넌트입니다. React가 아닌 기존 자바스크립트에서 사용하던 방식이랑 동일합니다.
자바스크립트에서 폼을 제출할때 submit 버튼을 사용해서 태그 요소 내부의 값을 가져왔습니다. 비제어 컴포넌트 또한 이와 유사한 방식으로 사용됩니다.
제어 컴포넌트에서 사용했던 setState
방식으로 값을 가져오는게 아닌 ref
를 사용해서 값을 가져옵니다.
import { useRef } from "react";
const App = () => {
const nameRef = useRef(null);
const submitName = (e) => {
e.preventDefault();
console.log(nameRef.current.value);
};
return (
<form onSubmit={submitName}>
<label>name : </label>
<input ref={nameRef} placeholder="이름을 입력하세요" />
</form>
)
}
위 코드는 비제어 컴포넌트
의 대표적인 예시 코드입니다.
코드를 보면 알다시피 state가 아닌 ref를 통해 input의 값을 가져옵니다. input ref attribute에 name ref를 직접적으로 할당해주고 있습니다.
ref 같은 경우 자바스크립트 객체이기에 current 프로퍼티를 통해 input 태그에 접근할 수 있고 ref는 변화가 이루어져도 리렌더링이 발생하지 않는 특징을 가지고 있습니다.
해당 코드를 보면 React가 Form에 관여하는 부분이 전혀 없다는걸 알 수 있습니다. 리액트가 form에 입력값을 제어하지 않기 때문에 비제어 컴포넌트
가 됩니다. 과거에 많이 사용했던 HTML과 JavaScript를 활용한 form 방식입니다.
하지만 위에서 언급한 신뢰 가능한 단일 출처를 가지는 컴포넌트는 맞습니다. ref를 통해 input attribute에 접근해 값을 가져오기 때문입니다.
다만 위 코드의 단점이자 비제어 컴포넌트의 단점은 값이 실시간으로 동기화되지 않습니다. React는 state 변화가 일어나면 리렌더링을 발생시키고 값을 동기화시켜주는데 해당 작업이 이루어지지 않기 때문입니다.
만약 A와 B 두 개의 컴포넌트가 있을 때 A에 대한 변화를 B가 실시간으로 동기화시켜서 사용자에게 보여줘야할 경우 비제어 컴포넌트는 이런 방식에 대응하지 않기 때문에 의도치 않은 결과값을 사용자에게 제공할 수 있습니다.
제어 컴포넌트 | 비제어 컴포넌트 | |
---|---|---|
데이터 관리 주체 | React | DOM |
데이터 갱신 시점 | 사용자가 값을 입력할 때마다 갱신 | 특정 시점에 DOM을 통해 갱신 |
리렌더링 여부 | 값을 입력할 때마다 발생 | 값을 입력할 때는 발생하지 않음 |
제어 컴포넌트
는 항상 최신값을 유지하며 새로운 입력 값이 생길때마다 값이 새로 갱신되고 동기화됩니다.
비제어 컴포넌트
는 새로운 입력 값이 생겨도 값이 갱신되지 않으며 submit등 특정 트리거 이벤트를 통해 갱신을 해줘야 합니다.
제어 컴포넌트 | 비제어 컴포넌트 | |
---|---|---|
Submit과 같은 일회성 정보 검색 | O | O |
Submit시 유효성 검사 | O | O |
조건에 따른 Submit 버튼 (비)활성화 | O | X |
즉각적인 필드 유효성 검사 | O | X |
특정 입력 형식 적용 | O | X |
동적 입력 | O | X |
간단하게 생각하면 사용자와의 상호작용에 있어서 실시간으로 변화가 이루어지는 부분들이 제어 컴포넌트는 가능하지만 비제어 컴포넌트는 불가능합니다.
이렇게만 보면 제어 컴포넌트만 사용하면 되는거 아닌가?라고 생각하실 수 있겠지만 제어 컴포넌트 또한 단점이 존재합니다.
단점들을 확인해보면 크게 문제되지 않는 단점이지 않을까 싶지만 필자 같은 경우는 첫 번째 항목인 리렌더링 관련 부분이 크게 거슬렸습니다.
input에서 글자를 입력하고 지울 때마다 리렌더링이 발생한다는건 불필요한 메모리가 사용된다는 부분과 동일합니다.
그렇기에 즉각적으로 반응이 필요한 값이 아니라면 오히려 비제어 컴포넌트처럼 submit할때만 데이터를 변화시켜주는게 더 효율적인 경우도 있다고 생각을 했습니다.
이러한 제어 컴포넌트와 비제어 컴포넌트의 문제점을 최소화하고 렌더링 최적화를 해주는 대표적인 라이브러리도 react-hook-form이 존재합니다.
Form과 직접적인 연관이 있는 제어 컴포넌트와 비제어 컴포넌트에 대해 알아보았습니다.
정리해보면 결국 하나만 사용하는 방향 보다는
"실시간으로 변화에 대한 대응이 필요한 작업에는 제어 컴포넌트를"
"실시간 대응 보다는 submit등 특정 이벤트에만 값이 필요한 작업에는 비제어 컴포넌트를"
이렇게 정리할 수 있을 거 같습니다.
필요한 상황에 맞춰서 뭐가 더 효율적일지를 설계해보고 사용하는게 최적의 방향성이라고 생각합니다.
다음에는 잠깐 언급한 제어, 비제어 컴포넌트에 대해 form에서 최적화가 이루어진 react-hook-form
라이브러리에 대해서 알아보겠습니다.
감사합니다.
제어 컴포넌트 리액트 공식 문서
https://ko.legacy.reactjs.org/docs/forms.html#controlled-components
비제어 컴포넌트 리액트 공식 문서
https://ko.legacy.reactjs.org/docs/uncontrolled-components.html
[10분 테코톡] 세인의 제어 컴포넌트와 비제어 컴포넌트
https://www.youtube.com/watch?v=PBgQKK6nelo
[React] 제어 컴포넌트 VS 비제어 컴포넌트
https://itprogramming119.tistory.com/entry/React-%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-VS-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8
React: 제어 컴포넌트와 비제어 컴포넌트의 차이점
https://velog.io/@yukyung/React-%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0