ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Reactjs hooks 를 통한 비교 이해
    물망초. 2022. 5. 30. 00:37

    Reactjs 구현체에서 활용 중인 비교와 구현 코드

    • package/react-conciler/src/ReactFiberHooks.new.js/areHookInputsEqual
    ... 아래는 실질 비교부
    for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
        if (is(nextDeps[i], prevDeps[i])) {
          continue;
        }
        return false;
      }
      return true;
    }
    • is? Object.is
    // Object.is 폴리필
    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;
    Object.is('foo', 'foo');     // true
    Object.is(window, window);   // true
    
    Object.is('foo', 'bar');     // false
    Object.is([], []);           // false
    
    var test = { a: 1 };
    Object.is(test, test);       // true
    
    Object.is(null, null);       // true
    
    // 특별한 경우
    Object.is(0, -0);            // false
    Object.is(-0, -0);           // true
    Object.is(NaN, 0/0);         // true
    • React hooks 구현체
    function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = updateWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      let destroy = undefined;
    
      if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        destroy = prevEffect.destroy;
        if (nextDeps !== null) {
          const prevDeps = prevEffect.deps;
          if (areHookInputsEqual(nextDeps, prevDeps)) {
            hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
            return;
          }
        }
      }
    
      currentlyRenderingFiber.flags |= fiberFlags;
    
      hook.memoizedState = pushEffect(
        HookHasEffect | hookFlags,
        create,
        destroy,
        nextDeps,
      );
    }
    
    function mountEffect(
      create: () => (() => void) | void,
      deps: Array<mixed> | void | null,
    ): void {
      if (
        __DEV__ &&
        enableStrictEffects &&
        (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
      ) {
        return mountEffectImpl(
          MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
          HookPassive,
          create,
          deps,
        );
      } else {
        return mountEffectImpl(
          PassiveEffect | PassiveStaticEffect,
          HookPassive,
          create,
          deps,
        );
      }
    }
    // useCallback mount
    function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = mountWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      hook.memoizedState = [callback, nextDeps];
      return callback;
    }
    
    // useCallback 갱신 부
    function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = updateWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      const prevState = hook.memoizedState;
      if (prevState !== null) {
        if (nextDeps !== null) {
          const prevDeps: Array<mixed> | null = prevState[1];
          if (areHookInputsEqual(nextDeps, prevDeps)) { // 상단에 비교 로직
            return prevState[0];
          }
        }
      }
      hook.memoizedState = [callback, nextDeps];
      return callback;
    }
    
    // useMemo mount
    function mountMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = mountWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      const nextValue = nextCreate();
      hook.memoizedState = [nextValue, nextDeps];
      return nextValue;
    }
    
    // useMemo 갱신 부
    function updateMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = updateWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      const prevState = hook.memoizedState;
      if (prevState !== null) {
        // Assume these are defined. If they're not, areHookInputsEqual will warn.
        if (nextDeps !== null) {
          const prevDeps: Array<mixed> | null = prevState[1];
          if (areHookInputsEqual(nextDeps, prevDeps)) {
            return prevState[0];
          }
        }
      }
      const nextValue = nextCreate();
      hook.memoizedState = [nextValue, nextDeps];
      return nextValue;
    }

    실제 비교는 무엇이 있지?

    Referential equality → === 또는 Objest.is

    const group1 = {
      name: 'itzy'
    };
    const group2 = {
      name: 'twice'
    };
    group1 === group1; // true
    group1 === group2; // false
    group1 == group1; // true
    group1 == group2; // false
    Object.is(group1, group1); // true
    Object.is(group1, group2); // false

    Manual comparison → 직접 비교로직 작성

    function isGroupEqual(object1, object2) {
      return object1.name === object2.name;
    }
    const group1 = {
      name: 'itzy'
    };
    const group2 = {
      name: 'itzy'
    };
    const group3 = {
      name: 'RocketPunch'
    };
    isGroupEqual(group1, group2); // => true
    isGroupEqual(group1, group3); // => false

    Shallow equality → 비교 대상이 많은 속성을 가지거나 런타임 중에 객체의 구조가 결정되는 경우 (1뎁스 비교에 주의)

    function shallowEqual(object1, object2) {
      const keys1 = Object.keys(object1);
      const keys2 = Object.keys(object2);
      if (keys1.length !== keys2.length) {
        return false;
      }
      for (let key of keys1) {
        if (object1[key] !== object2[key]) {
          return false;
        }
      }
      return true;
    }
    
    const group1 = {
      name: 'itzy',
      realName: 'Yuna'
    };
    const group2 = {
      name: 'itzy',
      realName: 'Yuna'
    };
    const group3 = {
      name: 'RocketPunch'
    };
    shallowEqual(group1, group2); // => true
    shallowEqual(group1, group3); // => false
    
    const group1 = {
      name: 'itzy',
      song: {
        title: 'wannabe'
      }
    };
    const group2 = {
      name: 'itzy',
      song: {
        title: 'wannabe'
      }
    };
    shallowEqual(group1, group2); // => false

    Deep equality → 비교 대상이 중첩된 객체로 이루어져 심층 동등성을 명확하게 확인해야하는 경우

    function deepEqual(object1, object2) {
      const keys1 = Object.keys(object1);
      const keys2 = Object.keys(object2);
      if (keys1.length !== keys2.length) {
        return false;
      }
      for (const key of keys1) {
        const val1 = object1[key];
        const val2 = object2[key];
        const areObjects = isObject(val1) && isObject(val2);
        if (
          areObjects && !deepEqual(val1, val2) ||
          !areObjects && val1 !== val2
        ) {
          return false;
        }
      }
      return true;
    }
    function isObject(object) {
      return object != null && typeof object === 'object';
    }
    
    const group1 = {
      name: 'itzy',
      song: {
        title: 'wannabe'
      }
    };
    const group2 = {
      name: 'itzy',
      song: {
        title: 'wannabe'
      }
    };
    deepEqual(group1, group2); // => true

    결론

    • Reactjs 는 Object.is 기반의 reference 비교를 통해 갱신한다.
    • deps 의 무한의 가까운 값을 지닐 수 있음을 염두하여 효율성을 위해서 Reactjs 는 shallow equality 를 적용하였다.

    참고 문서

    '물망초.' 카테고리의 다른 글

    이상적인 코드 스플릿팅  (0) 2023.11.27
    Typescript - 기본타입  (0) 2022.04.24
    Dockerfile 명령어  (0) 2022.01.17
    redux] async 를 어떻게 처리 하는게 좋을까?  (0) 2021.11.14
    text-decoration is not woking?  (0) 2021.10.07

    댓글

Designed by Tistory.