이전글에서 이어지는 내용입니다.
기존의 react-hook-form
의 useController
를 활용하여 하나의 프로젝트에서 보다 재사용성을 높혀서 활용할 수 있었지만, UI 라이브러리를 antd로 변경하여 활용하면서 기존 방식의 문제점들 발견 하게 되었다.
바로 다른 Input 컴포넌트(UI 라이브러리 혹은 내 커스텀 input등)
을 활용하려 할때 마다 새롭게 useController를 포함한 컴포넌트를 생성해야 한다는 점이였다. 물론 한 프로젝트에서 다른 Input 컴포넌트들을 조합해서 쓸일은 많지 않겠지만 규모가 작은 프로젝트(혹은 사이드 프로젝트)들을 개발 할때 이 점이 단점으로 다가오게 되었다.
더군다나 최근 MSA(MicroService Architecture)
와 모노레포
를 채택하는 일이 많아지면서 이러한 단점이 크게 다가오게 되어 개선 작업을 하게 되었다.
기존의 구조는 아래와 같았다
// react-hook-form import는 생략....
import { Input } from "antd"; // 사용할 Input 컴포넌트 import
// type import 생략....
const MyInput = <
T extends FieldValues,
K extends FieldPath<T>
>({ controllerProps, ...props }
:{ controllerProps: UseControllerProps<T,K> & InputProps }) => {
const { field } = useController({...controllerProps});
return (
<Input {...field} />
)
}
즉, MyInput
컴포넌트에 사용한 Input
을 import 함으로써 사용할 Input외에 다른 컴포넌트를 사용하고 싶을 경우 동일한 방식의(사실상 import만 다른) 다른 컴포넌트를 하나 더 생성 하여야 하였다.
즉, 위의 문제점을 개선하기 위해서 Input 컴포넌트를 prop으로 전달할 필요성이 있어 다음 코드와 같이 구현하게 되었다.
import React, { ComponentProps, ElementType } from "react";
import { useController } from "react-hook-form";
import type { UseControllerProps, FieldPath, FieldValues} from "react-hook-form";
interface ControlledInputProps<
T extends FieldValues,
K extends FieldPath<T>,
U extends ElementType
> {
controllerProps: UseControllerProps<T, K>;
as: U;
asProps?: ComponentProps<U>;
}
const ControlledInput = <
T extends FieldValues = FieldValues,
K extends FieldPath<T> = FieldPath<T>,
U extends ElementType = "div"
>(
props: ControlledInputProps<T, K, U>
) => {
// as => input component, asProps => input component props
const { controllerProps, as, asProps } = props;
const { field } = useController({ ...controllerProps });
return React.createElement(as, { ...field, ...asProps });
};
export default ControlledInput;
위의 코드와는 다르게 as
prop을 통해 input 컴포넌트를 받아 활용하였고, asProps
를 통해서 해당하는 컴포넌트의 props
를 전달 받았다. 또한 ComponentProps
를 활용해 as
를 통해 전달한 input 컴포넌트의 props를 추론할 수 있도록 사용하였다.
import { Input } from "antd";
import { TextField } from "@mui/material";
// 만든 컴포넌트 import
import MyInput from "@/somewhere";
// form의 type
type FormType = {
password?: string;
id?: string;
};
const SomeFormComponent = () => {
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
id: ""
}
});
const onSubmit = (data: FormType) => { console.log(data); };
return (
<form onSubmit={handleSubmit(onSubmit)}>
<MyInput
controllerProps={{ control, name: "id" }}
as={TextField}
asProps={{
//... 원하는 props들
}}
/>
<MyInput
controllerProps={{ control, name: "password" }}
as={Input.password}
asProps={{
//... 원하는 props들
}}
/>
</form>
)
};
export default SomeFormComponent;
즉 위와 같은 구조로 as
를 통해 Input 컴포넌트를 전달하여 이전보다 활용성을 높일 수 있게 되었다.
물론 개요에서 언급 했던 것처럼 단일 프로젝트 보다는 MSA
와 모노레포
구조에서 보다 더 잘 활용 할 수 있을 것이라 생각한다.