[ 공모전 ] FileController : 비제어형, 제어형 Component 3/4

최문길·2024년 6월 21일
0

공모전

목록 보기
12/46

제어 컴포넌트, 비제어 컴포넌트

FileController를 만들기 전에 비제어형과 제어형을 알아보자

1. 제어 컴포넌트

제어 컴포넌트는 React에서 상태(state)를 통해 관리되는 컴포넌트를 의미한다. 이 경우, 컴포넌트의 상태(state)를 업데이트하고, 이를 컴포넌트의 값으로 설정하여 관리하는데, 주로 onChange 이벤트 핸들러를 통해 상태 업데이트를 처리하고, 컴포넌트의 값은 상태(state)로부터 설정,결정된다.

따라서 제어 컴포넌트는 우리가 아는 일반적인 React에서 input의 value 값을 제어, 컨트롤 할 때 사용하는 것으로 이해 할수 있다.

2. 비제어 컴포넌트

비제어 컴포넌트는 DOM에서 직접 값을 가져오거나 설정하는 방식이다. 상태(state)를 사용하여 관리 하지 않고, ref를 통해 직접 DOM요소에 접근하여 값을 가져오거나, 설정한다.

여기서 생각할 수 있는 라이브러리는 React Hook Form이 이 방식을 채택한다.

  • 제어 컴포넌트 : 제어 컴포넌트는 값이 변경될 때마다 컴포넌트 전체가 리레런링 된다.
  • react-hook-form : 내부적으로 변경된 필드만 다시 렌더링 하여 최적화를 제공한다.

그런데 왜 설명하나? 라고 생각 할 수 있는데,
React-hook-Form 은 form 라이브러리지만, 제어형 컴포넌트 (UI library)와 결합하여 사용 할 수 있을까? 라고 처음 사용하는 사람에게 물어봤을 때 쉽게 YES 라고 대답을 하지 못할 것 이다. ( 나 또한 어???? 했다 )

React-hook-form에서는 다른 라이브러리와 결합, 제어형 컴포넌트 라이브러리(MUI,Radix..등)와 상호작용 하기 위해 다양한 메소드, 컴포넌트를 제공한다.

그것이 바로 Controller 컴포넌트이다.
물론 Controller 컴포넌트 말고 Hook을 사용하고 싶다면 useController가 있다.

Controller

react-hook-form에서 Controller에 관해 설명글이 있다.

React Hook Form은 제어되지 않는 컴포넌트와 네이티브 입력을 수용하지만, React-Select, AntD 및 MUI와 같은 외부 제어 컴포넌트로 작업하는 것을 피하기는 어렵습니다. 이 Controller 컴포넌트를 사용하면 이러한 컴포넌트로 더 쉽게 작업할 수 있습니다.

그리고 render 함수에 대한 설명글이 있는데, render prop pattern 이라고 한다. (이걸 활용해 FileController를 만들것이다. )
render함수안에 사용할 제어형 컴포넌트를 return 값에 넣어주면 react-hook-form과 같이 사용할 수 있다.
render prop pattern관련 사이트


조금 더 상세하게, 내 프로젝트로 돌아가서 보면
현재 프로젝트에서 ShadCn UI를 사용하는데 form UI를 가져와서 코드를 보면 Controller 컴포넌트가 보일 것이다.

그리고 Controller 컴포넌트를 자세히 확인해보면



위와 같이 함수 형식의 type으로 결정되어있다.
여기서 ControllerProps 즉, Controller의 props를 확인해보면

props는 객체 형태이고 property로는 render함수가 정의 되어있으며 ReactElement 를 반환 한다 그리고 & UseControllerProps 객체가 들어있다.

UseControllerProps객체의 property에는

// node_modules/react-hook-form/dist/types/controller.d.ts

export type UseControllerProps<TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = {
    name: TName;
    rules?: Omit<RegisterOptions<TFieldValues, TName>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
    shouldUnregister?: boolean;
    defaultValue?: FieldPathValue<TFieldValues, TName>;
    control?: Control<TFieldValues>;
    disabled?: boolean;
};

타입과 프로퍼티들이 정의되어있다.

정리하자면
Controller는 react-hook-form인 비제어형과 제어형 컴포넌트를 쉽게 상호작용 ( 나는 연결시키는 것으로 이해함 ) 할 수 있도록 하는 것이고, render props pattern 을 사용하여 제어형 컴포넌트를 보여주도록 한다.



그렇다면 여기서 의문문이 조금은 들지 않는가?
방금전 내가 상호작용(연결) 할 수 있도록이라 했는데 어떻게 연결 시킨다는 거지?? 할 수 있는데... 좋은 질문이다.

zod로 스키마를 작성하고, react-hook-form에 resolver에 등록 시켜놓으면 react-hook-form의 useForm 을 호출 하면 각 field (스키마로 정의한 각 문항들)관련 각각의 field(= input)에 필요한 메소드, value 값이 들어있는데, 여기에 control 이라는 객체가 있다.

이 control을 Controller에 내려주고, name prop을 내려주면 어느 field(input관련)인지 useForm과 연결을 시켜준다.

참고로 React-hook-form의 register의 타입을 보면

 /*
 register: (name: string, RegitserOptions?) => ({  {
    onChange: ChangeHandler;
    onBlur: ChangeHandler;
    ref: RefCallBack;
    name: TFieldName;
    min?: string | number;
    max?: string | number;
    maxLength?: number;
    minLength?: number;
    pattern?: string;
    required?: boolean;
    disabled?: boolean;
 })
 */
const { register } = useForm()
<input { ... register('field1번째') } />

=> <input onChange={onChange} onBlur={onBlur} name={name(='field1번째') ref={ref}}

위와 같은 타입 형식인데, 반환되는 값중 value가 없다. 이 말은 다시 한번더 비제어 컴포넌트 형태로 값(ref)을 표기, 수정, 동작한다.

다시 돌아와서 Controller는 컴포넌트 형태이며, register의 반환값과 같은 상태를 받을 수 있다.

const {control } = useForm()
<Controller
	control={control} // useForm의 control을 내려주어 useForm과 연결
	name='text'  // 가리킬 Form의 field명 (가리킬 field명은 알아야 하잖냐)
	render= {
       ({ field,formState,fieldState }) => (<ReactSelect value={field.value} onChange={field.onChange} />)
    }
.../>

field : onChange,onBlur,value, name, ref
fieldState : invalid, isTouched, isDirty, error
formState : // react-hook-form 참고...

Controller : React-hook-form Docs

register 와 유사하다. register같은 경우 직접 useForm에 등록할 수 있지만, Controller는 말 그대로 무언가를 제어하기 위한 컴포넌트이기에 제어할 field에 이름과 react-hook-form의 register와 같은 상호작용 할 수 있는 control을 넣어 주는 것이다.

useController

다양한 블로그에서 읽어봤을거다. 재사용 가능한 Controller Component를 만들거면 useController를 사용하라고...
useController에 관해 짤막하게 이야기 하자면

"Controller를 커스텀한 훅으로 같은 props와 메소드를 Controller와 같이 사용 할수 있으며 재사용 가능한 제어된 input을 만들 수 있습니다." 라고 적혀있다.

사용예시도 잘 적혀져 있는데, 내가 만들고 싶은 만능 input 컴포넌트 하나를 생성 후, props를 설정 (control, name은 필수로 들어간다. ) 후에 useController를 호출하여 props를 내려주면 Controller와 같이 input이나 다른 컴포넌트에 등록 하여 제어 할 수 있다.

const SamplePage = () => {
  const { handleSubmit, register, control } = useForm();

  return (
    <form onSubmit={handleSubmit((e) => console.log(e))}>
      
      <input {...register("register")} /> //register 사용
      
      <Controller // Controller Component 사용
        control={control}
        name="asdf"
        render={({ field, fieldState, formState }) => <input {...field} />}
      />
      
      <UseControllerCustomInput name="field" control={control} /> //useController 사용
      
     //... 생략
    </form>
  );
};

function UseControllerCustomInput<TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ control, name }: UseControllerProps<TFieldValues, TName>) {
  const { field, fieldState, formState } = useController({
    control,
    name,
  }); // 반드시 control로 연결시키고, 무슨 field인지 name을 적어주자
  return (
    <Fragment>
      <input {...field} /> // Controller의 field와 똑같다. 
      {...fieldState} // 무언가를 해주고 싶을 때
      {...formState} // 무언가를 해주고 싶을 때
    </Fragment>
  );
}

이렇게 마무리

react-hook-form은 다른 제어형 컴포넌트와 결합하기 용이하게 Controller와 재활용성을 높인 Controller Hook인 useController를 가지고 있다.

제어형 컴포넌트와 결합하고 render props pattern 으로 제어형 UI Component를 보여주는 형식을 알아 보았다.

이를 통해서 나는

  • Controller의 render props를 사용
  • useController를 사용하여 method를 정의, 사용

하여 FileController를 만들어 볼 예정이다.

참고

React-hook-form Docs
블로그 1
블로그 2
render props pattern_1
제어/비제어 컴포넌트 블로그
카카오 엔터테이먼트 ioc-pattern
제어컴포넌트 vs 비제어컴포넌트
react-hook-form 종합 블로그

1개의 댓글

comment-user-thumbnail
2024년 7월 2일

useForm을 최상위 컴포넌트에서 사용해보세요 ㅎㅎ

답글 달기

관련 채용 정보