241230 기초복습2

Hwi·2024년 12월 30일

TIL

목록 보기
93/96

📗 자바스크립트의 동등 비교

  • 리액트 함수 컴포넌트와 훅을 반복적으로 작성하다 보면 의존성 배열(dependencies array)에 대한 고민이 생김
    보통은 리액트에서 제공하는 eslint-react-config에 선언돼 있는 react-hooks/exhaustive-deps의 도움을 받아 해당 배열을 채우곤 함

  • 이것이 어떤 식으로 작동하는지, 또한 왜 이러한 변수들을 넣어야 하는지 이해하지 못하는 경우가 많은데, 리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나가 바로 props의 동등 비교에 따른 결과
    이 props의 동등 비교는 객체의 얕은 비교를 기반으로 이루어짐

📗 값을 저장하는 방식의 차이

  • 원시 타입과 객체 타입의 가장 큰 차이점 = 값을 저장하는 방식 차이
    이 값을 저장하는 방식의 차이가 동등 비교를 할 때 차이를 만드는 원인
    먼저 원시 타입은 불변 형태의 값으로 저장된다. 이 값은 변수 할당 시점에 메모리 영역을 차지하고 저장된다

  • 반면 객체는 프로퍼티를 삭제, 추가, 수정할 수 있으므로 원시 값과 다르게 변경 가능한 형태로 저장되며, 값을 복사할 때도 값이 아닌 참조를 전달하게 됨

  • 객체는 값을 저장하는 게 아닌 참조를 저장하기 때문에 동일하게 선언했던 객체라 하더라도 저장하는 순간 다른 참조를 바라보기 때문에 false를 반환하게 됨

📗 자바스크립트의 또 다른 비교 공식, Object.is

  • Object.is는 두 개의 인수를 받으며, 이 인수 두 개가 동일한지 확인하고 반환하는 메서드

  • ==와 다른 점: == 비교는 같음을 비교하기 전에 양쪽이 같은 타입이 아니라면 비교할 수 있도록 강제로 형변환을 한 후에 비교함
    Object.is는 이러한 형변환 작업을 하지 않음

  • ===와 다른 점: -0 === +0 // true 를 나타낼 때,
    Object.is(-0, +0) // false 를 나타냄
    즉, 좀 더 개발자가 기대하는 방식으로 정확한 비교

📗 리액트에서의 동등 비교

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

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

  • 리액트에서의 비교를 요약하자면 Object.is로 먼저 비교를 수행한 다음에 Object.is에서 수행하지 못하는 비교, 즉 객체 간 얕은 비교를 한 번 더 수행하는 것을 알 수 있음

  • Object.is는 참조가 다른 객체에 대해 비교 불가능
    반면 리액트 팀에서 구현한 shallowEqual은 객체의 1 depth까지 비교 가능. 그러나 2 depth까지 가면 이를 비교할 방법이 없으므로 false를 반환

shallowEqual 코드 예시

Object.is({ hello: 'world' }, { hello: 'world' }) // false

shallowEqual({ hello: 'world' }, { hello: 'world' }) // true

shallowEqual({ hello: {hi: 'world'} }, { hello: { hi: 'world' } })
// false
  • 이렇게 객체의 얕은 비교까지만 구현한 이유: 리액트에서 사용하는 JSX props는 객체이고, 여기에 있는 props만 일차적으로 비교하면 되기 때문임

객체의 얕은 비교까지만 구현한 이유 코드 예시

type Props = {
	hello: string
}
function Component(props:Props) {
	return <h1>{props.hello}</h1>
}

// ...

function App() {
	return <Component 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 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가 작동하지 않는 모습을 볼 수 있음

  • 자바스크립트의 특징을 잘 숙지한다면 함수 컴포넌트에서 사용되는 훅의 의존성 배열의 비교, 렌더링 방지를 넘어선 useMemo와 useCallback의 필요성, 렌더링 최적화를 위해서 꼭 필요한 React.memo를 올바르게 작동시키기 위해 고려해야 할 것들을 쉽게 이해할 수 있음

profile
개발자가 되고 싶어~~~

0개의 댓글