백엔드 개발자이지만 회사 업무의 일환으로 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() {
//..함수 동작 내용
}
//..
}));
우리는 ChildContainer
의 someFunction
을 다른 컴포넌트(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을 던지면서 참조가 가능해지는 것입니다.
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