[React] 함수 컴포넌트에서 다른 컴포넌트의 메소드를 사용할 수 있을까

봄도둑·2022년 8월 12일
0

백엔드 개발자이지만 회사 업무의 일환으로 react 프로젝트를 진행하고 있습니다. 그런데 평소 우리가 참조하는 것이 불가능하다고 생각했던 함수를 참조해 그 안에 들어 있는 메소드를 호출할 수 있다는 진귀한 배움이 있었습니다. 이번 글은 자바를 해오면서 객체 참조에 대한 개념이 정면으로 충돌하면서 배웠던 것을 풀어보려고 합니다.

회사에서 진행하는 프로젝트 중 클래스 컴포넌트로 작업되어 있는 프로젝트를 함수 컴포넌트로 바꾸는 작업을 진행하던 중 한가지 문제에 휘말립니다.

class ParentsConatainer  extends Component {
	//..
	componentDidMount() {
    //..
    this.ChildContainer.someFunction();
  }
	//..
}

부모 컴포넌트에서 자식 컴포넌트에 있는 함수를 불러와서 사용해야 하는 상황이 생겼습니다. 클래스 컴포넌트의 경우 객체 자체를 타고 들어가면 자식 컴포넌트에 선언된 함수를 사용할 수 있습니다. 그러나 함수 컴포넌트는 이러한 방법으로 다른 컴포넌트의 함수를 불러올 수 없는 상황이 발생했습니다. 보다 정확히 말하자면 다른 함수 컴포넌트에 있는 함수를 어떻게 가져와야 할지 모르는 상황인 것입니다.

const ParentsConatainer = () => {
	//..
	useEffect(() => {
			//여기에 불러올 수 없음ㅋㅋㅋㅋㅋㅋ
	}, []);
}

주로 사용하는 언어가 자바이기 때문에 들었던 생각은 다른 컴포넌트(함수) 안에 선언되어 있는 무언가를 가져온다는 건 자바로 치자면 메소드안에 선언한 변수를 가져오는 것이 아닌가라는 생각이 들었습니다.

이 부분에 대해서는 프론트 부분에서 가이드를 주고 계신 사수님이 자바스크립트에는 애당초 함수라는 개념이 없다고 알려주셨습니다. 정확히 말하자면 자바스크립트는 함수라는 개념이 없고 Object를 상속 받은 객체들을 함수처럼 처리 후 결과를 리턴하는 객체로 사용하는 것입니다.

이러한 가이드를 받고 생각한 것은 함수를 객체 참조하듯이 사용하면 불러올 수 있지 않을까라는 생각에 도달했고, 이렇게 해서 사용한 것이 useRef 입니다.

const ParentsConatainer = () => {
	let childRef = useRef<any>(null); //타입스크립트를 쓰고 있음!
	//..
	useEffect(() => {
			childRef.current. //...???? 여기에 들어 있는 것은 우리가 가져오고 싶은 것이 없었음
	}, []);
}

useRef를 사용하면 객체로 가져올텐데 ChildView 안에 들어 있는 녀석에 대해 아무것도 가져올 수 없었습니다. console.log를 찍어 보았을 때 그 안에 함수 비슷한 무언가 들어 있는 것은 보이는데 그 무언가를 가지고 올 수 없는 상황이었습니다.

(마치 창 밖에 하와이를 보고 탈락해버린 정형돈의 심정이었을까요ㅋㅋㅋㅋ)

이것 외에도 수많은 삽질(이라고 쓰고 뻘짓이라고 읽기도 합니다)을 한 결과 forwardRef 라는 새로운 답에 접근하게 됩니다.

사용방법은 아주 간단합니다. 가져올 컴포넌트(자식 컴포넌트)를 forwardRef 로 감싸줍니다. 그리고 다른 컴포넌트(부모 컴포넌트)에서 사용할 함수를 자식 컴포넌트의 useImperativeHandle 안에 선언합니다. 그리고 부모 컴포넌트에서 해당 함수를 그대로 가져와 사용하기만 하면 됩니다.

(참고 링크 : https://bobbyhadz.com/blog/react-call-function-in-child-component)

그렇다면 코드로 어떻게 풀어 냈는지 같이 살펴봅시다.

const ChildContainer = forwardRef((props, ref) => {
	//..
  useImperativeHandle(ref, () => ({
    someFunction() {
      //..함수 동작 내용 
    }
	//..
  }));

우리는 ChildContainersomeFunction 을 다른 컴포넌트(ParentsConatainer)에서 사용할 것입니다. 그렇기 위해 ChildContainer를 forwardRef 로 감싸고 someFunction을 useImperativeHandle 안에 선언했습니다

const ParentsConatainer = () => {
	let childRef = useRef<any>(null); //타입스크립트를 쓰고 있음!
	//..
	useEffect(() => {
			childRef.current.someFunction(); //정상적으로 someFunction을 찾아서 호출할 수 있게 됨!
	}, []);
}

그리고 ParentsConatainer에서 useRef 로 객체 형태로 참조한 후 가져와서 사용하면 됩니다.

문제는 무사히 해결했습니다만, 아직 풀리지 않은 의문점이 하나 있습니다. 왜 함수 컴포넌트는 useRef를 통해 바로 사용하지 못한 걸까요?

react의 ref prop은 엄밀히 말하면 사용자가 생성한 custom component를 사용하기 위한 것이 아닙니다. html element의 reference를 변수에 저장한 후 해당 element를 제어 하는데 그 목적이 있습니다. 아래의 코드를 한 번 봅시다.

const sampleComponent = () => {
	const inputRef = useRef(null);	

	return(<input ref={inputRef} />)
}

위의 예시에서 input element에 ref prop으로 inputRef를 넘기게 되면, inputRef의 current를 통해 <input>에 접근할 수 있습니다. 즉 html의 element 중 input을 제어할 수 있게 된 것입니다.

그러나 함수 컴포넌트에서는 useRef를 통해 해당 컴포넌트의 객체를 제어할 수 없습니다. 그 이유는 함수 컴포넌트는 인스턴스가 없기 때문에 ref를 통한 참조가 불가능하기 때문입니다. 그렇기 때문에 useRef를 통해 참조를 시도하면 null을 리턴 받게 되는 것입니다.

(리엑트 공식 문서에서 ref 참조 설명, 이미지 출처 : https://ko.reactjs.org/docs/refs-and-the-dom.html)

이러한 이유로 react에서는 html element가 아닌 react 컴포넌트에서 ref 사용을 할 수 있도록 forwardRef() 라는 함수를 제공합니다. 외부에서 사용할 컴포넌트를 forwardRef() 로 감싸주면 파라미터를 하나 더 받게 되는데 이 파라미터가 ref입니다. 해당 파라미터를 통해 외부 컴포넌트가 forwardRef()로 감싸여진 컴포넌트한테 ref prop을 던지면서 참조가 가능해지는 것입니다.


REFERENCE

https://merrily-code.tistory.com/121
https://www.daleseo.com/react-forward-ref/
https://www.daleseo.com/react-refs/
https://bobbyhadz.com/blog/react-call-function-in-child-component
https://ko.reactjs.org/docs/refs-and-the-dom.html

profile
배워서 내일을 위해 쓰자

0개의 댓글