1.1.4 리액트에서의 동등 비교
- 리액트에서 사용하는 동등 비교는 ==나 ===가 아닌 Object.is
- Object.is는 ES6에서 제공하는 기능이기 때문에 리액트에서는 이를 구현한 폴리필(Polyfill)을 함께 사용(웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드)
objectIs
- 아래는 리액트에서 실제로 값을 비교할 때 사용하는 코드
// flow로 구현돼 있어 any가 추가돼 있음. flow에서 any는 타입스크립트와 동일하게 어떠한 값도 받을 수 있는 타입을 의미함 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 ) }
/**
- ChatGPT의 도움!
- 이 코드는 두 개의 값
x
와y
가 서로 동일한지를 확인하는 함수입니다. 이 함수는 JavaScript의 동등성 비교를 통해 두 값이 같은지 여부를 확인합니다. - 여기서 사용된 조건들을 하나씩 살펴보겠습니다:
x === y
:x
와y
가 동등한지 비교합니다. 즉, 값 뿐만 아니라 타입도 일치해야 합니다.
(x !== 0 || 1 / x === 1 / y)
: 만약x
가 0이 아니면,x
와y
가 숫자라면 그들의 역수가 서로 같은지를 비교합니다. 이 부분은 0으로 나누는 경우를 피하기 위한 안전장치입니다.
(x !== x && y !== y)
:x
와y
가 NaN (Not-a-Number) 인지를 확인합니다. NaN은 자기 자신과도 동일하지 않습니다.
- 따라서 이 함수는
x
와y
가 동등하면true
를 반환하며, 동등하지 않으면false
를 반환합니다. - /
// 런타임에 Object.is가 있다면 그것을 사용하고, 아니라면 위 함수 사용
// Object.is는 인터넷 익스플로러 등에 존재하지 않기 때문에 폴리필을 넣어준 것으로 보임
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is
export default objectIs
## shallowEqaul
- 리액트에서는 이 objectIs를 기반으로 동등 비교를 하는 shallowEqual 함수를 만들어 사용함
- 이 shallowEqual 함수는 의존성 비교 등 리액트의 동등 비교가 필요한 다양한 곳에서 사용
- ```js
import is from './objectIs'
// 다음 코드는 Object.prototype.hasOwnProperty
// 이는 객체에 특정 프로퍼티가 있는지 확인하는 메서드
import hasOwnProperty from './hasOwnProperty'
/**
* 주어진 객체의 키를 순회하면서 두 값이 엄격한 동등성을 가지는지 확인하고
* 다른 값이 있다면 false 반환. 만약 두 객체 간에 모든 키의 값이 동일하다면 true 반환.
*/
// 단순히 Object.is를 수행하는 것뿐 아니라 객체 간의 비교도 추가돼있음
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)
// 배열의 길이가 다르다면 false
if (keysA.length !== keysB.length) {
return false
}
// A의 키를 기준으로, B에 같은 키가 있는지, 그리고 그 값이 같은지 확인
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i]
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false
}
}
return true
}
export default shallowEqual
리액트에서의 비교를 요약하자면 Object.is로 먼저 비교를 수행한 다음에 Object.is에서 수행하지 못하는 비교, 즉 객체 간 얕은 비교(객체의 첫 번째 깊이에 존재하는 값만 비교)를 한 번 더 수행함
// Object.is는 참조가 다른 객체에 대해 비교가 불가능 Object.is({ hello: 'world' }, { hello: 'world' }) // false // shallowEqual은 객체의 1 depth까지는 비교 가능 shallowEqual({ hello: 'world' }, { hello: 'world' }) // true // 그러나 2 depth까지 가면 이를 비교할 방법이 없으므로 false 반환 shallowEqual({ hello: { hi: 'world' } }, { hello: { hi: 'world' } }) // false
객체의 얕은 비교까지만 구현한 이유? 먼저 리액트에서 사용하는 JSX props는 객체이고, 여기에 있는 props만 일차적으로 비교하면 되기 때문
type Props = { hello: string } function HelloComponent(props: Props) { return <h1>{hello}</h1> } function App() { return <HelloComponent hello="hi!" /> }
위 코드에서 props는 객체임. 그리고 기본적으로 리액트는 props에서 꺼내온 값을 기준으로 렌더링 수행하기 때문에 일반적인 케이스에서는 얕은 비교로 충분
이 특성 안다면, props에 또 다른 객체를 넘겨준다면 리액트 렌더링이 예상치 못하게 작동한다는 것 알 수 있음
import { memo, useEffect, useState } from 'react' type Props = { counter: number } const Component = memo((props: Props) => { useEffect(() => { console.log('Component has been rendered!') }) return <h1>{props.counter}</h1> }) type DeeperProps = { counter: { counter: number } } const DeeperComponent = memo((props: DeeperProps) => { useEffect(() => { console.log('DeeperComponent has been rendered!) }) return <h1>{props.counter.counter}</h1> }) export default function App() { const [, setCounter] = useState(0) function handleClick() { setCounter((prev) => prev + 1) } return ( <div className="App"> <Component counter={100} /> <DeeperComponent counter={{ counter: 100 }} /> <button onClick={handleClick}>+</button> </div> ) }
이와 같이 props가 깊어지는 경우, 즉 한 객체 안에 또 다른 객체가 있을 경우 React.memo는 컴포넌트에 실제로 변경된 값이 없음에도 불구하고 메모이제이션된 컴포넌트를 반환하지 못함
즉, Component는 props.counter가 존재하지만, DeeperComponent는 props.counter.counter에 props가 존재함
상위 컴포넌트인 App에서 버튼을 클릭해 강제로 렌더링을 일으킬 경우, shallowEqual을 사용하는 Component 함수는 위 로직에 따라 정확히 객체 간 비교를 수행해 렌더링을 방지해 주지만 DeeperComponent 함수는 제대로 비교하지 못해 memo가 작동하지 않는 모습을 볼 수 있음
만약 내부에 있는 객체까지 완벽하게 비교하기 위한 재귀문까지 넣었으면 객체 안에 객체가 몇 개까지 있는지 알 수 없으므로 이를 재귀적으로 비교하려 할 경우 성능에 악영향 미칠 것
'Modern React Deep Dive > 01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트' 카테고리의 다른 글
1.2.2 함수를 정의하는 4가지 방법 (0) | 2024.04.02 |
---|---|
1.2.1 함수란 무엇인가? (0) | 2024.04.02 |
1.1.3 자바스크립트의 또 다른 비교 공식, Object.is (0) | 2024.03.29 |
1.1.2 값을 저장하는 방식의 차이 (0) | 2024.03.29 |
1.1.1 자바스크립트의 데이터 타입 (0) | 2024.03.29 |