kokoball의 devlog
article thumbnail
728x90

 

 

이번 글은 리액트에서 폼 관련 라이브러리 중 가장 많은 사랑을 받고 있는 React Hook Form의 동작 원리에 대해서 알아보려고 합니다.

 

우선 Form Tag Elements를 알아보면서, 신뢰 가능한 단일 출처에 대해 알아보고, 리액트에서 폼을 다루는 방법과 React Hook Form의 동작원리 및 제어 컴포넌트와 비제어 컴포넌트의 차이점을 알아보도록 하겠습니다.

 

React Form 라이브러리 인기 그래프

 

 

Form Tag Elements

대표적인 Form Tag Elements는 input, textarea, select 이렇게 3가지로 볼 수 있습니다.

아래 예시를 확인해 보면 Form Tag에 submit 이벤트를 감지하여 각각의 tag의 value Attribute에 접근하고 있습니다. 

// 코드 예시 1
const form = document.createElement('form');

const onSubmit = (e) => {
  e.preventDefault();

  console.log(e.target.elements['input-elem'].value);
  console.log(e.target.elements['textarea-elem'].value);
  console.log(e.target.elements['select-elem'].value);
};

form.addEventListener('submit', onSubmit);

 

여기서 value Attribute는 사용자가 입력한 값을 가져오는 역할과 사용자가 입력한 값을 저장하는 역할을 합니다.

이 말은 '사용자가 입력한 값은 DOM에 저장된다' 와 같은 말이 되는데요. value Attribute는 DOM에 존재하기 때문입니다.

 

value Attribute를 사용하는데 주의할 점은 '신뢰 가능한 단일 출처'를 사용해야 한다는 점입니다.

 

만약 위 예시 1에서 아래 예시 2처럼 코드를 수정한다고 가정해 보겠습니다.

// 코드 예시 2
const form = document.createElement('form');

let inputValue = '';
let textareaValue = '';
let selectValue = '';

const onSubmit = (e) => {
  e.preventDefault();

  inputValue = e.target.elements['input-elem'].value;
  textareaValue = e.target.elements['textarea-elem'].value;
  selectValue = e.target.elements['select-elem'].value;
};

form.addEventListener('submit', onSubmit);

 

이 예시는 value Attribute가 계속 최신화되고 있으며 이 값을 let 변수를 사용해서 다른 컴포넌트에서 사용하는 등 여러 로직을 수행할 것으로 보입니다.

 

하지만, 만약 코드 변경 등 다양한 이유로 let 변수와, value Attribute의 관계가 끊기게 되면 let 변수는 최신 값을 가지지 못하게 되므로 신뢰 가능한 단일 출처를 유지하는 것이 중요합니다.

React에서 Form을 다루는 방법

리액트에서는 이 문제를 제어 컴포넌트를 사용하여 이 문제를 해결할 수 있습니다.

// 코드 예시 3
import { useState } from 'react';

export default function App() {
  const [name, setName] = useState('');

  return (
    <form>
      <label>name : </label>
      <input type="text" value={name} onChange={(e) => {setName(e.target.value)}} />
    </form>
  );
}

 

예시 3을 확인해 보면 input tag의 value Attribut와 React의 state를 결합하여 신뢰 가능한 단일 출처를 만들고 있습니다.

 

다른 방법으로는 비제어 컴포넌트를 이용하는 방법이 있습니다.

// 코드 예시 4
import { useRef } from 'react';

export default function App() {
  const nameRef = useRef(null);

  const submitName = (e) => {
    e.preventDefault();
    console.log(nameRef.current.value);
  }

  return (
    <form onSubmit={submitName}>
      <label>name : </label>
      <input type="text" ref={nameRef} />
    </form>
  );
}

 

위 예시 4는 예시 3과 달리 useRef를 직접 연결하여 관련 로직을 구성하고 있습니다.

ref 같은 경우는 순수 JS 객체이기 때문에 current.value를 통해 값을 확인할 수 있으며, 변경이 되더라도 리렌더링이 일어나지 않는 특징을 가지고 있습니다.

 

React Hook Form 동작 방식 및 제어 컴포넌트와 비 제어 컴포넌트 비교

눈치가 빠르다면 여기서 React Hook Form 동작 원리를 아셨을 거 같습니다.

 

React Hook Form은 React가 Form의 입력값을 제어하지 않고 전통적인 HTML Form Element와 같은 방식을 사용함으로써

value Attribute의 신뢰 가능한 단일 출처를 유지하고 있습니다.

 

그렇기에 사용자 입력 값이 변경되더라도 리렌더링을 발생시키지 않으며, Form Elements를 React 상태로 관리하지 않아 관련 코드 및 로직을 간단히 처리할 수 있습니다.

 

제어 컴포넌트와 비제어 컴포넌트의 차이점을 정리해 보면 아래 표와 같습니다.

  제어 컴포넌트 비제어 컴포넌트
Submit 과 같은 일회성 정보 검색 O O
Submit 시 유효성 검사 O O
조건에 따른 Submit 버튼 (비)활성화 O X
즉각적인 필드 유효성 검사 O X
특정 입력 형식 적용 O X
동적 입력 O X

 

물론 비제어 컴포넌트가 제어 컴포넌트보다 무조건 좋은 건 아닙니다.

 

React가 각각의 폼의 상태를 관리함으로써 즉각적인 필드의 유효성을 검사할 수 있으며, 입력값에 대한 특정 입력 형식 및 동적 입력을 지원할 수 있습니다. 

 

그렇기에 React Hook Form은 비제어 컴포넌트 방식을 기본으로 채택하고 있지만, 특정 방식으로 이를 확장하여(subscription 방식) 제어 컴포넌트처럼 동작하게 만들고 있습니다.

 

입력 필드를 내부의 상태 관리 시스템에 등록하는 방식으로, 해당 필드는 React Hook Form 상태에 Subscription 하게 됩니다.

 

Subscription 된 필드는 값이 변경될 때마다 React Hook Form에 이를 알리고, 이 값이 내부적으로 반영됩니다.

이 과정은 비제어 컴포넌트 기반으로 이루어지지만, Subscription 시스템 덕분에 React Hook Form은 실시간으로 상태 변화를 추적할 수 있습니다. (watch 사용법 링크)

 

기본적인 React Hook Form 사용 방법은 'register'를 사용하는 방식입니다.

<textarea
  ...
  {...register('ability.introduce')}
/>

 

register의 경우는 입력한 값의 업데이트를 직접 setValue 등의 방법을 이용하여 수동으로 진행해야 하며, 사용하고 있는 컴포넌트 패턴에 따라 사용하기 힘든 상황이 있을 수 있습니다. (Compound Component Pattern을 사용하면 사용하기 어렵습니다.)

이 경우 Controller을 사용해서 해당 요소를 감싼다면, register를 적용한 것처럼 React Hook Form에서 Form 요소를 제어할 수 있습니다.

<Controller
  name={`data.lists.${index}.status`}
  render={({ field }) => (
    <Dropdown label="데이터 라벨" autoSelect options={statusOptions} {...field} />
  )}
/>

 

 

 

 

 

 

https://react-hook-form.com/

https://legacy.react-hook-form.com/api/useform/watch/

https://react-hook-form.com/advanced-usage

https://www.react-hook-form.com/api/usecontroller/controller/

728x90
profile

kokoball의 devlog

@kokoball-dev

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!