useEvent
useCallback의 한계는 의존성 배열(deps)만을 이용해 제어하므로 인수로 전달한 함수가 (1) 다시 생성되어야 한다는 의도와 함수 내부에서 (2) 어떤 값을 참조하겠다는 의도를 분리하기 어렵다는 점이다.일반적으로
useCallback을 적용하고자 하는 함수는 해당 컴포넌트의state나 부모로부터 전달받은props을 이용해 특정 작업을 수행한다.이때 자식 컴포넌트에 전달할 함수 객체의 참조 값이 변화하여 리렌더링이 발생하는 것을 막기 위해 빈 의존성 배열을 이용해
useCallback을 적용하면 함수 내부에서 참조하는 값이 선언 시점으로 고정되어 버리는 문제가 발생한다.
useEvent (useStableCallback, useCommittedCallback)
useEvent (useStableCallback, useCommittedCallback)RFC에 따르면
useEvent는useCallback에서 겪는 두 가지 이슈를 해결하고자 등장하였다.Stable Identity Callbacks
Fresh Values in Callbacks
function useLatestValue<T>(value: T) {
const cache = useRef(value);
// 실제 구현체에서는 Layout Effect보다 이른 시점에 호출될 것이다.
useIsomorphicLayoutEffect(() => {
cache.current = value;
}, [value]);
return cache;
}
function useEvent<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>
>(cb?: (...args: P) => R) {
const cache = useLatestValue(cb);
return useCallback(
(...args: P) => cache.current?.(...args),
[cache]
);
}먼저 함수 객체의 참조를 항상 동일하게 유지하기 위해
ref를 사용하고,ref가 담고 있는 값(함수 객체)을 계속해서 갱신하는 방식이다.다만 함수 객체의 생성 자체를 막을 방법은 제공되지 않으며,
useEvent에 전달한 함수는 렌더링 단계에서는 호출되어서는 안된다는 제약이 존재한다.리액트에서는 기본적으로 입력(
props,state,context)이 동일하다면 동일한 결과를 생성해야 하므로useEvent에 전달한 함수(ref에 담겨 있는 함수)의 상태에 따라 렌더링 결과가 달라질 수 있다는 것은 비결정적(non-deterministic)이기 때문이다.따라서 렌더링 단계에서 호출되는 함수에는
useEvent대신useCallback을 사용해야 한다.
이벤트는 특정 순간에 객관적으로 발생한 일로 생각하는 것이 명확하며, 일반적으로 함수 이름이
on또는handle이라는 접미사와 함께 정의되어 있다면 이벤트라고 여기는 것이 좋다.
참고 자료
Last updated