[번역] React useImperativeHandle() 은 어떻게 동작하나요?
영문 블로그 글을 번역했습니다. 허가를 받으면 시리즈를 이어갈 예정입니다.
원문링크: https://jser.dev/react/2021/12/25/how-does-useImperativeHandle-work
ℹ️React Internals Deep Dive 에피소드 12, 유튜브에서 제가 설명하는 것을 시청해주세요.
⚠React@18.2.0기준, 최신 버전에서는 구현이 변경되었을 수 있습니다.
💬 역자 주석: Jser의 코멘트는 ❗❗로 표시 해뒀습니다.
그 외 주석은 리액트 소스 코드 자체의 주석입니다.
... 은 생략된 코드입니다.
useImperativeHandle()을 사용해본 적이 있나요? 내부적으로 어떻게 동작하는지 한번 알아보죠.
사용법
다음은 공식 사용 예시입니다.
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
위의 코드를 통해 이제 FancyInput
에 ref를 첨부할 수 있습니다.
function App() {
const ref = useRef();
const focus = useCallback(() => {
ref.current?.focus();
}, []);
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={focus} />
</div>
);
}
간단해 보이지만, 왜 이렇게 할까요?
ref.current만 업데이트하면 어떨까요?
ImperativeHandle()
을 사용하는 대신 아래와 같이 ref.current
만 업데이트하면 어떨까요?
function FancyInput(props, ref) {
const inputRef = useRef();
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
return <input ref={inputRef} />;
}
사실 그냥 작동하지만 문제가 있습니다. FancyInput
은 정리가 아닌 수락된 ref의 current
만 설정합니다.
React 내부 심층 분석 11 - useRef()는 어떻게 작동할까요? 에서 설명한 것처럼, React는 요소에 연결된 ref를 자동으로 정리하지만 이제는 그렇지 않습니다.
렌더링 도중 ref
가 변경되면 어떻게 하나요? 그러면 이전 ref가 여전히 ref를 보유하게 되므로 <FancyInput ref={inputRef} />
를 사용하려면 정리해야 합니다.
이 문제를 어떻게 해결할 수 있을까요? 정리하는 데 도움이 될 수 있는 useEffect()
가 있으므로 다음과 같은 방법을 시도해 볼 수 있습니다.
function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
return () => {
ref.current = null;
};
}, [ref]);
return <input ref={inputRef} />;
}
하지만 잠깐만요, ref
가 함수 ref가 아닌 RefObject인지 어떻게 알 수 있을까요? 그럼 확인해보겠습니다.
function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
if (typeof ref === "function") {
ref({
focus: () => {
inputRef.current.focus();
},
});
} else {
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
}
return () => {
if (typeof ref === "function") {
ref(null);
} else {
ref.current = null;
}
};
}, [ref]);
return <input ref={inputRef} />;
}
이것은 실제로 useImperativeHandle()
의 작동 방식과 매우 유사합니다. useImperativeHandle()
이 레이아웃 이펙트라는 점을 제외하면, ref 설정은 useEffect()
보다 빠른 useLayoutEffect()
와 동일한 단계에서 이루어집니다.
ℹ 유튜브에서 useLayoutEffect 에 대해 설명 하는 모습 보기
이제 소스 코드를 살펴봅시다
이펙트의 경우 마운트 및 업데이트가 있으며, useImperativeHandle()
이 호출되는 시점에 따라 달라집니다.
이것은 mountImperativeHandle()
,(원본 코드)의 단순화된 버전입니다.
function mountImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
또한 업데이트의 경우, 원본 코드
function updateImperativeHandle<T>(
ref: {| current: T | null |} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null
): void {
// TODO: If deps are provided, should we skip comparing the ref itself?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
return updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps
);
}
다음 사항에 유의하세요.
내부적으로는
mountEffectImpl
과updateEffectImpl
이 사용됩니다.useEffect()
및useLayoutEffect()
는 여기와 여기에서 동일한 작업을 수행합니다.두 번째 인수는
HookLayout
으로 레이아웃 이펙트를 의미합니다.
퍼즐의 마지막 조각, imperativeHandleEffect()
의 작동 방식입니다.(코드)
function imperativeHandleEffect<T>(
create: () => T,
ref: {| current: T | null |} | ((inst: T | null) => mixed) | null | void
) {
if (typeof ref === "function") {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
완벽함을 위한 디테일을 제쳐두면, 실제로는 우리가 쓴 것과 매우 비슷해 보이죠?
마무리
useImperativeHandle()
은 마법이 아니며, 단지 ref 설정과 정리를 래핑할 뿐이며, 내부적으로는 useLayoutEffect()
와 같은 단계에 있으므로 useEffect()
보다 조금 더 빠릅니다.
(원본 게시일: 2021-12-25)
Subscribe to my newsletter
Read articles from Ted Lee directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ted Lee
Ted Lee
I love TDD. Tool Driven Developement. Software engineer for web tech. I'm interested in how tech work.