TIL - forwardRef() 적용하기

Jisu Park·2022년 12월 2일
0

오늘의개발일지-TIL

목록 보기
8/12

문제 상황 : 커져가는 컴포넌트 나누기

사용자 정보를 제공받는 폼 페이지를 수정 보완해아하는 일이 생겼다. 처음엔 추가되는 디자인을 집어 넣기에 급급했다. 하지만 디자인에는 예외케이스를 위한 다양한 케이스들이 존재했다. 모든 케이스들을 섬세하게 대응하려다보니 코드가 점점 불어날 수 밖에 없었다. 코드길이가 700줄이 넘어가기 시작하나 전부 내가 짠 코드인데도 뭐가 뭔지 이해하려면 너무나 오랜 시간이 필요했다. 오늘 쓴 코드는 내일의 레거시 코드가 된다는 말이 어느 정도 이해가 갔다. 분리가 답이라는 결론에 도달했다.

처음엔 특정 부분을 새로운 파일에 옮기고(=> child component) 연관된 모든 state와 setState를 (모든 컴포넌트를 가진)parent component에서 props로 보내주려고 했다. 하지만 도저히 props가 지저분해지는걸 두고 볼 수 없었다. 이건 아니라는 생각이 들었다. 그래서 다른 개발자분께 SOS를 청했다.

그렇게 forwardRef()를 적용하게 되었다. 하지만 난 이해를 하고 적용한 게 아니기 때문에, 여기에 정리하며 조금 더 forwardRef()를 명확히하고 가려고 한다!

ref란?

forwardRef()를 사용하기 위해서는 ref가 뭔지 알아야 한다. 대신 이 글은 forwardRef()를 위한 것이니 간략하게 하고 넘어가야겠다.

ref : 리액트에서 특정 DOM 요소를 선택하여 접근하기 위해 사용하는 것

이렇게 정의만 보면 당연히 ref를 언제 써야하는지 애매하니, 공식 문서의 글을 보면,

음, 결국 특정 엘리먼트 스타일 변경과 관련이 되어버리는 건가 싶은 생각이 든다. 공식 문서에서의 설명을 조금 더 가져와보면 일반적인 데이터 플로우를 벗어나 직접적으로 자식(ex. 컴포넌트의 인스턴스, DOM 엘리먼트 등)을 수정하기 위해 ref를 제공한다고 한다.

forwardRef()를 써야하는 이유

앞서 간략하게 설명한 상황 설명에 덧붙여 보면 결국 필요한 건 부모 컴포넌트에서 자식 컴포넌트에 있는 입력창의 값을 가져오는 것이었다. 다른 예외 케이스를 위한 state들은 모두 자식 컴포넌트에서만 필요했기 때문에 props로 지저분하게 넘길 필요가 없었다. 그렇기 때문에 input에 ref를 걸어주면 되는 일이었다. 하지만 두 컴포넌트는 서로 다른 파일로 분리가 되어있기 때문에 그냥 ref를 지정한다고 끝나는 일이 아니었던 것이다. 게다가 함수 컴포넌트에는 ref가 존재하지 않는다고 하니, forwardRef()를 써야 원하던 바를 달성할 수 있었다.

forwardRef() 사용하기

먼저 부모컴포넌트에서는 필요한 ref를 선언한다. 그리고 자식컴포넌트는 forwardRef()로 감싸는 것이 가장 기본적인 형식이다. 여기서 내가 잠깐 해맸던 것은 forwardRef()를 props부분만 감쌌다는 것인데, 그러면 안되고 다음과 같이 컴포넌트 전체를 감싸야 제대로 작동한다. 이렇게 감싸고 나면 두 개의 인자를 보낼 수 있게 되고, 첫 번째 인자에는 기존처럼 props를 두 번째 인자에는 ref를 보낼 수 있게 된다.

// 부모 컴포넌트
const parentComponent = () =>{
  const ref1 = useRef() as any;
  
  return <childComponent ref={ref1} />
}
  
// 자식 컴포넌트
const childComponent = forwardRef((props, ref) => {
	return <input ref={ref}/>
})

이렇게 하면 부모 컴포넌트에서 선언된 ref이지만 자식 컴포넌트의 특정 요소를 가르키고 있기 때문에 해당 요소의 변화를 들으면서도, 부모 컴포넌트에서 해당 요소로 접근할 수 있게 된다.

여러 개의 ref 한 번에 보내기

문제는 여기서 끝나지 않았다. 난 하나의 ref만 넘겨주는 것이 아닌, 총 3개의 ref를 넘겨주어야했다. 처음엔 다음과 같이 작성했다.

<InputForm ref={ref1, ref2, ref3}/>

그랬더니 계속 다음과 같은 오류를 내며 코드가 동작하지 않았다.

Type '{ ref1: any; ref2: any; ref3: any }' is not assignable to type 
'((instance: unknown) => void) | RefObject<unknown> | null | undefined'.

결국 타입 문제라는 건데, 여러 시도를 해봤으나 통하지 않았고 결국 하나로 묶어준 다음 해당 객체의 타입을 any로 지정하니 해결되었다. 사실 타입을 any로 지정하는 건 좋지 않다는 걸 알지만 일단 돌아가는 게 우선이라 생각해서 이렇게 결론내게 되었다.

  const formRef = {
    ref1,
    ref2,
    ref3,
  } as any;

<InputForm ref={formRef}/>

마지막으로 보낼 때는 묶어서 보냈지만, 받을 때는 따로 받아도 문제 없이 돌아갔다.

const InputForm = forwardRef((props, {ref1, ref2, ref3} : any) => {});

마무리

결론적으로 자식 컴포넌트의 입력창의 value가 변경되는 것을 ref1.current.value의 형식으로 접근할 수 있게 되어 분리가 완성되었다!

Reference

Special Thanks to Donghun, BY

React 공식문서 : Ref와 DOM
[React] ref란? - DOM에 직접 접근하기(useRef)
[React] forwardRef 사용법

profile
언젠간 데이터 분석을 하고 싶은 초짜 프론트엔드 개발자입니다🙃

0개의 댓글