useCallback으로 리액트 컴포넌트 성능최적화하기

모아나·2025년 4월 10일
0

react dnd를 사용하여 드래그 앤 드랍을 구현하던 중, 함수가 매번 재생성되어 불필요한 재랜더링을 발생시키는 코드를 만들게 되었다.

다음 코드는 드래그 앤 드롭 기능을 가진 아이템들의 컨테이너를 구현한 코드다.

최적화 전

'use client'

import React, { useState } from 'react'
import update from 'immutability-helper'

import CardContainer from '@/components/CardContainer'
import ErrorList from '@/components/ErrorList'
import ErrorSummaryCard from './ErrorSummaryCard'

export default function DashboardContainer() {
  // useState
  const [items, setItems] = useState([
    {
      id: 'item-1',
      title: 'Error Overview',
      component: <ErrorList />,
    },
    { id: 'item-2', title: 'Monthly Uptime' },
    { id: 'item-3', title: 'Error Summary', component: <ErrorSummaryCard /> },
    { id: 'item-4', title: 'Error Overview' },
    { id: 'item-5', title: 'Monthly Uptime' },
    { id: 'item-6', title: 'Error Summary' },
  ])

  // functions
  const moveItem = (dragIndex, hoverIndex) => {
    // Swap the index of the dragged item with the dropped item
    setItems(prevItems =>
      update(prevItems, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, prevItems[dragIndex]],
        ],
      }),
    )
  }

  const renderCardItem = (id, title, component, index) => (
    // Render CardItem
    <CardContainer
      key={id}
      id={id}
      title={title}
      index={index}
      moveItem={moveItem}
    >
      {component && component}
    </CardContainer>
  )

  return (
    <div className="grid w-full grid-cols-3 gap-4 text-white">
      {items.map(({ id, title, component }, index) =>
        renderCardItem(id, title, component, index),
      )}
    </div>
  )
}

moveItem 은 드래그한 아이템의 인덱스와 드롭한 아이템의 인덱스를 바꿔주는 함수이고 이를 드래그 가능한 하위 컴포넌트에게 props로 넘겨주고 있다.

renderCardItem은 드래그할 아이템 목록의 렌더링 컴포넌트다.

이 코드에서는 moveItemrenderCardItem 함수가 매 렌더링마다 새로 생성된다.
이는 하위 컴포넌트가 props로 받은 함수가 변경되었다고 생각하여 불필요한 재렌더링을 발생시키게 됩니다.

물론 드래그할 아이템이 많지 않다면 문제되지 않지만, 몇 백개가 있다고 하면 성능최적화가 필요된다.

useCallback으로 최적화하기

이 문제를 해결하기 위해 useCallback을 사용해 함수 메모이제션을 적용한다. 이는 불필요한 함수 재생성을 막고, 하위 컴포넌트의 불필요한 재렌더링도 막아 성능을 최적화할 수 있다.

useCallback?

useCallback은 리렌더링 간에 함수 정의를 캐싱해주는 리액트 훅이다.

정의는 다음과 같이 한다.
useCallback(fn, dependencies)

fn: 캐싱할 함수값. 리액트 첫 렌더링에서 이 함수를 반환하며 다음 렌더링에서 dependencies 값이 이전과 같다면 같은 함수를 반환한다. 반대로 값이 변경되었다면 해당 렌더링 때에는 전달한 함수를 반환하고 다음에는 재사용할 수 있도록 저장한다. 리액트는 함수를 호출하지 않고 개발자가 함수의 호출 여부와 시점을 결정한다.

dependencies: fn 내에서 참조되는 모든 반응형 값의 목록이다. 이 값은 props, state, 컴포넌트 안에서 직접 선언된 모든 변수와 함수를 포함한다.

'use client'

import React, { useCallback, useState } from 'react'
import update from 'immutability-helper'

import CardContainer from '@/components/CardContainer'
import ErrorList from '@/components/ErrorList'
import ErrorSummaryCard from './ErrorSummaryCard'

export default function DashboardContainer() {
  // useState
  const [items, setItems] = useState([
    {
      id: 'item-1',
      title: 'Error Overview',
      component: <ErrorList />,
    },
    { id: 'item-2', title: 'Monthly Uptime' },
    { id: 'item-3', title: 'Error Summary', component: <ErrorSummaryCard /> },
    { id: 'item-4', title: 'Error Overview' },
    { id: 'item-5', title: 'Monthly Uptime' },
    { id: 'item-6', title: 'Error Summary' },
  ])

  // functions
  const moveItem = useCallback((dragIndex, hoverIndex) => {
    // Swap the index of the dragged item with the dropped item
    setItems(prevItems =>
      update(prevItems, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, prevItems[dragIndex]],
        ],
      }),
    )
  }, [])

  const renderCardItem = useCallback(
    (id, title, component, index) => (
      // Render CardItem
      <CardContainer
        key={id}
        id={id}
        title={title}
        index={index}
        moveItem={moveItem}
      >
        {component && component}
      </CardContainer>
    ),
    [moveItem],
  )

  return (
    <div className="grid w-full grid-cols-3 gap-4 text-white">
      {items.map(({ id, title, component }, index) =>
        renderCardItem(id, title, component, index),
      )}
    </div>
  )
}

참고
https://ko.react.dev/reference/react/useCallback

profile
Make things

0개의 댓글