리액트

[React] 렌더링 최적화

코딩하는둥이 2025. 4. 16. 15:12

컴포넌트는 자신의 state가 변경되거나 부모에게서 받는 props가 변경되었을 때마다 다시 랜더링됩니다.

심지어 자식 컴포넌트에서 렌더링 최적화를 위한 별도의 코드를 추가하지 않으면, 부모에데거 받는 props가 변경되지 않았더라도 다시 리렌더링 됩니다.

 

함수 컴포넌트는 그냥 함수입니다.  함수컴포넌트가 헨더링 된다는 것은 부모컴포넌트가 그 함수를 호출하여 실행되는 것입니다.

함수가 실행될 때마다 내부에 선언되어 있던 표현식들도 매번 다시 계산이 됩니다.

 

 

useMemo란?

 복잡한 계산(예: 큰 배열 정렬, 데이터 변환)의 결과를 캐싱합니다.

 동일한 입력값에 대해 동일한 출력을 반환하는 순수 함수에 적합합니다.

 

 

info.js

아래코드는 색값만 변경되어도 colorKor과 movieGenreKor이 실행되고 영화값만 바꿔도 colorKor과 movieGenreKor이 실행되고 있습니다.

import React, { useMemo } from "react";
import "./styles.css";

const getColorKor = (color) => {
  console.log("getColorKor");
  switch (color) {
    case "red":
      return "빨강";
    case "orange":
      return "주황";
    case "yellow":
      return "노랑";
    case "green":
      return "초록";
    case "blue":
      return "파랑";
    case "navy":
      return "남";
    case "purple":
      return "보라";
    default:
      return "레인보우";
  }
};

const getMovieGenreKor = (movie) => {
  console.log("getMovieGenreKor");
  switch (movie) {
    case "Marriage Story":
      return "드라마";
    case "The Fast And The Furious":
      return "액션";
    case "Avengers":
      return "슈퍼히어로";
    default:
      return "아직 잘 모름";
  }
};

const Info = ({ color, movie }) => {
  const colorKor = getColorKor(color);
  const movieGenreKor = getMovieGenreKor(movie);

  return (
    <div className="info-wrapper">
      제가 가장 좋아하는 색은 {colorKor} 이고, <br />
      즐겨보는 영화 장르는 {movieGenreKor} 입니다.
    </div>
  );
};

export default Info;

 

useMemo를 사용하면 []안에 있는 값만 변경될 때마다 반환하도록 수정했습니다. 그렇게 된다면 불필요한 리랜더링이 발생하지 않습니다.

import React, { useMemo } from "react";
import "./styles.css";

const getColorKor = (color) => {
  console.log("getColorKor");
  switch (color) {
    case "red":
      return "빨강";
    case "orange":
      return "주황";
    case "yellow":
      return "노랑";
    case "green":
      return "초록";
    case "blue":
      return "파랑";
    case "navy":
      return "남";
    case "purple":
      return "보라";
    default:
      return "레인보우";
  }
};

const getMovieGenreKor = (movie) => {
  console.log("getMovieGenreKor");
  switch (movie) {
    case "Marriage Story":
      return "드라마";
    case "The Fast And The Furious":
      return "액션";
    case "Avengers":
      return "슈퍼히어로";
    default:
      return "아직 잘 모름";
  }
};

const Info = ({ color, movie }) => {
  const colorKor = useMemo(() => getColorKor(color), [color]);
  const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);

  return (
    <div className="info-wrapper">
      제가 가장 좋아하는 색은 {colorKor} 이고, <br />
      즐겨보는 영화 장르는 {movieGenreKor} 입니다.
    </div>
  );
};

export default Info;

 

다른  방법도 있는데 useCallback를 사용하면 됩니다.  

 

useCallback란?

React에서 함수를 메모이제이션하여 불필요한 함수 재생성을 방지하고, 컴포넌트의 성능을 최적화하는 데 사용됩니다. 주로 자식 컴포넌트에 콜백 함수를 전달할 때 유용합니다.

import React, { useState, useCallback } from "react";
import Info from "./Info";
import "./styles.css";

const App = () => {
  const [color, setColor] = useState("");
  const [movie, setMovie] = useState("");

  const onChangeHandler = useCallback((e) => {
    if (e.target.id === "color") setColor(e.target.value);
    else setMovie(e.target.value);
  }, []);

  return (
    <div className="App">
      <div>
        <label>
          What is your favorite color of rainbow ?
          <input id="color" value={color} onChange={onChangeHandler} />
        </label>
      </div>
      <div>
        What is your favorite movie among these ?
        <label>
          <input
            type="radio"
            name="movie"
            value="Marriage Story"
            onChange={onChangeHandler}
          />
          Marriage Story
        </label>
        <label>
          <input
            type="radio"
            name="movie"
            value="The Fast And The Furious"
            onChange={onChangeHandler}
          />
          The Fast And The Furious
        </label>
        <label>
          <input
            type="radio"
            name="movie"
            value="Avengers"
            onChange={onChangeHandler}
          />
          Avengers
        </label>
      </div>
      <Info color={color} movie={movie} />
    </div>
  );
};

export default App;

 

React.memo

React에서 컴포넌트를 메모이제이션하여 불필요한 리렌더링을 방지하고 성능을 최적화하는 고급 기능입니다. 주로 순수 컴포넌트에 사용되며, props가 변경되지 않은 경우 이전 렌더링 결과를 재사용합니다.

 

코드를 수정하기 전에는 click를 클릭할 때마다 child component가 실행되었습니다. 

import "./styles.css";
import React, { useState } from "react";

export default function Parent() {
  const [number, setNumber] = useState(1);
  const age = 1;

  return (
    <div className="App">
      <button
        onClick={() => {
          setNumber(number + 1);
        }}
      >
        Click
      </button>
      <Child age={age} />
    </div>
  );
}

const Child = (props) => {
  console.log("Child component");
  console.log(props.age);
  return <div>Child</div>;
});

 

 

하지만, memo를 사용하면 click를 클릭할 때마다 child component가 실행되지 않습니다.

import "./styles.css";
import React, { useState } from "react";

export default function Parent() {
  const [number, setNumber] = useState(1);
  const age = 1;

  return (
    <div className="App">
      <button
        onClick={() => {
          setNumber(number + 1);
        }}
      >
        Click
      </button>
      <Child age={age} />
    </div>
  );
}

const Child = React.memo((props) => {
  console.log("Child component");
  console.log(props.age);
  return <div>Child</div>;
});

 

Code Splitting이란?

애플리케이션의 JavaScript 파일을 여러 개로 나누어 필요한 시점에만 로드하는 방법입니다.

 

React.lazy 

컴포넌트를 동적으로 가져오도록 설정합니다.

 번들 크기를 줄이고, 필요한 시점에만 컴포넌트를 로드합니다.

const ProfilePage = React.lazy(() => import('./ProfilePage'));

 

Suspense

 React.lazy와 함께 사용하여 로딩 중 상태를 처리합니다. 

fallback 속성을 통해 로딩 중 UI를 표시합니다.

<Suspense fallback={<div>Loading...</div>}>
  <ProfilePage />
</Suspense>