본문 바로가기
Tech/React

useEffect의 Dependency Array 비교 원리

by egas 2022. 5. 25.

요약

  • 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))

참고

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

댓글