React에서 여러 ref를 자식 컴포넌트에게 전달하는 방법

여러 ref를 하나로 묶어 자식 컴포넌트에게 ref props로 전달해보자


input 태그의 value를 state 대신 ref.current.value로 관리하고 있는 프로젝트가 있다. 원래는 하나의 컴포넌트에서 모든 작업을 전부 수행했기 때문에 각각의 input 태그에 맞는 useRef<RefElemType>(null)를 선언하고 ref에 할당하면 그만이었으나 컴포넌트의 사이즈가 커지면서 결국 컴포넌트를 분리하기로 했다.

모든 input 태그들을 하나의 공통 컴포넌트로 만들고 상위 컴포넌트에서 이 공통 컴포넌트를 자식 컴포넌트로 갖게 되면서, 어떻게 해야 상위 컴포넌트에서 총 6개의 ref를 하나로 묶어서 자식에게 전달시켜줘야 할지 생각해보기로 했다.

잘못된 예제
// Parent.tsx
type RefObj = {
  ref1: React.RefObject<HTMLInputElement>
  ref2: React.RefObject<HTMLSelectElement>
  ref3: React.RefObject<HTMLInputElement>
  ref4: React.RefObject<HTMLInputElement>
  ref5: React.RefObject<HTMLSelectElement>
  ref6: React.RefObject<HTMLTextAreaElement>
};
 
export const Parent = () => {
  const refObj = {
    ref1: useRef<HTMLInputElement>(null),
    ref2: useRef<HTMLSelectElement>(null),
    ref3: useRef<HTMLInputElement>(null),
    ref4: useRef<HTMLInputElement>(null),
    ref5: useRef<HTMLSelectElement>(null),
    ref6: useRef<HTMLTextAreaElement>(null),
  }
 
  // ..
 
  return (
    <ChildComponent ref={refObj}>
  )
};
 
// Child.tsx
export const Child = forWardRef<RefObj, Props>((props, ref) => {
  const { ref1, ref2, ref3, ref4, ref5, ref6 } = ref;
  // ..
})

처음엔 위와 같이 단순하게 "하나의 객체로 묶어내서 보내주면 되겠다" 했는데, ref={refObj}에서 타입 에러가 계속 생겼다.

생각해보니 원래 자식 컴포넌트에 하나의 ref를 전달하면, 그 자식 컴포넌트는 forwardRef로 원래의 ref 타입 즉 React.MutableRefObject<T>을 받기 때문에 ref는 무조건 ref 타입으로 보내도록 코드를 수정했다.

적용 예제
// Parent.tsx
type RefObj = {
  ref1: HTMLInputElement | null;
  ref2: HTMLSelectElement | null;
  ref3: HTMLInputElement | null;
  ref4: HTMLInputElement | null;
  ref5: HTMLSelectElement | null;
  ref6: HTMLTextAreaElement | null;
};
 
export const Parent = () => {
  const refObj = useRef<RefObj>({
    ref1: null,
    ref2: null,
    ref3: null,
    ref4: null,
    ref5: null,
    ref6: null,
  });
 
  // ..
 
  return (
    <ChildComponent ref={refObj}>
  )
};
 
// Child.tsx
export const Child = forWardRef<RefObj, Props>((props, ref) => {
  const { current } = ref as React.MutableRefObject<RefObj>;
  // ..
 
  // ⭕️
  return (
    <input id="ref1" ref={(el) => {
      current.ref1 = el;
    }} />
  )
 
  // ❌
  return (
    <input id="ref1" ref={current.ref1} />
  )
});

as React.MutableRefObject<RefObj>로 타입 단언을 해주어 current만 구조분해 할당으로 빼오자. 원래의 ref의 value를 가져오기 위해선 ref.current.value를 사용해야 하는 것을 잊지말자.

위 ❌의 예시에서 ref에 바로 current.ref1을 넣었는데 이렇게 바로 넣어줄 경우, 아래와 같은 타입 에러가 발생한다.

'HTMLInputElement | null' 형식은 'LegacyRef<HTMLInputElement> | undefined' 형식에 할당할 수 없습니다.

나는 이 에러를 해결하기 위해 ref의 elem을 받아 current.ref1에 바로 넣어줬다.