React

React 성능 최적화: useMemo, useCallback, React.memo

노야근 2025. 4. 1. 18:48
반응형

 

React 애플리케이션을 개발할 때 성능 최적화는 중요한 핵심 요소입니다. 렌더링 성능을 제대로 최적화하지 않으면 불필요한 리렌더링으로 인해 앱의 전반적인 반응성이 크게 저하될 수 있습니다. 이번 글에서는 TypeScript 기반으로 useMemo, useCallback, React.memo를 활용한 성능 최적화 방법을 상세히 알아보겠습니다.

 

💂‍♀️ React 성능 최적화가 필요한 이유

React는 상태(state)나 props가 변경될 때마다 컴포넌트를 자동으로 다시 렌더링합니다. 그러나 모든 렌더링이 실제로 필요한 것은 아닙니다. 불필요한 렌더링이 과도하게 발생하면 애플리케이션의 성능이 현저하게 저하될 수 있습니다. 이러한 문제를 예방하고 개선하기 위해 메모이제이션(memoization) 기법을 효과적으로 활용할 수 있습니다. 

 

React에서 제공하는 최적화를 위한 hook 

1. useMemo → 값을 메모이제이션하여 불필요한 연산 방지
2. useCallback → 함수를 메모이제이션하여 불필요한 함수 재생성 방지
3. React.memo → 컴포넌트를 메모이제이션하여 불필요한 리렌더링 방지

 

하나씩 예제를 살펴보겠습니다. 

 

1. useMemo : State의 메모제이션

useMemo는 많이 소요되는 연산 결과를 저장하고, 의존성이 변경되지 않는다면 재계산하지 않도록 합니다.

즉, 불필요한 연산을 방지할 수 있습니다. 그러나 잘못 사용하게되면 오히려 오버헤드가 발생할 수도 있습니다.

import { useState, useMemo } from "react";

const ExpensiveComponent = () => {
  const [count, setCount] = useState<number>(0);
  const [text, setText] = useState<string>("");

  // 연산 비용이 큰 작업 (ex: 배열의 합 구하기)
  const expensiveValue = useMemo(() => {
    console.log("expensiveValue 계산 중...");
    return Array.from({ length: 10000 }, (_, i) => i).reduce((a, b) => a + b, 0);
  }, []); // 의존성 배열이 빈 배열이므로 한 번만 계산됨

  return (
    <div>
      <p>비싼 연산 결과: {expensiveValue}</p>
      <button onClick={() => setCount(count + 1)}>Count 증가: {count}</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
};

export default ExpensiveComponent;

2. useCallback: 함수의 메모제이션

useCallback은 함수의 재생성을 방지하여, 자식 컴포넌트에 전달할 때 불필요한 리렌더링을 막아줍니다.
자식 컴포넌트가 불필요하게 렌더링되지 않으나 확실히 의존성이 많아지면 코드가 점점 복잡해 질 수 있습니다. 

import { useState, useCallback } from "react";

const ChildComponent = ({ onClick }: { onClick: () => void }) => {
  console.log("ChildComponent 렌더링");
  return <button onClick={onClick}>클릭</button>;
};

const ParentComponent = () => {
  const [count, setCount] = useState<number>(0);

  // useCallback을 사용하여 함수 재생성을 방지
  const handleClick = useCallback(() => {
    console.log("버튼 클릭!");
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

export default ParentComponent;

3. React.memo : 컴포넌트의 메모제이션

React.memo는 props가 변경되지 않는 한 컴포넌트를 다시 렌더링하지 않도록 합니다.
불필요한 리렌더링을 방지할 수 있지만 useMemo와 useCallback과 함께 사용해야하는 경우가 생기면 코드가 점점 복잡해집니다. 

import React, { useState } from "react";

const MemoizedChild = React.memo(({ count }: { count: number }) => {
  console.log("MemoizedChild 렌더링");
  return <p>카운트 값: {count}</p>;
});

const ParentComponent = () => {
  const [count, setCount] = useState<number>(0);
  const [text, setText] = useState<string>("");

  return (
    <div>
      <MemoizedChild count={count} />
      <button onClick={() => setCount(count + 1)}>+1</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
};

export default ParentComponent;

 

 

최적화의 중요성은 잘 알고 있지만 어떠한 상확에 적절하게 적용하는건 생각보다 어렵습니다. 모든 곳에 사용하지 말고 필요한 곳에 적절히 사용하는것이 중요해 보입니다. 😏

반응형