리액트에서 동등 비교를 하는 방법

상현·2024년 5월 11일
1

React

목록 보기
22/24
post-thumbnail

자바스크립트의 동등 비교

리액트의 가상 DOM과 실제 DOM의 비교, 리액트 컴포넌트가 렌더링 할지를 판단하는 방법, 변수나 함수의 메모이제이션 등 모든 작업은 자바스크립트의 동등 비교를 기반으로 한다.

자바스크립트에서 값을 비교하기 위한 세가지 방법이 있다.

  1. == 연산자
  2. === 연산자
  3. [Object.is](http://Object.is) 메서드

Object.is 메서드는 다음과 같은 차이점이 있다.

-0 === +0 // true
Object.is(-0, +0) // false

Number.NaN === NaN // false
Object.is(Number.NaN, NaN) // true

NaN === 0 / 0 // false
Object.is (NaN, 0 / 0) //true

위의 예시를 보면 알 수 있듯이 Object.is가 좀 더 개발자가 기대하는 방식으로 정확히 비교한다. 그러나 여전히 객체 간 비교에 있어서는 ===과 동일하게 동작한다.

// 참조가 다른 객체에 대해 비교 불가능
Object.is({ hello: 'world' }, { hello: 'world' }) // false

리액트에서의 동등 비교

리액트에서는 Object.is를 이용해 동등 비교를 한다. 구형 브라우저 지원을 위해 폴리필을 함께 사용한다.

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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 =
  // $FlowFixMe[method-unbinding]
  typeof Object.is === 'function' ? Object.is : is;

export default objectIs;

https://github.com/facebook/react/blob/main/packages/shared/objectIs.js

리액트에서는 이 objectIs함수를 기반으로 동등 비교를 하는 shallowEqual이라는 함수를 만들어 사용한다. shallowEqual은 의존성 비교 등 리액트의 동등 비교가 필요한 다양한 곳에서 사용된다.

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      // $FlowFixMe[incompatible-use] lost refinement of `objB`
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

https://github.com/facebook/react/blob/main/packages/shared/shallowEqual.js

리액트에서의 비교를 요약하면 Object.is로 먼저 비교를 수행한 다음, Object.is로 수행하지 못하는 비교, 즉 객체 간 얕은 비교를 한번 더 수행한다.

기본적으로 리액트는 props에서 꺼내온 값을 기준으로 렌더링을 수행하기 때문에 일반적인 케이스에서는 얕은 비교로 충분하다. 이러한 특성을 안다면 props에 또 다른 객체를 넘겨준다면 리액트 렌더링이 예상치 못하게 작동하는 것을 알 수 있다.

얕은 비교로만 수행하는 이유는 깊은 비교를 할 경우 객체안에 얼마나 중첩이 되어있는지 모르기 때문에 성능에 악영향을 미칠 것이다. 이러한 언어적 한계를 뛰어넘을 수 없어 얕은 비교만을 사용해 비교한다.

참조: 모던 리액트 Deep Dive

profile
프론트엔드 개발자

0개의 댓글