
해당 게시글은 모던 리액트 딥다이브를 읽고, 작성한 정리글입니다.
리액트 컴포넌트 랜더링이 일어나는 이유 중 하나가, props의 동등 비교에 따른 결과이다.
이 props의 동등 비교는 객체의 얕은 비교를 기반으로 이뤄지기 때문에 중요하다.
(1) 리액트의 가상 DOM과 실제 DOM 비교,
(2) 리액트 컴포넌트가 랜더링할지 판단하는 방법,
(3) 변수나 함수의 메모이제이션 등 거의 모든 작업이 동등 비교를 기반으로 이루어진다.
JS 에서 타입은 크게 원시타입과 객체타입으로 나눠지며, 두 타입을 기준으로 하위엔 다음과 같이 나눠볼 수 있다.
undefined
undefined 가 할당된다. 즉, 선언되었지만 할당되지 않은 값을 의미null
boolean
falsy 로 표현되는 값
| 값 | 타입 | 설명 |
|---|---|---|
false | Boolean | false는 대표적인 falsy한 값 |
0, -0, 0n, 0x0n | Number, BigInt | 0은 부호나 소수점 유무에 상관없이 falsy |
NaN | Number | NaN(Not a Number)은 falsy한 값 |
"" / '' / `` | String | 문자열이 falsy하기 위해서는 반드시 공백이 없는 빈 문자열이어야 함 |
null | null | null은 falsy한 값 |
undefined | undefined | undefined는 falsy한 값 |
truthy 로 표현되는 값 : falsy가 아니면 참으로 판명. 특히, 객체와 배열은 내부 값의 존재 여부과 관계업이 모두 truthy ([] {})
number
// ECMAScript 표준 기준 표현가능했던 범위 (bigInt 등장전)
const maxinteger = Math.pow(2, 53)
maxinteger - 1 === Number.MAX_SAFE_INTEGER // true
const mininteger = -(Math.pow(2, 53) - 1)
mininteger === Number.MIN_SAFE_INTEGER // truebigint
number 형의 제한을 극복하기 위해 등장한, 타입
// 사용할땐, 숫자 끝에 n을 붙여주거나 BigInt 함수를 이용한다.
const number = 9007199254740992
const bigint = 9007199254740992n
typeof number // number
typeof bigint // bigint
number == bigint // true
number === bigint // false(타입이 달라서)
string
'), 혹은 큰 따옴표("), 또는 템플릿 리터럴 표현방식인 백틱 (`) 으로 표현된다.symbol
object (배열 / 함수 / 정규식 / 클래스 )원시타입은 불변형태의 값으로 저장, 그래서 할당 시점에 메모리영역을 차지함
객체타입은 참조를 저장하고 있기에, 변경 가능한 형태로 저장됨
// 내부 형태, 값이 같아도 객체는 참조를 저장하기 때문에 두 함수는 동등 비교시, false!
var hello = {
greet: 'hello, world',
};
var hi = {
greet: 'hello, world',
};
// 객체 간의 동등 비교는 false
console.log(hello === hi); // false
// 원시값인 내부 속성값은 같음을 알 수 있다.
console.log(hello.greet === hi.greet); // true
Object.is
==과Object.is차이
== 비교는 강제로 형변환을 한후에, 타입이 다를경우 둘을 맞춰주고 비교가 이루어지는 반면, Object.is 는 === 와 같이 이러한 작업을 하지 않음 (타입이 다르면, 그냥 false)
===과Object.is차이
Object.is는 === 와 달리, +0, -0을 구분한다.
NaN과 Number.NaN 도 마찬가지 이며,
NaN와 0/0 도 구분하는 식의, 정밀 비교가 가능하다
(ES6에서 동등 비교 === 한계를 극복하기 위해 도입됨)
하지만 객체 비교에는 큰 차이가 없다!
리액트에서 사용되는 동등비교는 Object.is를 사용한다. 구형 브라우저의 경우 (ES6 미지원) polyfill을 사용한다고 한다.
(폴리필 코드: 최신 JavaScript 기능이 구형 브라우저에서 동작하지 않을 때, 이를 대신 구현하여 제공하는 코드)
// 폴리필 코드
function is(x: any, y: any) {
return (
// x === y 두 값이 완전히 같으면 기본적으로 true
// x === 0인 경우 1 / x === 1 / y로 +0과 -0을 구별
// NaN !== NaN이 항상 true이라 x와 y가 둘 다 NaN이라면 true를 반환
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is;
리액트에서는 Object.is로 먼저 비교를 진행한 후에, 객체 간 얕은 비교를 한번 더 수행한다.
이때, 객체 간 얕은 비교란 객체의 첫번째 깊이에 존재하는 값만 비교하는 방법이다.
깊은 비교는 객체 내부의 모든 속성을 재귀적으로 확인해야 하지만, 이는 리소스가 많이 드는 일이다. 따라서, 객체에서 첫번째 deps의 키를 꺼내서, 비교하고자 하는 객체에 같은 키가 있는지, 그리고 그 값이 같은 지를 확인한다.
리액트는 객체 비교에 있어서 불안정성이 있기에, 위의 비교 방법은 리액트 개발자로서 알아두면 좋을 것 이다!! 뒤에서 나올, useMemo나 useCallback의 필요성을 활용하기 위해 잘 알아두자!