프로그래머스 스프린트: react-hook-form

박상하·2024년 3월 5일
0

React-Hook-Form 🎯

필자는 스프린트3를 시작하며 아직 1주차 강의에 멈춰있다.
부족한 점이 많아서인 이유도 있고 모르는 부분을 꼭 집고 넘어가고 싶어서이다.(그러려고 노력중)

그 중 막혔던 부분은 React-Hook-Form이다.
이 전에도 React 실습을 진행하며 사용을 해보았었다.
그런데 그 사용방법도 잊어버렸고 또, 왜 사용하는지 어떤 점이 좋은지 또 어떤 상황에선 지양하는지
그 점을 한번 정리해보자!

먼저 React-Hook-Form을 왜 사용할까?

자 먼저 React-Hook-Form을 사용하지 않고 Form을 하나 만들어보자

useState vs react-hook-form 비교 🎯

먼저 동일한 결과물은 다음과 같다 .

useState로 각 Field 값 받아오기 1️⃣

function App() {
  const [input1, setInput1] = useState("");
  const [input2, setInput2] = useState("");
  const [input3, setInput3] = useState("");

  const checkInput1Validation = (input1: string) => {
    if (input1 === ``) {
      throw new Error("비었네요");
    }
  };
  const checkInput2Validation = (input2: string) => {
    if (input2 === ``) {
      throw new Error("비었네요");
    }
  };
  const checkInput3Validation = (input3: string) => {
    if (input3 === ``) {
      throw new Error("비었네요");
    }
  };

  const handleSubmit = () => {
    try {
      checkInput1Validation(input1);
      checkInput2Validation(input2);
      checkInput3Validation(input3);
      alert(`input1:${input1}, input2:${input2}, input3:${input3}`);
    } catch (e) {
      alert(e);
    }
  };

  return (
    <>
      <h1>test</h1>
      <form onSubmit={handleSubmit}>
        <input
          onChange={(e) => {
            setInput1(e.target.value);
          }}
          placeholder="Input1"
        />
        <input
          onChange={(e) => {
            setInput2(e.target.value);
          }}
          placeholder="Input2"
        />
        <input
          onChange={(e) => {
            setInput3(e.target.value);
          }}
          placeholder="Input3"
        />
        <button>Submit</button>
      </form>
    </>
  );
}

다음은 react-hook-form

react-hook-form 사용 2️⃣

export interface FromProps {
  input1: string;
  input2: string;
  input3: string;
}

function App() {
  const onSubmit = (data: FromProps) => {
    alert(
      `input1: ${data.input1}, input2: ${data.input2}, input3: ${data.input3}`
    );
  };
  const { register, handleSubmit } = useForm<FromProps>();

  return (
    <>
      <h1>test</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input
          {...register("input1", { required: true })}
          placeholder="input1"
        />
        <input
          {...register("input2", { required: true })}
          placeholder="input2"
        />
        <input
          {...register("input3", { required: true })}
          placeholder="input3"
        />
        <button type="submit">submit</button>
      </form>
    </>
  );
}

input이 3개밖에 없었는데도 코드의 수가 50%이상 줄어들어 든 것을 확인할 수 있다!

이처럼 간단한 사용방법으로 코드의 수를 감소시킬 수 있고 또, 좋은점이 있다.
위 useState와 다르게 State로 input의 값을 관리하지 않기 때문에 rerendering에 대한 부담이 줄어든다. 이는 VirtualDOM이 계속해서 변경되는 것을 방지할 수 있다.

간단한 데모를 만들어본 후 다음과 같은 이점을 정리할 수 있었다.

  • 상태 대신 각 필드의 참조를 사용하여 불필요한 리렌더링을 방지하고 이를 통해 가상 DOM의 업데이트를 최소화한다.
  • 내장된 유효성 검사로 코드의 수가 많이 줄어들게 된다.
  • 간결한 사용법

여기서 1번 상태 대신 각 필드의 참조를 사용한다.
이 말은 비제어 컴포넌트 방식이다라는 말이다.

비제어 컴포넌트 🎯

비제어 컴포넌트방식이란 state를 사용하지 않고 직접 DOM요소의 상태를 직접 관리한다. 그래서 직접 DOM요소에 대한 값을 가져오지만 rerendering 역시 일어나지 않는다.

그리고 CRA에서 react-hook-form을 지원하는 줄 알았는데..
지원하지 않으니 꼭 npm에서 설치하기를!!

이외에 react-hook-form이 제공하는 기능은 굉장히 다양하다.

각종 옵션들이 많이 존재하니 이를 정리하는 과정은 필요할 거 같다,
react-hook-form을 정리한 블로그

react-hook-form 자세히 🎯

일단 useFrom은 객체의 형태로 다양한 반환값을 제공한다.

  • register: 요소의 속성으로 전달되어야 하는 값들을 반환하는 함수로 해당 함수를 통해 거의 대부분의 유효성 검증이 가능하다.
  • hadleSubmit: 유효성 검증을 통과한 사용자 입력값을 전달받기 위한 함수
  • watch: 사용자 입력값의 변화를 감시한다.
  • formState: 폼의 상태를 저장하는 객체로, 대표적으로 errors 객체를 통해 에러정보를 가져올 수 있다.

공식문서와 npm의 요약정보를 함축하면

  1. 기존의 HTML을 사용하는 크기가 작고 빠른 라이브러리
  2. UI라이브러리와 함께 사용할 수 있는 라이브러리
  3. 일관된 유효성 검사를 제공

register 🎯

우리는 register함수를 html(input)에 props로 제공하여 다양한 유효성 검증과 입력값을 추적할 수 있다.

register함수의 형태를 보면 다음과 같다.

register: (name: string, RegisterOptions?) => ({ onChange, onBlur, name, ref });

type RegisterOptions = Partial<{
  required: boolean | string | { value: boolean; message: string; }; // 필수값 여부 체크합니다.
  min: number | string | { value: number | string; message: string }; // 입력 값의 최솟값 체크합니다.
  max: number | string | { value: number | string; message: string }; // 입력 값의 최댓값 체크합니다.
  maxLength: number | string | { value: number | string; message: string }; // 입력 값의 최대 길이 체크합니다.
  minLength: number | string | { value: number | string; message: string }; // 입력 값의 최소 길이 체크합니다.
  pattern: RegExp | { value: RegExp; message: string }; // 정규식을 사용한 입력 값 체크합니다.
  validate: Validate | Record<string, Validate>; // 커스텀 유효성 체크를 만들 때 사용됩니다.
  valueAsNumber: boolean // Number로 형변환 합니다.
  valueAsDate: boolean // Date로 형변환 합니다.
  setValueAs: (value: any) => any // value를 가공합니다.
  disabled: boolean // true로 설정 시, value가 undefined 되며 요소가 disabled 됩니다.
  onChange: (event: any) => void // change 이벤트 발생 시 실행되는 콜백입니다.
  onBlur: (event: any) => void // blur 이벤트 발생 시 실행되는 콜백입니다.
  value: unknown // 요소가 가지고 있는 값입니다.
  shouldUnregister: boolean // true로 지정할 경우 요소가 언마운트 되면 입력값이 제거됩니다.
  deps: string | string[] // 나열된 요소가 함께 유효성 검증이 실행됩니다.
}>;

거의 대부분의 타당성 검증이 가능할거 같다.

그런데 register를 적용하는 모습을 보면 다음과 같다.

return <>
  <input {...register("inputName",{옵션넣기})}/>
  
  </>

왜 이런 모습을 하고 있을까?

사실 register의 옵션을 직접 넣어줄 수 있다. 그런데 위 방법이 더 편해서 저렇게 사용하는 것이다.

이게 무슨말이냐면

const {register}=useForm()
const {onBlur,onChange,name,ref} = register("inputName")


return <>
  <input
    onChange={onChange} 
    onBlur={onBlur}
    name={name}
    ref={ref}
    />
  
  </>

이렇게 사용도 가능한데 그냥 register를 spread 연산자를 사용해 넣어주면?

const {register} = useForm()

return <>
  <input 
    {...register("inputName",{옵션 덮어쓰기})}
    />
  
  </>

정확하게는 register의 두번째 파라미터는 RegisterOptions라고한다.

register: (name: string, RegisterOptions?) => ({ onChange, onBlur, name, ref });

아까 위에서 정리한 개념에 나와있다.

자 원래 register는 객체로 onChange, onBlur,name,ref가 담겨져있다.

그래서 {...register}을 input에 넣으면 자동으로 해당 option이 들어가게 되고 정확하게는 onChange, onBlur,name,ref가 들어가게 되는 것이다.

즉, register는 객체의 형태로 속성값을 전달해주기 때문에 해당 속성값을
spread 연산자를 통해 비교적 편리하게 넣어주는 것이다. 그리고 RegisterOptions를 통해 타당성검증을 이어나갈 수 있다.

function LoginForm({
  onSubmit = async (data) => {
    await new Promise((r) => setTimeout(r, 1000));
    alert(JSON.stringify(data));
  },
}) {
  const {
    register,
    handleSubmit,
    formState: { isSubmitting, isSubmitted, errors },
  } = useForm();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="email">이메일</label>
      <input
        id="email"
        type="text"
        placeholder="test@email.com"
        aria-invalid={
          isSubmitted ? (errors.email ? "true" : "false") : undefined
        }
        {...register("email", {
          required: "이메일은 필수 입력입니다.",
          pattern: {
            value: /\S+@\S+\.\S+/,
            message: "이메일 형식에 맞지 않습니다.",
          },
        })}
      />
      {errors.email && <small role="alert">{errors.email.message}</small>}
      <label htmlFor="password">비밀번호</label>
      <input
        id="password"
        type="password"
        placeholder="****************"
        aria-invalid={
          isSubmitted ? (errors.password ? "true" : "false") : undefined
        }
        {...register("password", {
          required: "비밀번호는 필수 입력입니다.",
          minLength: {
            value: 8,
            message: "8자리 이상 비밀번호를 사용하세요.",
          },
        })}
      />
      {errors.password && <small role="alert">{errors.password.message}</small>}
      <button type="submit" disabled={isSubmitting}>
        로그인
      </button>
    </form>
  );
}

위 코드 처럼 각 Input에 대한 타당성검증 역시 한눈에 볼 수 있게 정리할 수 있다. 위 코드는
블로그를 참고하였다.

RegisterOptions는 다양한 옵션이 존재해 직접 input을 만들 때 사용해보면 더 깊은 이해를 할 수 있을 것이다.

또 register를 html코드 내에서 작성하면 지저분할 수 있으니

const passwordCheckRegister = register('passwordCheck', {
		required: { value: true, message: '비밀번호를 다시 입력해주세요.' },
		validate: (changePasswordCheck?: string) => {
			if (!changePassword) return;
			return changePasswordCheck === changePassword || '비밀번호가 일치하지 않습니다.';
		},
	});

이렇게 커스텀 한 뒤

<input {...passwordCheckRegister}/>

도 가능하다!

이후로 프로젝트를 더 진행하면서 스스로 더욱 써보고 그 후기를 작성해보겠다

0개의 댓글

관련 채용 정보