3.1.8 useImperativeHandle
실제 개발 과정에서는 자주 볼 수 없는 훅으로 널리 사용되지는 않는다.
useImperativeHandle를 이해하기 위해서는 먼저 React.forwardRef에 대해 알아야 한다.
forwardRef 살펴보기
ref는 useRef에서 반환한 객체로, 리액트 컴포넌트의 props인 ref에 넣어 HTMLElement에 접근하는 용도로 흔히 사용된다.
ref를 상위 컴포넌트에서 하위 컴포넌트로 전달하고 싶다면? 그러나 직접 props로 넣어 사용할 수 없다면?
forwardRef를 사용하면 네이밍의 자유가 주어진 props보다 더 확실하게 ref를 전달할 것임을 예측할 수 있고,
사용하는 쪽에서도 안정적으로 받아 사용할 수 있다.
const ChildComponent = forwardRef((props, ref) => {
useEffect(() => {
// {current: undefined}
// {current: HTMLInputElement}
console.log(ref)
}, [ref]);
return <div>안녕!</div>;
}
function ParentComponent() {
const inputRef = useRef();
return (
<>
<input ref={inputRef} />
<ChildComponent ref={inputRef} />
</>
);
}
ref를 받고자 하는 컴포넌트를 forwardRef로 감싸고, 두 번째 인수로 ref를 전달받는다.
부모 컴포넌트에서는 동일하게 props.ref를 통해 ref를 넘겨준다.
forwardRef를 사용하면 ref를 props로 전달할 수 있고, 전달받은 컴포넌트에서도 ref라는 이름을 그대로 사용할 수 있다.
useImperativeHandle이란?
부모에게서 넘겨받은 ref를 원하는 대로 수정할 수 있는 훅.
원래 ref는 {current: <HTMLElement>}와 같은 형태로 HTMLElement만 주입할 수 있는 객체였다.
그러나 전달받은 ref에 useImperativeHandle 훅을 사용하면 부모는 단순히 HTMLElement 뿐만 아니라 자식 컴포넌트에서 새롭게 설정한 객체의 키와 값에 대해서도 접근할 수 있다.
useImperativeHandle를 사용하면 ref의 값에 원하는 값이나 액선을 정의할 수 있다.
3.1.9 useLayoutEffect
공식문서
> 이 함수의 시그니처는 useEffect와 동일하나, 모든 DOM의 변경 후에 동기적으로 발생한다.
ㄴ 두 훅의 형태나 사용 예제가 동일하다는 의미
useEffect와 useLayoutEffect의 사용법 비교
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect', count);
}, [count]);
useLayoutEffect(() => {
console.log('useLayoutEffect', count);
}, [count]);
function handleClick() {
setCount((prev) => prev + 1);
}
return (
<>
<h1>{count}</h1>
<button onClick={handleClick}>+</button>
</>
);
}
두 훅을 사용한 예제 코드 모두 동일한 모습으로 작동하는 것 처럼 보인다.
여기서 중요한 사실은 useLayoutEffect는 모든 DOM 변경 후에 useLayoutEffect의 콜백 함수 실행이 동기적으로 발생한다는 점이다.
여기서 말하는 DOM 변경은 렌더링이다.
실행 순서
- 리액트가 DOM을 업데이트
- useLayoutEffect를 실행
- 브라우저에 변경 사항을 반영
- useEffect를 실행
순서상으로는 useEffect가 먼저 선언돼 있지만 항상 useLayoutEffect가 먼저 실행된다.
>> useLayoutEffect가 브라우저에 변경 사항이 반영되기 전에 실행되고, useEffect는 브라우저에 변경사항이 반영된 이후 실행되기 때문.
리액트 컴포넌트는 useLayoutEffect가 완료될 때까지 기다리기 때문에 컴포넌트가 잠시 일시중지되는 것 같은 일이 발생한다.
이러한 작동방식으로 웹 애플리케이션 성능에 문제가 발생할 수 있다.
그럼 언제 useLayoutEffect를 사용하는 것이 좋을까?
특징상 DOM은 계산됐지만 이것이 화면에 반영되기 전에 하고 싶은 작업이 있을 때 와 같이 반드시 필요할 때만 사용하는 것이 좋다.
DOM의 요소를 기반으로 한 애니메이션, 스크롤 위치를 제어하는 등 화면에 반영되기 전에 하고싶은 작업에 useLayoutEffect를 사용한다면 useEffect를 사용했을 때보다 훨씬 더 자연스러운 사용자 경험을 제공할 수 있다.
3.1.10 useDebugValue
useDebugValue는 일반적으로 프로덕션 웹서비스에서 사용하는 훅이 아니다.
이 훅은 리액트 애플리케이션을 개발하는 과정에서 사용되는데, 디버깅하고 싶은 정보를 이 훅에 사용하면 리액트 개발자 도구에서 볼 수 있다.
// 현재 시간을 반환하는 사용자 정의 훅
function useDate() {
const date = new Date();
// useDebugValue로 디버깅 정보를 기록
useDebugValue(date, (date) => `현재 시간: ${date.toISOString()}`);
return date;
}
export default function App() {
const date = useDate();
const [counter, setCounter] = useState(0); // 리렌더링을 발생시키기 위한 변수
function handleClick() {
setCounter((prev) => prev + 1);
}
return (
<div className="App">
<h1>
{counter} {date.toISOString()}
</h1>
<button onClick={handleClick}>+</button>
</div>
);
위 코드를 실행한 다음, 리액트 개발자 도구로 확인하면

useDebugValue는 사용자 정의 훅 내부의 내용에 대한 정보를 남길 수 있는 훅이다.
두 번째 인수로 포매팅 함수를 전달하면 이에 대한 값이 변경됐을 때만 호출되어 포매팅된 값을 노출한다.
useDebugValue를 사용할 때는 오직 다른 훅 내부에서만 실행할 수 있다.
만약 컴포넌트 레벨에서 실행한다면 작동하지 않는다.
> 공통 훅을 제공하는 라이브러리나 대규모 웹 애플리케이션에서 디버깅 관련 정보를 제공하고 싶을 때 유용하다.
3.1.11 훅의 규칙
- 최상위에서만 훅을 호출해야 한다.
- 반복문이나 조건문, 중첩된 함수 내에서 훅을 실행할 수 없다.
- 훅을 호출할 수 있는 것은 리액트 함수 컴포넌트, 사용자 정의 훅 두가지 경우 뿐이다. 일반 자바스크립트 함수에서는 훅을 사용할 수 없다.