본문 바로가기

FE/React

react-hook-form, register 렌더링 분석

Recoil 메모리 누수 확인을 하려고, 성능 확인 라이브러리 이것 저것을 확인해보다가 React Dev tools를 사용하게 되었다.

Dev tools를 사용하면서 메모리 누수 보단 오히려 렌더링 관점의 확인을 더 할 수 있었다.

예를 들어, 컴포넌트의 리렌더링 빈도와 원인을 시각적으로 파악할 수 있었다.

 

내가 발견한 상황은,

input의 값을 useState-onChange로 변경하면, 글자 하나 변경당 useState를 갖는 컴포넌트가 리렌더 되는 것을 확인했다.

하지만 react-hook-form에서는 register가 적용된 input의 값이 변경되어도, 컴포넌트가 리렌더 되지 않았다.

[ 여기서 'register가 적용된 input'은 react-hook-form에서 제공하는 함수로 관리되는 입력 필드이다. ]

 

왜 그런지 이유가 궁금했다.

결과적으로는, register가 적용된 input은 ref 사용을 통해 리렌더링이 일어나고 있지 않았던 것이다. [ useRef는 랜더링이 필요없는 참조 값임을 알려주는 React Hook이다. ref는 DOM 요소에 직접 접근할 수 있는 기능을 제공하며, 값이 변경되어도 리렌더링이 트리거되지 않는다. ]

 

동작 관련 코드들이 너무 길기 때문에, 중요하게 생각한 지점을 간추려 밑에 남기겠다.

[ 다만, useState-onChange로 글자를 입력할 때, 리렌더링이 되는 상황이 나쁜 것인가? 꼭 ref를 사용 해야 하는가?에 대해서 고민할 필요가 있다고 생각하여 이 부분에 대해서는 다음에 간단하게 정리 해보고자 한다. ]

const register: UseFormRegister<TFieldValues> = (name, options = {}) => {
    ...
      name,
      onChange,
      ref: (ref: HTMLInputElement | null): void => {
      	...
      },
    };
  };
const onChange: ChangeHandler = async (event) => {
	...
    // "name" 값에 해당하는  필드의 값을 fieldValue로 설정
    // Ref 사용으로 re-rendering X
	set(_formValues, name, fieldValue);
    
    // 유효성 / 규칙 준수 여부 검증이 필요 없을 시
    if (shouldSkipValidation) {
        // re-rendering 여부 판단 후 실행
        // const { watch } = useForm(); 를 사용하고, input 값이 변경되면 rendering
        const shouldRender = !isEmptyObject(fieldState) || watched;

        return (
          shouldRender &&
          _subjects.state.next({ name, ...(watched ? {} : fieldState) })
        );
    }

	// 값이 변경되었고
    if (isFieldValueUpdated) {
        field._f.deps &&
        trigger(
            field._f.deps as
            | FieldPath<TFieldValues>
            | FieldPath<TFieldValues>[],
        );
        // 유효성 / 규칙 비준수 시
        // const { formState } = useForm() 을 사용한다면 rendering
        shouldRenderByError(name, isValid, error, fieldState);
    }
}

set

type FieldValues = Record<string, any>;

type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>;

export default (
  object: FieldValues, // 값을 설정할 대상이 되는 객체
  path: FieldPath<FieldValues>, // FieldValues 객체의 경로
  value?: unknown, // 설정할 값
) => {
  ...
  ...
  object[key] = newValue;
  object = object[key];

  return object; // 최종적으로 수정된 객체 반환
};

shouldRenderByError

const shouldRenderByError = (
    name: InternalFieldName,
    isValid?: boolean,
    error?: FieldError,
    fieldState?: {
      dirty?: FieldNamesMarkedBoolean<TFieldValues>;
      isDirty?: boolean;
      touched?: FieldNamesMarkedBoolean<TFieldValues>;
    },
) => {
    const previousFieldError = get(_formState.errors, name);
    const shouldUpdateValid =
      (_proxyFormState.isValid || _proxySubscribeFormState.isValid) &&
      isBoolean(isValid) &&
      _formState.isValid !== isValid;

    if (_options.delayError && error) {
      delayErrorCallback = debounce(() => updateErrors(name, error));
      delayErrorCallback(_options.delayError);
    } else {
    clearTimeout(timer);
    delayErrorCallback = null;
    error
    ? set(_formState.errors, name, error)
    : unset(_formState.errors, name);
}

re-rendering 조건 궁금하다면

https://www.developerway.com/posts/react-re-renders-guide

번역본: https://velog.io/@yoonvelog/react-rerendering

위 링크에서는 React 컴포넌트가 리렌더링되는 조건과 이를 최적화하는 방법을 상세히 다룬다. 리렌더링 최적화가 필요할 때 유용한 가이드가 될 것이다.

 

form을 다루는 여러가지 방법이 궁금하다면

https://rudaks.tistory.com/entry/react-form%EC%9D%84-%EB%8B%A4%EB%A3%A8%EB%8A%94-5%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95

해당 글에서는 React에서 폼을 관리하는 다양한 접근법과 각각의 장단점을 소개하고 있다. React로 폼을 구현할 때 참고하기 좋은 자료이다.


링크

register & onChange & shouldRenderByError

https://github.com/react-hook-form/react-hook-form/blob/12551357b11d6d3e93e7d1101a3ee7517d81b630/src/logic/createFormControl.ts#L129

set

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/utils/set.ts

Field & FieldRefs & InternalFieldName & FieldValues

https://github.com/react-hook-form/react-hook-form/blob/12551357b11d6d3e93e7d1101a3ee7517d81b630/src/types/fields.ts

FieldPath & Path & PathInternal

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/types/path/eager.ts

isKey

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/utils/isKey.ts

stringToPath

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/utils/stringToPath.ts

compact

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/utils/compact.ts

get

https://github.com/react-hook-form/react-hook-form/blob/12551357b11d6d3e93e7d1101a3ee7517d81b630/src/utils/get.ts

RegisterOptions

https://github.com/react-hook-form/react-hook-form/blob/12551357b11d6d3e93e7d1101a3ee7517d81b630/src/types/validator.ts

isObject

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/utils/isObject.ts

isWatch

https://github.com/react-hook-form/react-hook-form/blob/7f95b265d16aee8b6b34d2746f4248aebb9d4678/src/logic/isWatched.ts

useFormProps

https://github.com/react-hook-form/react-hook-form/blob/master/src/types/form.ts

cloneObjects

https://github.com/react-hook-form/react-hook-form/blob/12551357b11d6d3e93e7d1101a3ee7517d81b630/src/utils/cloneObject.ts

 

'FE > React' 카테고리의 다른 글

useState가 의도대로 동작을 하지 않는다.  (0) 2025.02.22
React useRef vs useState  (0) 2025.02.13
Recoil 메모리 누수 확인하기  (0) 2025.01.11
Recoil  (0) 2024.12.27
npm vs yarn  (0) 2024.12.26