soso-tip
soso-tip copied to clipboard
react-hook-form 톺아보기
react-hook-form의 동작방식은 HTML Element를 내부 ref
로 관리하는 방식이다.
// useForm 'register'함수의 마지막 부분
return (ref: (TFieldElement & Ref) | null) =>
ref && registerFieldRef(ref, refOrRegisterOptions);
// input의 ref를 attachEventListeners 함수에 전달한다.
function registerFieldRef(ref, options) {
const fieldsRef = React.useRef<FieldRefs<TFieldValues>>({});
const { name, type, value } = ref;
// input type
if (type) {
attachEventListeners(
field,
isRadioOrCheckbox || isSelectInput(ref),
handleChangeRef.current,
);
}
}
// blur와 change or input event를 등록한다.
export default function attachEventListeners(
{ ref }: Field,
shouldAttachChangeEvent?: boolean,
handleChange?: EventListenerOrEventListenerObject,
): void {
if (isHTMLElement(ref) && handleChange) {
ref.addEventListener(
shouldAttachChangeEvent ? EVENTS.CHANGE : EVENTS.INPUT,
handleChange,
);
ref.addEventListener(EVENTS.BLUR, handleChange);
}
}
ref.addEventListener
를 통해 이벤트를 등록하고, UnMount(혹은 unregister)시점에 이벤트 리스너를 제거한다.
watch
입력의 onChange
mode에서 유효성 검사가 필요할 때는 watch
(or useWatch
)를 사용한다.
const watchFieldsRef = React.useRef<InternalNameSet<TFieldValues>>(new Set());
const watchFields = watchId
? useWatchFieldsRef.current[watchId]
: watchFieldsRef.current;
const assignWatchFields = (
fieldValues: TFieldValues,
fieldName: InternalFieldName<TFieldValues>,
watchFields: Set<InternalFieldName<TFieldValues>>,
...
) => {
let value = undefined;
watchFields.add(fieldName);
/* 생략 */
return isUndefined(value)
? isSingleField
? inputValue
: get(inputValue, fieldName)
: value;
};
watch
를 수행하면 watchFields.add(fieldName);
에 의해 fieldName에 해당하는 input을 watchFieldsRef
에 추가한다.
handleChangeRef.current = async ({ type, target }: Event) => {
const name = (target as Ref)!.name;
const field = fieldsRef.current[name];
if (field) {
/** 생략... */
let shouldRender =
!isEmptyObject(state) ||
(!isBlurEvent && isFieldWatched(name as FieldName<TFieldValues>));
}
};
이벤트 리스너에 등록되는 handleChangeRef.current
함수에서 isFieldWatched
를 확인하여 shouldRender
값을 결정한다. 이 값은 shouldRenderBaseOnError
함수에 전달되어 true일 경우 ReRendering을 발생시키는 updateFormState
함수를 호출한다.
Controller
Controller는 외부 라이브러리 의존성 등으로 Controlled Component를 사용할 수 밖에 없는 경우 사용하는 Component이다.
const Controller = <
TAs extends React.ReactElement | React.ComponentType<any> | NativeInputs,
TFieldValues extends FieldValues = FieldValues
>(
props: ControllerProps<TAs, TFieldValues>,
) => {
const { rules, as, render, defaultValue, control, onFocus, ...rest } = props;
const { field, meta } = useController(props);
const componentProps = {
...rest,
...field,
};
return as
? React.isValidElement(as)
? React.cloneElement(as, componentProps)
: React.createElement(as as NativeInputs, componentProps as any)
: render
? render(field, meta)
: null;
};
export { Controller };
useController
Controller
와 같은 기능을 hooks로 사용할 수도 있다.
import { useController, useForm } from "react-hook-form";
function Input({ control, name }) {
const {
field: { ref, ...inputProps },
meta: { invalid, isTouched, isDirty },
} = useController({
name,
control,
rules: { required: true },
defaultValue: "",
});
return <TextField {...inputProps} inputRef={ref} />;
}
function App() {
const { control } = useForm();
return <Input name="firstName" control={control} />;
}