얼마 전 벨로그에서 이 글(useState vs useRef)을 읽었다.
내가 처음 이 글을 보았을 때에는 내용이 추가되기 전이었는데 나랑 같은 고민을 하고 있던 것이 꽤 흥미로웠다.
보통 input 태그를 사용하고 여기 입력된 값을 가져오기 위해 다음과 같이 작성한다.
import { useState } from 'react';
function App() {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
export default App
사용자가 입력창에 입력할 때마다 setValue
를 통해 value의 값이 바뀌게 되고 따라서 매 입력마다 리렌더링이 일어나게 된다.
여기서 나는 사용자가 입력한 값의 결과만 알고 싶은데 이 중간 과정에서 리렌더링이 많이 일어나는 것이 불필요한 게 아닐까? 하고 생각했었다.
사실 이 문제에 대한 답은 이미 React 공식문서 내에 있었다.
공식문서를 제대로 읽지 않은 것을 반성하면서 한 번 짚고 넘어가고자 한다.
HTML에서 <input>
, <textarea>
, <select>
와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트한다.
import { useState } from 'react';
function App() {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
export default App
그러니까, 이 코드같이 사용한다.
React의 State를 신뢰 가능한 단일 출처로 만들어 사용하면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어한다.
즉, 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트(Controlled Component)라고 한다.
제어 컴포넌트는 데이터를 변경할 수 있는 모든 방법에 대해 이벤트 핸들러를 작성하고 React 컴포넌트를 통해 모든 입력 상태를 연결해야 하기 때문에 이 방법 대신 ref를 사용해 DOM에서 폼 값을 가져올 수 있다.
import { useRef } from 'react';
function App() {
const ref = useRef(null);
return <input ref={ref} />;
}
export default App
비제어 컴포넌트는 DOM에 신뢰 가능한 출처를 유지하기때문에 React와 non-React 코드를 통합하는 것이 쉬울 수 있다.
대부분의 경우 폼을 구현하는데에는 제어 컴포넌트를 사용하는 것이 좋다.
제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어지고 비제어 컴포넌트에서 폼 데이터는 DOM 자체에서 다루어지기 때문이다.
값을 다루는 방식을 비교해보자면 제어 컴포넌트는 input과 같은 폼 엘리먼트의 현재 값을 React의 state에 저장하고 비제어 컴포넌트는 ref로 DOM 엘리먼트를 저장하고 DOM 엘리먼트의 속성에 접근해 현재 값을 가져온다.
그러니까, 제어 컴포넌트를 사용해 input과 같은 폼 엘리먼트의 현재 값을 React의 state에 저장함으로써 React가 이 값을 추적하는 것을 용이하게 하며, 항상 입력의 최신 값을 가진다는 것을 보장할 필요가 있기 때문에 제어 컴포넌트를 사용하는 것이 좋다.
React 공식문서 - 제어 컴포넌트
React 공식문서 - 비제어 컴포넌트
Controlled and uncontrolled form inputs in React don't have to be complicated