FrontEnd/Deep Dive

[React] DeepDive 모던리액트(2) 동등 비교

728x90

자바스크립트의 데이터 타입

 

자바스크립트의 모든 값은 데이터 타입을 가지고 있으며 원시타입과 객체 타입으로 나누어져 있다.

 

원시타입 : boolean,nulll,undefined,number,string,symbol,bigint

객체타입 : object

 

이 데이터 타입 중에서 true,false이외에도 조건문에서 마치 true와 false처럼 동작하는 truthy,falsy값이 존재함을 알아두자.

 

false,0,-0,0n,0x0n,NaN,"",'',``,null,undefined

위 값들이 JS에서 falsy로 동작되게 된다.

 

 

Number

정수,실수를 구분하는 일반적인 언어와는 달리 자바스크립트는 모든 숫자를 하나의 타입에 저장했었다. 지금은 보다 큰 숫자를 다룰 수 있는 BigInt 자료형이 추가되었다.

 

JS는 2진수,8진수,16진수 등의 별도 데이터타입이 없기 때문에 비교를 진행할때 모두 10진수로 해석된다.

 

8 == (8).toString(8) //true로 해석

 

 

 

 

Object

객체타입은 참조를 전달한다. 따라서 육안으로 보는 것과 비교하는 실제 값이 다르게 동작할 수 있다.

 

const hello1 = function () {}
const hello2 = function () {}

hello1 === hello2 //false

 

 

 

불변 형태의 값으로 저장되는 원시타입과 달리 객체는 프로퍼티가 삭제,추가,수정될 수 있어 원시값과는 다르게 변경 가능한 형태로 저장되며 값을 복사하는 경우에도 값이 아닌 참조를 전달하게 된다.

 

즉 위의 경우에도 겉으로 보기에는 값이 같을지언정 참조하는 주소가 다르기 때문에 false를 반환하게 된다.

 

 

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는 ES6에서 도입된 문법이기에 리액트에서는 이를 구현한 폴리필을 함께 사용하게 된다.

 

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

 

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

/**
 * 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;

 

리액트는 이 objectIs를 기반으로 shallowEqual이라는 함수를 만들어 사용한다고 한다.

 

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';

/**
 * 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;
}

export default shallowEqual;

 

 

즉 리액트에서의 비교는 객체의 얕은 비교까지 가능하다. 객체의 얕은 비교란 아래처럼 객체의 1 깊이까지 값을 비교하는 것이다.

 

Object.is({hello:'world'},{hello:'world'}) // false

shallowEqueal({hello:'world'} , {hello:'world'}) // true

 

 

이렇게 리액트가 객체의 얕은 비교까지만 구현한 이유는 리액트에서 사용하는 JSX props가 객체이며 이 props만 일차적으로 비교하면 되기 때문이다.

 

type Props = {
	hello : string
}

function HelloComponet(props : Props){
	return <h1>{hello}</h1>
}

 

위코드에서 props는 객체이며 props에서 꺼내온 값을 기준으로 렌더링을 수행하기 때문에 일반적인 케이스에서는 얕은비교로 충분하다. 이러한 특성때문에 props안에 다른 객체를 넣는다면 예상하지 못한 문제가 발생할 수 있다.

 

props가 깊어지는 경우 객체간 비교가 완벽하지 못해 React.memo가 정상적으로 동자하지 않는 경우가 있다. 이런 경우가 있음에도 재귀적으로 완전히 객체를 비교하지 않는이유는 성능에 악영향이 있을 수 있기 때문이다.

 

 

이러한 객체 비교의 불완전성은 자바스크립트 개발자라면 반드시 기억해야 하는 특성으로 이를 잘이해해야 후에 useMemo,useCallback의 필요성과 React.memo를 올바르게 작동 시킬 수 있을 것이다.

 

 

 

 

 

728x90