icon안동민 개발노트

메모이제이션 및 리렌더링 최적화


 React 애플리케이션, 특히 Next.js를 사용한 대규모 프로젝트에서 성능 최적화는 중요한 과제입니다.

 이 절에서는 React의 메모이제이션 기법과 리렌더링 최적화 전략에 대해 알아보고, 이들이 애플리케이션 성능에 미치는 영향을 살펴보겠습니다.

useMemo 훅

 useMemo는 계산 비용이 높은 값을 메모이제이션하는 데 사용됩니다. 의존성 배열의 값이 변경될 때만 재계산을 수행합니다.

import { useMemo } from 'react'
 
function ExpensiveComponent({ data }) {
  const expensiveResult = useMemo(() => {
    return data.map(item => /* 복잡한 계산 */)
  }, [data])
 
  return <div>{expensiveResult}</div>
}

useCallback 훅

 useCallback은 함수를 메모이제이션하여 불필요한 리렌더링을 방지합니다. 주로 자식 컴포넌트에 전달되는 콜백 함수를 최적화할 때 사용됩니다.

import { useCallback } from 'react'
 
function ParentComponent({ data }) {
  const handleClick = useCallback(() => {
    console.log(data)
  }, [data])
 
  return <ChildComponent onClick={handleClick} />
}

React.memo를 사용한 컴포넌트 최적화

 React.memo는 컴포넌트를 메모이제이션하여 props가 변경되지 않았을 때 리렌더링을 방지합니다.

import React from 'react'
 
const MemoizedComponent = React.memo(function MyComponent(props) {
  // 컴포넌트 로직
})
 
export default MemoizedComponent

성능 프로파일링 도구

 Next.js에서는 React DevTools와 Chrome DevTools의 Performance 탭을 사용하여 성능을 분석할 수 있습니다. 또한, Next.js의 내장 성능 분석 도구를 활용할 수 있습니다.

// pages/_app.js
import { useReportWebVitals } from 'next/web-vitals'
 
function MyApp({ Component, pageProps }) {
  useReportWebVitals(metric => {
    console.log(metric)
  })
 
  return <Component {...pageProps} />
}
 
export default MyApp

8장 상태 관리 및 폼 처리와의 연관성

 메모이제이션과 리렌더링 최적화는 8장에서 다룬 상태 관리 및 폼 처리와 밀접하게 연관됩니다. 복잡한 상태 로직이나 대규모 폼에서 useMemouseCallback을 적절히 사용하면 불필요한 계산과 리렌더링을 방지할 수 있습니다.

 예를 들어, 폼 제출 핸들러를 useCallback으로 감싸거나, 복잡한 유효성 검사 로직을 useMemo로 최적화할 수 있습니다.

import { useMemo, useCallback } from 'react'
 
function ComplexForm({ initialData }) {
  const [formData, setFormData] = useState(initialData)
 
  const validateForm = useMemo(() => {
    // 복잡한 유효성 검사 로직
  }, [formData])
 
  const handleSubmit = useCallback((event) => {
    event.preventDefault()
    // 폼 제출 로직
  }, [formData])
 
  return (
    <form onSubmit={handleSubmit}>
      {/* 폼 필드들 */}
    </form>
  )
}

실습 : 대량 데이터 리스트 컴포넌트 최적화

 대량의 데이터를 렌더링하는 리스트 컴포넌트를 최적화하는 실습을 해보겠습니다.

  1. 먼저, 대량의 데이터를 생성하는 함수를 만듭니다.
function generateData(count) {
  return Array.from({ length: count }, (_, index) => ({
    id: index,
    name: `Item ${index}`,
    value: Math.floor(Math.random() * 100)
  }))
}
  1. 최적화된 리스트 아이템 컴포넌트를 만듭니다.
const ListItem = React.memo(function ListItem({ item, onItemClick }) {
  console.log(`Rendering item ${item.id}`)
  return (
    <li onClick={() => onItemClick(item.id)}>
      {item.name}: {item.value}
    </li>
  )
})
  1. 최적화된 리스트 컴포넌트를 만듭니다.
'use client'
 
import React, { useMemo, useCallback, useState } from 'react'
 
function OptimizedList({ count }) {
  const [selectedId, setSelectedId] = useState(null)
 
  const data = useMemo(() => generateData(count), [count])
 
  const handleItemClick = useCallback((id) => {
    setSelectedId(id)
  }, [])
 
  return (
    <div>
      <h2>Optimized List (Item count: {count})</h2>
      <ul>
        {data.map(item => (
          <ListItem 
            key={item.id} 
            item={item} 
            onItemClick={handleItemClick}
          />
        ))}
      </ul>
      {selectedId !== null && <p>Selected Item ID: {selectedId}</p>}
    </div>
  )
}
 
export default OptimizedList
  1. 페이지에서 최적화된 리스트 컴포넌트를 사용합니다.
// app/page.js
'use client'
 
import { useState } from 'react'
import OptimizedList from '../components/OptimizedList'
 
export default function Home() {
  const [count, setCount] = useState(1000)
 
  return (
    <div>
      <h1>Large List Optimization Demo</h1>
      <input 
        type="number" 
        value={count} 
        onChange={(e) => setCount(Number(e.target.value))}
        min="1"
      />
      <OptimizedList count={count} />
    </div>
  )
}

 이 실습에서는 다음과 같은 최적화 기법을 사용했습니다.

  1. React.memo를 사용하여 ListItem 컴포넌트를 메모이제이션합니다.
  2. useMemo를 사용하여 데이터 생성을 최적화합니다.
  3. useCallback을 사용하여 handleItemClick 함수를 메모이제이션합니다.

 이러한 최적화를 통해 대량의 데이터를 렌더링할 때 성능을 크게 향상시킬 수 있습니다. 특히 리스트의 개별 항목이 변경되지 않았을 때 불필요한 리렌더링을 방지합니다.

 메모이제이션과 리렌더링 최적화는 React와 Next.js 애플리케이션의 성능을 크게 향상시킬 수 있는 강력한 도구입니다. 하지만 모든 상황에서 이러한 최적화가 필요한 것은 아니며, 때로는 오히려 성능을 저하시킬 수 있습니다. 따라서 성능 프로파일링 도구를 사용하여 실제로 최적화가 필요한 부분을 식별하고, 적절하게 적용하는 것이 중요합니다. 또한, 코드의 가독성과 유지보수성을 해치지 않는 선에서 최적화를 수행해야 합니다.