리액트에서의 동등비교

쏘뽀끼·2025년 1월 7일
0

react

목록 보기
22/25

리액트에서 동등 비교는 =====가 아닌 Object.is이다.
Object.is는 ES6에서 제공하는 기능이기 때문에 리액트에서는 이를 구현한 폴리필(Polyfill)을 함께 사용한다.

(폴리필(Polyfill)은 특정 웹 브라우저에서 지원하지 않는 최신 기능이나 API를, 구형 브라우저에서도 사용할 수 있도록 구현한 코드나 라이브러리를 말합니다. 리액트와 관련하여 폴리필은 주로 브라우저 간 호환성을 보장하기 위해 사용됩니다.)

리액트에서 값을 비교하는 함수은 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-disabled-line no-self-compare
    )
  }
  
  //런 타임에 Object.is가 있다면 그것을 사용하고, 아니라면 위 함수를 사용한다. 
  //Object.is는 인터넷 익스플로러 등에 존재하지 않기 때문에 폴리필을 넣어준 것으로 보인다.
  
const objectIs: (x:any, y: any) => boolean = 
	typeof Object.is === 'function' ? Object.is : is
    
export default objectIs

React는 브라우저 호환성을 위해 Object.is를 사용할 수 없는 환경에서도 동일한 기능을 제공하기 위해 폴리필을 포함시킨다.
typeof Object.is === 'function'으로 런타임에서 Object.is 지원 여부를 확인하고, 지원하지 않으면is 함수를 사용한다.
이 방식으로 React는 최신 브라우저와 구형 브라우저 모두에서 일관된 동작을 보장합니다.





리액트에서 값을 비교 - sahllowEqual

리액트에서는 이 objectIs를 기반으로 동등 비교를 하는 shallowEqual이라는 함수를 만들어서 사용한다.
shallowEqual은 의존성 비교 등 리액트의 동등 비교가 필요한 다양한 곳에서 사용된다.

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 fasle
 }
 
 //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 shllowEqual



리액트에서 비교를 요약하자면 Object.is로 먼저 비교를 수행한다.
그 다음에 Object.is에서 수행하지 못하는 비교, 즉 객체 간 얕은 비교를 한 번 더 수행하는 것을 알 수 있다.
객체 간 얕은 비교란 객체의 첫 번째 깊이에 존재하는 값만 비교한다는 것을 의미한다.

//Object.is는 참조가 다른 객체애 대해 비교가 불가능하다. 
Object.is({hello:'world'}, {hello: 'world'}) //false

//반면 리액트 팀에서 구현한 shallowEqual은 객체의 1depth까지는 비교가 가능하다. 
shallowEqual({hello: 'world'}, {hello: 'world'}) //true

//그러나 2depth까지 가면 이를 비교할 방법이 없으므로 false랄 반환한다.
shallowEqual({hello:{hi:'world'}}, {hello: {hi: 'world'}}) //false

이렇게 객체의 걑은 비교까지만 구현한 이유는 무엇일까?
리액트에서 사용하는 JSX props는 객체이고, 그리고 여기에 있는 props만 일차적으로 비교하면 되기 때문이다.

type Props = {
hello: string
}

function HelloComponent(props:Props){
 return <h1>{props.hello}</h1>
 }
 //...
 
 
 function App(){
  return <HelloComponent hello = "hi"/>
  }

위 코드에서 props는 객체다.
기본적으로 리액트는 props에서 꺼내온 값을 기준으로 렌더링을 수행하기 때문에 일반적인 케이스에서는 얕은 비교로 충분할 것이다.
이 특성을 알면, props에 또 다른 객체를 넘겨준다면 리액트 렌더링이 예상치 못하게 작동한다는 것을 알 수 있다.





React.memo의 깊은 비교 문제 예시

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 defualt 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는 컴포넌트에 실제로 변경된 값이 없음에도 불구하고 메모이제이션된 컴포는트를 반환하지 못한다.
Componentprops.counter가 존재하지만, DeeperComponentprops.counter.counterprops가 존재한다.

상위 컴포넌트인 App에서 버튼을 클릭해서 강제로 렌더링을 일으킬 경우, shallowEqual을 사용하는 Component함수는 위 로직에 따라 정확히 객체 간 비교를 수행해서 렌더링을 방지해 주었지만 DeeperComponent함수는 제대로 비교하지 못해 memo가 작동하지 않는 모습을 볼 수 있다.

만약 내부에 있는 객체까지 완벽하게 비교하기 위한 재귀문까지 넣었으면 어떻게 됐을까?
객체 안에 객체가 몇 개까지 있을지 알 수 없으므로 이를 재귀적으로 비교하려 할 경우 성능에 악영향을 미칠 것이다.

0개의 댓글