Skip to content
On this page

2022년 04월 26일

수정하기
문서 생성 2022-04-26 17:43:19 최근 수정 2022-04-27 23:43:23

📚 오늘 도전하고, 배운 것

알고리즘 문제

React

컴포넌트의 업데이트

  • 부모 컴포넌트가 상태가 변경되어 다시 렌더링될 때 자식 컴포넌트 또한 다시 렌더링된다.
  • 자식 컴포넌트의 재실행이 불필요한 경우가 있을 수 있다. 부모 컴포넌트의 상태와 상관이 없어서 다시 렌더링되지 않아도 되는 경우에 큰 프로젝트라면 성능에 영향을 미칠 수 있어 최적화가 필요하다.
  • 특정 상황에 자식 컴포넌트를 다시 렌더링하도록 리액트에게 지시할 수 있다.
    • 특정 상황이란, 전달받은 props의 값이 변경된 경우 같은 것

React.memo

props가 변경된 경우에만 컴포넌트를 다시 렌더링 하려면 컴포넌트를 React.memo로 감싸주면 된다.

export default React.memo(Button)

인자로 들어간 컴포넌트에 어떤 props가 입력되었는지 확인하고 새로 입력되는 props 값을 확인해서 비교한다. props가 변경된 경우에만 해당 컴포넌트를 다시 렌더링한다. 즉, 부모 컴포넌트가 다시 렌더링되었지만 props가 변경되지 않았다면 컴포넌트는 재실행되지 않는 것이다.

최적화가 가능하다면, 왜 모든 컴포넌트에 React.memo를 사용하지 않는 걸까? 최적화에는 비용이 따르기 때문이다. React.memo로 감싼 컴포넌트는 매번 기존 props와 새 props를 비교한다. 해당 값을 저장할 공간과 비교하는 작업도 필요하다. 그래서 컴포넌트를 재평가하는데 필요한 성능 비용과 props를 비교하는 성능 비용의 등가교환이다. 무조건 모든 컴포넌트에 사용하는 것보다 개발하면서 상황에 맞게 사용하는 것이 훨씬 효과적일 것이다. 부모 컴포넌트가 다시 렌더링될 때마다 props 값이 변경될 경우에 사용해봤자 의미가 없다. 왜냐하면 매번 다시 렌더링되야하기 때문이다...

useCallback

간혹 React.memo를 사용했음에도 다시 렌더링되는 경우를 볼 수 있다. props로 함수를 전달하면 나타나는 일이다. 무슨 일일까? 예를 들어보자. 부모 -> 자식 컴포넌트로 함수가 전달되는 상황이다. 부모 컴포넌트가 다시 렌더링될 때 함수와 상수(const로 선언한 변수) 등은 매번 재생성된다. 재생성된 후 props로 전달될 때 함수의 경우는 객체이므로 원시값이 아니기 때문에 React.memo로 props를 비교하는 과정에서 기존 props와 다른 값으로 인식된다.

// 원시값은 비교하면 같다.
const a = 1
const b = 1
a === b // true
// 객체의 경우는 다르다.
const c = {}
const d = {}
c === d // false

그래서 자식 컴포넌트가 React.memo를 사용해도 다시 렌더링되는 것이다! 그렇다면 이럴 때는 사용할 수 없을까? useCallback 훅을 사용하면 된다.

함수를 저장해두고 매번 실행할 때마다 함수를 재생성할 필요가 없다는 것을 useCallback을 이용해 알릴 수 있다. 함수를 저장해두고 확인하면 같기 때문이다.

const c = {}
const d = c
console.log(c === d) // true

useCallback은 다음과 같이 사용한다.

import React, { useState, useCallback } from 'react'
const toggleMenuHandler = useCallback(() => {
setShowMenu((prevShowMenu) => !prevShowMenu)
}, [])

useCallback의 두 번째 인자로 useEffect 처럼 의존성 배열을 전달할 수 있는데 배열이 빈 값이라면 컴포넌트가 다시 렌더링되더라도 항상 해당 함수는 재생성되지 않고 같은 함수 객체를 사용되게 한다.

빈 의존성 배열을 사용해 함수를 그대로 사용할 수 있는데 왜 배열이 필요할까? 다음과 같은 경우를 생각해보자.

function App() {
const [showMenu, setShowMenu] = useState(false)
const [enableButton, setEnableButton] = useState(false)
const toggleMenuHandler = useCallback(() => {
if (enableButton) {
setShowMenu((prevShowMenu) => !prevShowMenu)
}
}, [])
const enableButtonHandler = () => {
setEnableButton(true)
}
}

toggleMenuHandler라는 함수에 useCallback()을 사용해서 해당 함수를 사용하는 컴포넌트가 재렌더링되지 않도록 한다. 그리고 toggleMemuHandler는 다른 enableButton 상태가 true인 경우에만 showMenu 상태를 변경할 수 있다.
위 코드의 의도는 enableButtonHandler 함수를 실행하고 나면 toggleMenuHandler함수를 실행해 showMenu 상태를 변경할 수 있는 것이다. 하지만 원하는대로 작동하지 않는다. toggleMenuHandler 함수에 클로저가 만들어졌기 때문에 해당 함수 내부의 enableButton은 처음 함수가 만들어졌을 때 저장된 그대로의 값이다. 그래서 변경이 되지 않는다. 이처럼 함수가 재생성되야 할 필요가 있는 경우가 있다. 이때 의존성 배열에 enableButton을 넣는다. enableButton이 변경되지 않는다면 함수는 재생성되지 않는다.

🤔 학습하면서 궁금하거나 어려웠던 점

  • React에서 컴포넌트가 다시 렌더링되면 그 내부 변수들도 다시 생성될 텐데 useState는 어떻게 값을 유지할까?

🌅 내일은 무엇을?

  • React 학습
  • 알고리즘 학습 & 문제 풀이

🖋 log

  • 봄비가 엄청나게 쏟아졌다. 그래서 집마당에 밥을 먹으러 오는 고양이에게 밥을 못 줬는데 저녁에 비가 그칠 때 쯤 애처롭게 현관문 앞에 서 있는 것을 발견했다. 밥 주는 사람이라는 걸 아는지, 그래도 여전히 다가가면 피하지만 오늘은 너무 배가 고팠는지 도망가지 않았다. 사람이나 고양이나 밥이 중요하다.