1.1 자바스크립트와 동등 비교
1.1.1 자바스크립트의 데이터 타입
자바스크립트의 모든 값은 데이터 타입을 갖고 있으며, 이 데이터 타입은 원시 타인과 객체 타입으로 나눌 수 있다.
원시타입(primitive type)
- 객체가 아닌 다른 모든 타입, 메서드를 갖지 않는다.
Undefined
선언한후 값을 할당하지 않은 변수 또는 값이 주어지지 않은 인수에 자동으로 할당되는 값
let foo
typeof foo === 'undefined' //true
function bar(hello) {
return hello
}
typeof bar() === 'undefined' //true
후술 할 원시값 중 null과 undefined는 오직 각각 null과 undefined라는 값만 가질 수 있으며, 그 밖의 타입은 가질 수 있는 값이 두 개 이상(boolean의 true, false와 같이) 존재한다.
Null
아직 값이 없거나 비어 있는 값을 표현할 때 사용한다.
typeof null === 'object' //true?
null은 object type을 반환
undefined 선언됐지만 할당되지 않은 값
null은 명시적으로 비어 있음을 나타내는 값
Boolean
참(true), 거짓(false)만을 가질수 있는 데이터 타입
true, false의 boolean형의 값 외에도 true와 false처럼 취급되는 truthy, falsy 값이 존재
| 값 | 타입 | 설명 |
| false | Boolean | false는 대표적인 falsy한 값이다. |
| 0, -0, 0n, 0x0n | Number, BigInt | 0은 부호나 소수점 유무에 상관없이 falsy한 값이다. |
| NaN | Number | Number가 아니라는 것을 뜻하는 NaN(Not a Number)은 falsy한 값이다. |
| "" | String | 문자열이 falsy하기 위해서는 반드시 공백이 없는 빈 문자열이어야 한다. |
| null | null | null은 falsy한 값이다. |
| undefined | undefined | undefined는 falsy한 값이다. |
if (1) {}
Boolean(1)
Number
자바스크립트는 모든 숫자를 하나의 타입에 저장(정수, 실수 구분 없음)
-(2^53-1) ~ 2^53 -1 사이 값을 저장
const maxinteger = Math.pow(2, 53)
maxinteger - 1 == Number.MAX_SAFE_INTEGER // true
const minlnteger = -(Math.pow(2, 53) - 1)
minlnteger === Number.MIN_SAFE_INTEGER // true
2진수, 8진수, 16진수 등의 별도 데이터 타입이 없음
const binary = 0b10
binary == 2 //true
const octal = 0o10
octal == 8 //true
const hex = 0x10
hex == 16 //true
Bigint
number 크기 제한 한계를 넘어선 더 큰 숫자를 저장 가능
//기존 number의 한계
9007199254740992 === 9007199254740993 //true, 더이상 다룰 수 없는 크기 이기 때문에
const maxinteger = Number.MAX_SAFE_INTEGER
console.log(maxinteger + 5 === maxinteger + 6) // true
const bigIntl = 9007199254740995n // 끝에 n을 붙인다
const bigInt2 = BigInt('9007199254740995') // BigInt 함수를 사용하면 된다
const number = 9007199254740992
const bigint = 9007199254740992n
typeof number // number
typeof bigint // bigint
number == bigint //true
number === bigint //false
BigInt 함수를 사용할때 Number 범위를 넘는 숫자를 사용하려면 따옴표를 붙여줘야 함
- BigInt('9007199254740995') (o)
- BigInt(9007199254740995) (x)
String
` , ‘ , “ 으로 사용가능
백틱(`)을 사용하여 표혐한 문자열을 템플릿 리터럴이라 함
줄 바꿈 가능, 문자열 내부 표현식 사용 가능
// '\\n안녕하세요.\\b'
const longText = `
안녕하세요.
`
// 오류 발생
const longText = "
안녕하세요.
"
문자열은 원시 타입이며 변경 불가능함
const foo = 'bar'
console.log(foo[0]) // 'b'
foo[0] = 'a'
console.log(foo) // bar, 변경 불가
Symbol
중복되지 않는 고유한 값을 나타내기 위해 사용
Symbol() 함수를 사용해야만 만들 수 있음
const key = Symbol('key')
const key1 = Symbol('key')
key === key1 //false
Symbol.for('hello') === Symbol.for('hello') //true
객체타입(obejct/reference type)
- 원시 타입 이외의 모든 것, 자바스크립트를 이루고 있는 대부분의 타입이 객체타입
- 객체 타입은 참조를 전달한다고 해서 참조 타입으로도 불린다
typeof [] === 'object' //true
typeof {} === 'object' //true
function hello() {}
typeof hello === 'function' //true, 함수도 객체이지만 실제로는 일급 객체로 특별한 종류로 분류하고 있음
const hello1 = function() {}
const hello2 = function() {}
hello1 === hello2 // false, 같아 보이지만 참조가 다르기 때문에 false를 반환
1.1.2 값을 저장하는 방식의 차이
원시 타입과 객체 타입의 가장 큰 차이점은 값을 저장하는 방식의 차이
원시타입
- 불변 형태의 값으로 저장
- 변수 할당 시점에 메모리 영역을 차지하고 저장
let hello = 'hello world'
let hi = hello
console.log(hellow === hi) //true
let hello = 'hello world'
let hi = 'hello world'
console.log(hellow === hi) //true
값을 전달하는 방식이 아닌 각각 선언하는 방식도 동일한 값으로 인식
객체
const hello = {
'greet' : 'hello, world'
}
const hi = {
'greet' : 'hellow, world'
}
console.log(hello === hi) // false
consoel.log(hello.greet === hi.greet) //true
값은 같아도 참조하는 곳이 다르다, 참조를 전달할 경우 원시값에서와 동일한 결과를 보여준다.
const hello = {
'greet' : 'hello, world',
}
const hi = hello
console.log(hi === hello) // true
1.1.3 자바스크립트의 또 다른 비교 공식 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({}, {}) //false
const a = {'hello' : 'hi'}
const b = a
Object.is(a, b) // true
a === b //true
1.1.4 리액트에서의 동등 비교
1. 리액트에서의 동등 비교 방식
리액트는 JavaScript의 일반적인 동등 비교 연산자(==, ===)를 사용하지 않고, Object.is를 사용합니다.
Object.is 사용 이유
- JavaScript의 ===로는 특정 케이스(예: NaN, 0과 +0 비교)를 정확히 처리할 수 없음
- 브라우저 호환성을 위해 폴리필 구현도 함께 사용
리액트의 objectIs 구현
function is(x, y) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
const objectIs = typeof Object.is === 'function' ? Object.is : is;
2. 리액트의 얕은 비교(Shallow Equal)
리액트는 Object.is를 기반으로 얕은 비교를 수행하는 shallowEqual 함수를 구현하여 사용합니다.
shallowEqual 작동 방식
- 먼저 Object.is로 두 값을 비교
- 두 값이 객체인 경우 객체의 1-depth까지만 비교 (얕은 비교)
- 객체의 키 개수 비교
- 각 키에 해당하는 값들을 Object.is로 비교
얕은 비교의 예
// 기본 Object.is 비교
Object.is({ hello: 'world' }, { hello: 'world' }) // false (참조가 다름)
// 리액트의 shallowEqual 비교
shallowEqual({ hello: 'world' }, { hello: 'world' }) // true (1-depth까지 비교)
// 중첩 객체는 비교 불가
shallowEqual({ hello: { hi: 'world' } }, { hello: { hi: 'world' } }) // false
3. 왜 얕은 비교만 구현했을까?
리액트가 얕은 비교만 구현한 이유
- 성능 최적화: 깊은 비교는 재귀적으로 수행해야 하며 성능에 악영향을 줄 수 있음
- 일반적 사용 패턴: 리액트 props는 주로 1-depth 객체로 전달되는 경우가 많음
- 예측 가능성: 얕은 비교는 동작이 명확하고 예측 가능함
4. React.memo에서의 동등 비교 문제
React.memo는 내부적으로 shallowEqual을 사용하기 때문에, 중첩된 객체를 props로 전달할 경우 메모이제이션이 제대로 작동하지 않을 수 있습니다.
예시 문제
// 정상 작동하는 경우 (1-depth props)
<Component counter={100} />
// 메모이제이션이 제대로 작동하지 않는 경우 (2-depth props)
<DeeperComponent counter={{ counter: 100 }} />
이유
- Component의 경우: counter가 변경되지 않았으므로 리렌더링 방지
- DeeperComponent의 경우: 매 렌더링마다 { counter: 100 }이 새로운 객체로 생성되어 얕은 비교에서 항상 다른 것으로 인식됨
5. 주의사항 및 최적화 팁
- 중첩 객체 전달 지양하기 : props로 중첩 객체를 전달하는 것은 피하는 것이 좋음
- 원시 값 사용하기 : 가능한 원시 값(string, number, boolean)을 props로 전달
- 메모이제이션 활용 : 객체는 useMemo, 함수는 useCallback을 사용하여 참조 일관성 유지
- 커스텀 비교 함수 사용 : 필요한 경우 React.memo의 두 번째 인자로 커스텀 비교 함수 제공
'FRONTEND > React' 카테고리의 다른 글
| 리액트 훅 | useState에 대해 알아보기 (4) | 2025.08.08 |
|---|---|
| 부모 요소 패딩 값에 영향을 받지 않고 전체 너비 구분선 그리기 (0) | 2025.04.04 |
| 리액트 훅(React Hook) 이해하기 (1) | 2025.01.19 |
| 리액트 컴포넌트 기반 구조 이해하기(WITH 재사용성, 유지보수성) (0) | 2025.01.05 |
| React 프로젝트 생성 with Vite & Tailwind (2) | 2024.12.26 |