React 폼 관리의 영원한 논쟁
Wenhao Wang
Dev Intern · Leapcell

소개
프론트엔드 개발의 활기차고 끊임없이 진화하는 환경에서 사용자 입력, 특히 폼을 통한 처리는 거의 모든 대화형 애플리케이션의 초석입니다. React는 강력한 컴포넌트 기반 아키텍처를 통해 폼 데이터를 관리하는 여러 패러다임을 제공하며, 각 패러다임은 고유한 장점과 이상적인 사용 사례를 가지고 있습니다. React 개발자들 사이에서 자주 논의되고 때로는 논쟁이 되는 주제 중 하나는 useState를 사용하여 제어 컴포넌트를 관리하는 것과 useRef를 사용하여 비제어 컴포넌트를 관리하는 것 사이의 선택입니다. 이것은 단순히 선호도의 문제가 아닙니다. 컴포넌트의 리렌더링, 데이터 흐름, 유효성 검사 전략, 전반적인 애플리케이션 성능 및 유지 보수성에 영향을 미치는 결정입니다. 이러한 두 접근 방식의 미묘한 차이를 이해하는 것은 견고하고 효율적인 사용자 인터페이스를 구축하는 데 중요합니다. 이 글은 이 논의의 핵심으로 들어가 각 접근 방식의 정의를 제시하고, 구현을 시연하며, 각 장단점을 분석합니다.
본문
폼에서 useState와 useRef의 구체적인 내용을 살펴보기 전에 핵심 개념인 제어 컴포넌트와 비제어 컴포넌트에 대한 명확한 이해를 확립해 보겠습니다.
제어 컴포넌트: 제어 컴포넌트는 React가 폼 데이터를 직접 관리하는 컴포넌트입니다. 입력 요소의 값은 React 컴포넌트의 상태 일부에 의해 제어됩니다. 입력에 대한 모든 변경은 상태를 업데이트하는 이벤트 핸들러에 의해 처리되며, 이는 컴포넌트를 다시 렌더링하고 입력의 표시 값을 업데이트합니다. 이를 통해 입력 데이터의 단일 진실 공급원을 확보할 수 있습니다.
비제어 컴포넌트: 반면에 비제어 컴포넌트는 폼 데이터가 DOM 자체에 의해 처리되는 컴포넌트입니다. 모든 상태 업데이트에 대한 이벤트 핸들러를 작성하는 대신, DOM이 데이터를 관리하도록 하고, 필요할 때(ref를 사용하여 폼 값이 제출될 때 등) 폼 필드의 값을 읽습니다.
이제 useState가 일반적으로 제어 컴포넌트를 지원하고 useRef가 비제어 컴포넌트를 구동하는 방식을 살펴보겠습니다.
useState를 사용한 제어 컴포넌트
원리: 제어 컴포넌트에서는 입력 요소의 값이 항상 React 상태에 의해 구동됩니다. 사용자가 입력하면 onChange 핸들러가 상태를 업데이트하고, 그러면 입력은 새 상태 값으로 다시 렌더링됩니다.
구현 예시:
import React, { useState } from 'react'; function ControlledForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const handleSubmit = (event) => { event.preventDefault(); // 기본 폼 제출 방지 console.log('제출된 이름:', name); console.log('제출된 이메일:', email); // API 등으로 이 데이터를 보낼 수 있습니다. }; return ( <form onSubmit={handleSubmit}> <label> 이름: <input type="text" value={name} onChange={(e) => setName(e.target.value)} /> </label> <br /> <label> 이메일: <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </label> <br /> <button type="submit">제출</button> </form> ); } export default ControlledForm;
장점:
- 단일 진실 공급원: React 상태는 입력 값의 절대적인 소스이므로 예측 가능하고 디버깅하기 쉽습니다.
 - 즉각적인 유효성 검사 및 피드백: 사용자가 입력하는 동안 실시간 유효성 검사 로직을 구현하고 즉각적인 피드백을 제공할 수 있습니다.
 - 쉬운 조작: 입력 값을 프로그래밍 방식으로 쉽게 조작할 수 있습니다(예: 폼 재설정, 값 미리 채우기, 입력 형식 지정).
 - 간소화된 조건부 로직: 입력 값을 기반으로 버튼 비활성화, 요소 표시/숨기기 등이 간단해집니다.
 - 동적 폼에 탁월: 다른 입력 값에 따라 동적으로 나타나거나 변경되어야 하는 입력 필드가 있는 경우.
 
단점:
- 성능 오버헤드: 모든 키 입력은 
onChange이벤트를 트리거하여 상태 업데이트와 컴포넌트(및 잠재적으로 해당 자식)의 리렌더링을 유발합니다. 많은 입력이나 빈번한 업데이트가 있는 폼의 경우 때때로 문제가 될 수 있습니다. - 더 많은 보일러플레이트: 각 입력 필드에 대한 
onChange핸들러를 작성하고 상태를 관리해야 합니다. 
useRef를 사용한 비제어 컴포넌트
원리: 비제어 컴포넌트에서는 React가 입력 값을 관리하지 않습니다. 대신 useRef를 사용하여 DOM 입력 요소에 직접 참조를 얻고 필요할 때(일반적으로 폼 제출 시) 해당 값을 읽습니다.
구현 예시:
import React, { useRef } from 'react'; function UncontrolledForm() { const nameInputRef = useRef(null); const emailInputRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); // 기본 폼 제출 방지 console.log('제출된 이름:', nameInputRef.current.value); console.log('제출된 이메일:', emailInputRef.current.value); // API 등으로 이 데이터를 보낼 수 있습니다. }; return ( <form onSubmit={handleSubmit}> <label> 이름: <input type="text" ref={nameInputRef} /> </label> <br /> <label> 이메일: <input type="email" ref={emailInputRef} /> </label> <br /> <button type="submit">제출</button> </form> ); } export default UncontrolledForm;
장점:
- 적은 보일러플레이트: 각 입력에 대한 
onChange핸들러나 상태가 필요하지 않으며, 특히 간단한 폼의 경우 더욱 그렇습니다. - 잠재적으로 더 나은 성능: React가 입력 값을 직접 관리하지 않으므로 모든 키 입력에 대한 리렌더링이 없습니다. 중간 리렌더링이 비용이 많이 드는 매우 큰 폼이나 고성능 시나리오에 유익할 수 있습니다.
 - 비React 코드와의 쉬운 통합: 타사 DOM 라이브러리를 직접 DOM 조작으로 통합해야 할 때.
 
단점:
- React 제어 상실: React는 입력의 현재 값을 즉시 알 수 없습니다.
 - 수동 유효성 검사: 실시간 유효성 검사는 현재 값을 상태에서 즉시 사용할 수 없으므로 더 어렵습니다. 일반적으로 제출 시에만 유효성을 검사합니다.
 - 쉬운 프로그래밍 업데이트 없음: 입력 값을 프로그래밍 방식으로 변경하는 것은 덜 관용적입니다. 
ref.current.value에 액세스하여 직접 설정해야 하며, 이는 React의 렌더링 주기를 우회할 수 있습니다. - 동적 UI에 어려움: 사용자가 입력하는 동안 문자 수 표시와 같이 즉각적인 입력 값에 따라 달라지는 동적 UI를 구축하는 것이 더 번거롭습니다.
 
무엇을 선택해야 할까
- 
제어 컴포넌트 (
useState): 대부분의 React 폼에 대해 권장되는 우선적인 접근 방식입니다. React의 선언적 UI 및 명시적 데이터 흐름 철학과 잘 맞습니다. 다음이 필요한 경우 사용하십시오.- 실시간 입력 유효성 검사.
 - 입력 값을 기반으로 한 조건부 UI 로직.
 - 동적 입력 필드 및 복잡한 폼 상호 작용.
 - 전역 상태 관리 또는 외부 데이터 소스를 위한 React 상태와의 원활한 통합.
 
 - 
비제어 컴포넌트 (
useRef): 간단한 폼이나 특정 엣지 케이스를 고려하십시오.- 제출 시 최종 입력 값만 필요한 매우 기본적인 폼이 있는 경우.
 useState리렌더링이 진정으로 병목 현상인 성능이 중요한 상황(일반적인 폼에서는 드물지만).- 기존 비React 코드 또는 직접 DOM 액세스를 예상하는 특정 타사 라이브러리와 통합하는 경우.
 - 파일 입력 요소는 보안 제한으로 인해 프로그래밍 방식으로 값을 설정할 수 없으므로 거의 항상 비제어 상태이므로 마지막 수단으로 사용하십시오.
 
 
결론
React 폼 관리를 위해 useState(제어)와 useRef(비제어) 사이의 선택은 제어, 유연성 및 성능 간의 절충점으로 귀결됩니다. 비제어 컴포넌트는 최소한의 보일러플레이트와 잠재적으로 더 적은 리렌더링을 제공하지만, React의 선언적 파워를 많이 희생하고 복잡한 상호 작용을 어렵게 만듭니다. 약간 더 명시적인 코드를 사용하더라도 제어 컴포넌트는 폼을 처리하는 강력하고 유연하며 React 관용적인 방법을 제공하여 대부분의 애플리케이션에 기본적이며 일반적으로 우수한 선택이 됩니다. 본질적으로 제어 컴포넌트는 React 방식을 수용하여 사용자 입력 처리에 탁월한 파워와 예측 가능성을 제공합니다.