토이 프로젝트를 진행하면서 Modal
을 구현하고 있는데, Modal의 상단 부분의 높이를 Modal의 하단 부분에 전달하고 싶어서 간단하게 구현해보았다.
우선 상단 높이를 저장할 clientHeight state
를 생성하고, 상단 부분의 DOM 높이를 측정하기 위해서 useRef
를 통해 상단부분의 ref 값으로 설정했다.
또한 useEffect
를 이용해서 topRef(상단 부분)의 높이가 바뀔 때 마다 setState
를 호출하도록 작성했다.
const [clientHeight, setClientHeight] = useState(0);
const topRef = useRef();
useEffect(()=>{
setClientHeight(topRef.current.clientHeight);
},[topRef.current]);
// ...some codes
<ModalTopInfo ref={topRef}/>
<ModalBottomInfo height={clientHeight}/>
위와같이 코드를 작성했는데 ModalBottomInfo
로 height 값이 이상하게 전달되는 것 같아서 확인해보니 topRef.current
가 null
을 return 하고 있었다. 그러니 topRef.current.clientHeight
또한 원하는 값을 return 하지 못하고 있었다.
Ref 타입을 선언을 안해줘서 그런가 싶어서 타입도 설정해봤지만 여전이 null
을 return 하길래 비슷한 사례가 있나 싶어서 검색해보니 역시나 동지1 동지2 동지3들이 있었다.
무엇이 문제인고 하니, useRef
에 의해 생성된 ref는 컴포넌트 리렌더링을 발생시키지 않는다.
useRef
를 통해서 ref가 생성이 되긴 했지만 const topRef = useRef();
를 초기값을 null로 초기화했으므로 첫번째 렌더링시 null로 유지된다. 첫번째 렌더링 중에 ref={topRef}
를 통해서 ref 값을 업데이트 하지만, 위에서 말했다 싶이 첫번째 렌더링 이외의 다른 렌더링을 발생시키지는 않기 때문에 ref는 여전히 null 값으로 기억되고 있는 것이다.
만약 업데이트 된 ref 값을 사용하고 싶다면 또 다른 리렌더링을 발생시켜서 ref 값이 바뀌었다는 것을 알려줘야한다.
이에 대한 해답은 React 공식문서에서 찾을 수 있었다. (심지어 내 사례와 똑같이 DOM 높이를 측정하는 예시다!)
const [clientHeight, setClientHeight] = useState(0);
const topRef = useCallback((node)=>{
if(node !== null){
setClientHeight(node.getBoundingClientRect().height);
}
},[]);
// ...some codes
<ModalTopInfo ref={topRef}/>
<ModalBottomInfo height={clientHeight}/>
예시 코드를 보면 useRef
를 사용하지 않고 useCallback
을 통해 ref를 생성하고 해당 컴포넌트가 마운트, 언마운트 될때 호출되도록 작성되어 있고, 해당 node가 존재하는 경우에만 setClientHeight
를 호출하도록 작성되어 있다.
⚡ useRef는 값이 변경 되더라도 사용자에게 알리지 않는다 (리렌더링을 발생시키지 않는다)
만약 React가 ref를 DOM node에 연결하거나 분리할 때 특정 코드를 실행하고 싶다면 useCallback을 사용하자!