자바스크립트의 데이터 타입
자바스크립트의 모든 값은 데이터 타입을 가지고 있으며 원시타입과 객체 타입으로 나누어져 있다.
원시타입 : 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를 올바르게 작동 시킬 수 있을 것이다.
'FrontEnd > Deep Dive' 카테고리의 다른 글
[React] Deep Dive 모던 리액트(4) JSX (1) | 2023.12.18 |
---|---|
[React] Deep Dive 모던 리액트(3)타입스크립트 (0) | 2023.12.15 |
[React] DeepDive 모던 리액트 (1) 리액트의 역사 (0) | 2023.12.13 |
[JS] DeepDive(49) Babel과 Webpack을 이용한 ES6+/ES.NEXT 개발 환경 구축 (0) | 2023.09.27 |
[JS] DeepDive(48) 모듈 (0) | 2023.09.27 |