요약
- useEffect의 Dependency Array 비교 원리를 파악한다.
- Reference Type일 경우 useEffect의 callback을 호출하지 않기 위해 dependency를 어떻게 비교할 것인지에 대해 대안을 알아본다.
useEffect가 존재하는 Component Render Flow
- First Render: init Component -> useEffect
- Re-Render: init Component -> clean up useEffect -> useEffect
react-reconciler
useEffect의 update 조건이 충족할 때, dependency를 확인하는 코드를 보자.
// https://github.com/facebook/react/blob/ddd1faa1972b614dfbfae205f2aa4a6c0b39a759/packages/react-reconciler/src/ReactFiberHooks.new.js#L1206
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
...
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
...
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create,
destroy,
nextDeps,
);
}
updateEffectImpl에서 areHookInputsEqual를 통해서 현재와 이전 의존성을 비교하는 것을 알 수 있다.
// https://github.com/facebook/react/blob/ddd1faa1972b614dfbfae205f2aa4a6c0b39a759/packages/react-reconciler/src/ReactFiberHooks.new.js#L296
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
...
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
areHookInputsEqual에서는 Object.is()로 prevDeps와 nextDeps를 비교한다.
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is;
export default objectIs;
Dependency Type에 따른 useEffect 동작
1. Primitive Type(Number, String, Bool)일 경우:
- dependency가 변경이 된다면, 함수가 새로 호출된다.
- dependency가 변경이 되지 않으면, 함수가 호출되지 않는다.
2. Reference Type(Array, Object)일 경우:
- Imutable 하게 관리하기 때문에, 매번 새롭게 생성이 된다.
- Object의 경우, JSON.stringify()으로 변경 여부를 확인할 수 있지만, undefined, Functions, Symbol 등 지원되지 않는 타입에 대해 조심해야 한다.
3. Function일 경우:
- 매번 새롭게 생성되기 때문에 함수가 매번 호출된다.
- useCallback을 통해 함수를 Memorization하게되면, 함수가 변경될 때만 호출할 수 있다.
Dependency Type이 Reference Type일 경우 어떻게 비교해야 할까?
- 이전 값을 기억해서 현재 값과 비교하기 위해 useRef를 활용해서 비교할 수 있다.
- kentcdodds/use-deep-compare-effect라는 잘 만들어진 Deep Compare 함수가 있다. 하지만, 보통 대부분의 경우 Deep Compare보다 나은 방법이 존재하므로 깊이 비교하는 것이 정말 필요한지 다시 생각해보자.
/**
* @param value the value to be memoized (usually a dependency list)
* @returns a memoized version of the value as long as it remains deeply equal
*/
export function useDeepCompareMemoize<T>(value: T) {
const ref = React.useRef<T>(value)
const signalRef = React.useRef<number>(0)
if (!deepEqual(value, ref.current)) {
ref.current = value
signalRef.current += 1
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useMemo(() => ref.current, [signalRef.current])
}
// useage
React.useEffect(callback, useDeepCompareMemoize(dependencies))
참고
- https://github.com/facebook/react/blob/ddd1faa1972b614dfbfae205f2aa4a6c0b39a759/packages/react-reconciler/src/ReactFiberHooks.new.js#L1206
- https://github.com/facebook/react/blob/ddd1faa1972b614dfbfae205f2aa4a6c0b39a759/packages/react-reconciler/src/ReactFiberHooks.new.js#L296
- https://github.com/kentcdodds/use-deep-compare-effect
728x90
'Tech > React' 카테고리의 다른 글
React 이슈 정리 (0) | 2022.06.03 |
---|---|
react-hot-loader란? (0) | 2021.10.26 |
SyntheticEvent and throttle (0) | 2021.08.07 |
ReactElement.js (2) | 2021.08.04 |
Controlled Components, Uncontrolled components (0) | 2021.08.03 |
댓글