[TIL] 프로젝트에서 react-hook-form을 사용한 이유

기성·2024년 10월 28일
1

TIL

목록 보기
77/81

문제점

//invitationFormType.type.ts
export type InvitationFormType = {
  gallery: GalleryType;
  type: 'scroll' | 'slide';
  moodPreset: MoodPresetType;
  mainView: DecorateImageType;
  bgColor: ColorType;
  stickers: StickerType[];
  imgRatio: ImageRatioType;
  personalInfo: PersonalInfoType;
  greetingMessage: GreetingMessageType;
  weddingInfo: WeddingInfoType;
  account: AccountInfoType;
  navigationDetail: NavigationDetailType;
  guestbook: boolean;
  attendance: boolean;
  dDay: boolean;
  mainPhotoInfo: MainPhotoType;
  isPrivate: boolean;
  renderOrder: OrderItem[];
  fontInfo: FontInfoType;
};

모바일 청첩장 제작 프로젝트를 진행하며, 사용자 맞춤형 기능 제공을 위해 다수의 입력 필드를 포함해야 한다는 점이 초기 기획의 중요한 부분이었다. 청첩장 제작에는 이미지, 텍스트, 색상, 계좌 정보 등 다양한 데이터가 필요하며, 이 모든 입력 필드를 하나의 폼으로 효율적으로 관리해야 했다.

특히 입력 필드의 개수가 많아질수록 상태 관리성능 최적화가 핵심 과제로 떠올랐다. 개별 입력 필드를 각각 다른 state로 관리하면 코드가 복잡해지고 유지보수가 어려울 뿐만 아니라, 상태 변화로 인한 불필요한 리렌더링 문제가 발생할 가능성이 높았다.

이러한 이유로 프로젝트 초기에 두 가지 기술적 접근법을 검토했다. 하나는 상태 관리 라이브러리인 Zustand를 활용해 폼 상태를 중앙 집중식으로 관리하는 것이었고, 다른 하나는 React Hook Form을 사용해 입력 필드 상태와 유효성 검사를 통합적으로 관리하는 방식이었다.

Zustand vs React Hook Form

19개의 입력 필드를 효과적으로 제어하기 위해 각각의 기술이 제공하는 장단점을 분석했다.

Zustand

Zustand는 경량 상태 관리 라이브러리로, 전역 및 로컬 상태 관리를 유연하게 처리할 수 있는 도구다.

장점

  • 간결한 API: Redux와 같은 복잡한 설정 없이 간단한 방식으로 상태를 관리할 수 있다.
  • 구성 가능성: 폼 상태 외에도 다른 전역 상태와 쉽게 통합 가능하다.
  • 리렌더링 최소화: 필요한 컴포넌트만 리렌더링하므로 성능에 유리하다.

단점

  • 폼 유효성 검사 부재: 입력 데이터에 대한 유효성 검사를 직접 구현해야 한다.
  • 복잡한 데이터 구조 관리: 여러 필드가 포함된 복잡한 폼 데이터를 관리하려면 추가적인 커스터마이징이 필요하다.

React Hook Form

React Hook Form은 폼 상태와 유효성 검사를 통합적으로 관리할 수 있는 도구로, 입력 필드가 많은 폼에서 효율성을 극대화할 수 있다.

장점

  • 유효성 검사 통합: Zod, Yup 같은 라이브러리와 결합해 복잡한 유효성 검사를 간단히 구현 가능하다.
  • 최소화된 리렌더링: 입력 필드의 상태 변화가 개별적으로 관리되어 전체 폼이 리렌더링되지 않는다.
  • 타입스크립트 호환성: TypeScript와의 시너지가 높아 안정적인 데이터 구조를 유지할 수 있다.
  • 간편한 에러 처리: 내장된 에러 관리로 사용자 경험(UX)을 개선할 수 있다.
  • 사용 편의성 : useForm훅을 통해 선언적 방식으로 관리가 가능하다. 입력 값의 추적이 쉽고 reset함수를 통해 기본 값으로 복원하는 것 또한 간편하게 할 수 있다.

단점

  • 학습 곡선: 일부 API가 생소할 수 있어 초기 설정에 시간이 걸릴 수 있다.
  • 폼 상태 전역화의 어려움: 폼 상태가 기본적으로 로컬로 관리되므로, 전역 상태로 활용하려면 추가 작업이 필요하다.

React Hook Form 선택 이유

두 기술 모두 강력한 장점을 가지고 있었지만, React Hook Form이 프로젝트의 요구 사항에 더 적합하다고 판단했다.

  1. 유효성 검사와 상태 관리의 통합

    입력 필드가 많아질수록 유효성 검사 로직이 복잡해지기 때문에, 이를 지원하는 React Hook Form은 개발 시간을 줄이고 유지보수를 단순화하는 데 유리했다.

  2. 최소화된 리렌더링

    Zustand는 해당 상태를 사용하는 컴포넌트가 리렌더링되지만, React Hook Form은 상태 변화가 발생한 입력 필드만 리렌더링하기 때문에, 다수의 입력 필드가 포함된 폼에서도 성능이 뛰어나다.

  3. 타입 안정성

    TypeScript를 적극적으로 활용하는 프로젝트 특성상, React Hook Form의 타입 기반 관리와 Zod를 활용한 스키마 검증이 안정적이고 일관된 데이터 관리를 가능하게 했다.

  4. 간단한 통합

    React Hook Form은 선언적인 방식으로 입력 필드를 등록할 수 있어 기존 UI 컴포넌트에 쉽게 적용할 수 있었다.

React Hook Form 사용하기

설치

pnpm install react-hook-form

기본적인 폼 컴포넌트 설정

폼 상태를 여러 컴포넌트에 제공하려면 FormProvider를 사용해 폼 상태를 전역적으로 감싸주어야 한다. 이렇게 하면 하위 컴포넌트들이 useFormContext를 사용하여 폼 상태에 접근할 수 있다.

const CreateCardPage = () => {
  const methods = useForm<InvitationFormType>({
    mode: 'onChange',
    defaultValues: INVITATION_DEFAULT_VALUE,
    resolver: zodResolver(validationSchema)
  });
  
  const onSubmit = (data: InvitationFormType) => {
    console.log(data);
  };
  
  return (
    <FormProvider {...methods}>
      <Components/>
    </FormProvider>
  );
}

useFormContext 사용하기

하위 컴포넌트에서 useFormContext를 사용하여 상위에서 제공한 form 상태를 참조하여 값을 가져오거나 수정할 수 있다.

const AccountInput = () => {
  const [accountType, setAccountType] = useState<'groom' | 'bride'>('groom');
  const { register, control, setValue, watch } = useFormContext();

  const { fields: groomFields } = useFieldArray({
    control,
    name: 'account.groom',
  });

  const { fields: brideFields } = useFieldArray({
    control,
    name: 'account.bride',
  });

  return (
    <div className='flex-col-center text-sm gap-4 w-full'>
      <div className='flex gap-3 h-[32px] w-full'>
        <label className='self-center w-[50px]'>제목</label>
        <input
          className='px-[8px] w-full rounded-md'
          {...register('account.title')}
          placeholder='신랑 & 신부에게 마음 전하기'
          maxLength={20}
        />
      </div>
      <div className='flex gap-3 h-[32px] w-full'>
        <label className='self-center w-[50px]'>내용</label>
        <input
          className='px-[8px] w-full rounded-md'
          {...register('account.content')}
          placeholder='축복의 의미로 축의금을 전달해보세요.'
          maxLength={20}
        />
      </div>
    </div>
  );
};

export default AccountInput;

useFormContext의 장점

  • 상태 관리의 일관성 유지: 폼 상태가 한 곳에서 관리되므로, 여러 컴포넌트에서 상태를 쉽게 공유하고 수정할 수 있다.
  • 코드 분할 및 모듈화: 폼을 여러 컴포넌트로 분할하여 코드의 가독성을 높이고, 유지보수를 용이하게 할 수 있다.
  • 폼 리렌더링 최적화: React Hook Form은 필요한 부분만 리렌더링하므로, useFormContext를 사용해도 성능 저하 없이 폼 상태를 관리할 수 있다.
  • 유효성 검사: 각 컴포넌트에서 유효성 검사를 독립적으로 처리하고, useFormContext를 통해 에러 상태를 중앙에서 관리할 수 있다.

zod를 통한 validation

zod를 통해 유효성 검사 schema를 만든 뒤 useForm을 선언할 때 resolver로 해당 schema를 넣어주면 유효성 검사를 쉽게 할 수 있다.

import { z } from 'zod';

export const validationSchema = z.object({
  bgColor: z.object({
    r: z.number(),
    g: z.number(),
    b: z.number(),
    a: z.number(),
    name: z.string(),
  }),
  ...
  dDay: z.boolean().default(true),
  mainView: z.object({
    name: z.string(),
    type: z.string(),
  }),
  isPrivate: z.boolean().default(false),
  renderOrder: z.any(),
});

export type validationFormType = z.infer<typeof validationSchema>;

useWatch를 통한 실시간 값 추적하기

useWatch 를 통해서 현재 form에서 관리하는 데이터를 실시간으로 관찰할 수 있다. 청첩장 제작 페이지에서 현재 입력에 따른 미리보기를 제공하고 있기 때문에 실시간으로 값을 추적하여 바로 사용자에게 변경사항을 보여준다.

const AccountPreView = ({ control }: { control: Control<InvitationFormType> }) => {
  const [account, fontInfo] = useWatch({
    control,
    name: ['account', 'fontInfo'],
  });

  return (
    <Account
      account={account}
      fontInfo={fontInfo}
    />
  );
};
export default AccountPreView;

하지만 useWatch를 사용하기 위해서는 사용하려는 컴포넌트에 useForm객체의 control을 props로 넣어주어야 한다.

<AccountPreView control={methods.control} key='accountPreview'/>

마치며

React Hook Form을 도입한 후, 폼 관리를 간편하고 효율적으로 할 수 있었다. form field를 리렌더링 최적화하고, 유효성 검사, useFormContext를 통한 여러 컴포넌트에서의 form 관리 등의 복잡한 로직을 손쉽게 처리할 수 있게 되었다. useFormuseFormContext의 기능 덕분에 form의 복잡도가 높아져도 편리한 컴포넌트 분리와 코드의 일관성 유지 및 성능 저하를 최소화할 수 있게 되었다.

profile
프론트가 하고싶어요

1개의 댓글

comment-user-thumbnail
2024년 11월 1일

이야... 이게 도전팀이구나...

답글 달기