리액트 ref 와 forwardRef (with. react-hook-form)

react-hook-form 라이브러리와 ref의 동작

2022-02-09

이번 새로운 프로젝트에 사용하게 된 react-hook-form과 씨름하면서 정리해본 ref와 react-hook-form의 간단한 원리에 대해 정리해보자.


ref란?

React에서는 props를 통해서만 부모와 자식간의 컴포넌트에 상호작용을 할 수 있다.

자식 컴포넌트에 props를 전달해 props의 값이 변경되면 자식 컴포넌트는 다시 렌더링이 된다.

그러나 props를 통한 방법이 아닌 직접적으로 자식 컴포넌트를 수정해야되는 일이 생길 수 있는데..

Ref를 사용해야 할 때

우선 DOM에 직접적으로 접근하는 방법은 건너 뛰고, react-hook-form과 관련된 비제어 컴포넌트 제어에 관해 알아보자.

react-hook-form의 가장 큰 목적은 퍼포먼스이며, 비제어 컴포넌트를 사용하고 있기 때문에 다른 라이브러리(formik, redux-form 등) 에 비해 타이핑이나 값이 변경될 때 리랜더링이 일어나는 양을 줄여준다.

여기서 비제어 컴포넌트란, 

React에서 제어하지 않고, 바닐라 JS를 이용해 제어하는 컴포넌트를 비제어 컴포넌트라 한다.

쉽게말해 React가 관여하지 않는다. (재조정, 비교 등)

이런 비제어 컴포넌트를 제어하기 위해서, DOM에 접근하는 역할을 ref가 한다.

예를들어

const [test, setTest] = useState('')
<input value={test} onChange={(e) => setTest(e.target.value)} />

위와같이 state로 상태를 관리하게 되면, state가 변하면서 이를 업데이트 하기 위해 계속해서 리렌더링이 된다.

post image

이는 React에서 제어하는 컴포넌트다.

그러면 굳이 인풋에 값만 변하는건데 불필요한 리렌더링이 필요하지 않을 수 있다.

  const inputRef = useRef(null)
  const testHandler = (e) => {
    inputRef.current.value = e.target.value
  }
    <input ref={inputRef} onChange={(e) => testHandler(e)} />
post image

ref를 사용하면 인풋값이 변경되어도 리렌더링이 발생하지 않는다.


Ref 와 함수형 컴포넌트

사실 위 예제는 클래스형 컴포넌트에서 작성해야 적합했지만, 함수형 컴포넌트에서도 ref는 사용할 수 있다.

다만 상태값이 바뀔 때 마다 다시 실행되는 함수형 컴포넌트의 특성때문에 기존과는 다른 방식을 사용한다.

위 예제에서 사용한 useRef는 hook으로 함수의 렌더링의 상관없이 상태 값을 유지한다.


forwardRef

react-hook-form을 사용하면서 

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

이런 에러를 만나게 됐는데, ref 기반으로 구성된 react-hook-form이 ref를 자식요소(인풋컴포넌트)에 전달하기 때문에 발생한 에러였다.

ref는 React에서 props로 사용하지 못하는 것 중에 하나였는데, 대표적인 Key값과 동일하게 props로 사용할 수 없다.

React에서 ref를 props로 사용하려면 forwardRef를 사용해 자식요소로 ref를 전달할 수 있다.

자식 컴포넌트를 forwardRef()함수로 감싸주고, props와 ref를 받아 사용할 수 있다.

const Input = forwardRef((props, ref) => {
  return <input ref={ref} />;
});

추가로 eslint 에러나 개발자 도구에서 forwardRef사용시 이름이 나오지 않는 에러는

displayName 속성에 이름을 설정하거나, 익명함수 대신 이름있는 함수를 넘겨 주면 된다.

Input.displayName = "Input";
const Input = forwardRef(function Input(props, ref) {
  return <input ref={ref} />;
});

ref는 특정 DOM에 접근해 애니메이션을 이용할때만 주로 사용했었는데, 이번 기회에 조금 더 자세하게 들여다 본것 같다.🤔