관리 메뉴

거니의 velog

(1) 컴포넌트 성능 최적화 1 본문

React/React_리액트 응용

(1) 컴포넌트 성능 최적화 1

Unlimited00 2023. 12. 6. 15:06

* 이전에는 학습한 지식을 활용하여 멋진 일정 관리 애플리케이션을 만들어 보았다. 현재까지는 이 애플리케이션을 사용할 때 불편하지 않았다. 추가되어 있는 데이터가 매우 적기 때문이다. 그러나 데이터가 무수히 많아지면, 애플리케이션이 느려지는 것을 체감할 수 있을 정도로 지연이 발생한다.

* 이번 실습은 다음과 같은 흐름으로 진행한다.


1. 많은 데이터 렌더링하기

* 우선 실제로 랙(lag)을 경험할 수 있도록 많은 데이터를 렌더링해 보자. 물론 데이터를 하나하나 직접 입력하지 않고 코드를 사용하여 쉽게 추가할 수 있다.

* App 컴포넌트를 다음과 같이 수정해 보자.

import React, { useCallback, useRef, useState } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

function createBulkTodos() {
  const array = [];
  for (let i = 1; i <= 2500; i++) {
    array.push({
      id: i,
      text: `할 일 ${i}`,
      checked: false,
    });
  }
  return array;
}

const App = () => {
  const [todos, setTodos] = useState(createBulkTodos);

  // 고윳값으로 사용될 id
  // ref를 사용하여 변수 담기
  const nextId = useRef(4);

  const onInsert = useCallback(
    (text) => {
      const todo = {
        id: nextId.current,
        text,
        checked: false,
      };
      setTodos(todos.concat(todo));
      nextId.current += 1; // nextId 1씩 더하기
    },
    [todos],
  );

  const onRemove = useCallback(
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id));
    },
    [todos],
  );

  const onToggle = useCallback(
    (id) => {
      setTodos(
        todos.map((todo) =>
          todo.id === id ? { ...todo, checked: !todo.checked } : todo,
        ),
      );
    },
    [todos],
  );

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};

export default App;

* 데이터를 하나하나 직접 입력할 수는 없으므로 createBulkTodos 라는 함수를 만들어서 데이터 2,500개를 자동으로 생성했다.

* 여기서 주의할 점은 useState의 기본값에 함수를 넣어 주었다는 것이다. 여기서 useState(createBulkTodos()) 라고 작성하면 리렌더링될 때마다 createBulkTodos 함수가 호출되지만, useState(createBulkTodos)처럼 파라미터를 함수 형태로 넣어 주면 컴포넌트가 처음 렌더링될 때만 createBulkTodos 함수가 실행될 것이다.

* 자, 이제 코드를 저장하면 다음과 같이 데이터 2,500 개가 나타날 것이다.

많은 데이터 렌더링 하기

* 데이터 2,500 개가 렌더링되었다면 항목 중 하나를 체크해 보자. 이전보다 느려진 것이 느껴지는가?


2. 크롬 개발자 도구를 통한 성능 모니터링

* 성능을 분석해야 할 때는 느려졌다는 느낌만으로 충분하지 않다. 정확히 몇 초가 걸리는지 확인해야 하는데, 크롬 개발자 도구의 Performance 탭을 사용하여 측정하면 된다. 크롬 개발자 도구의 Performance 탭을 열면 다음과 같이 녹화 버튼이 나타난다.

* 이 버튼을 누르고 '할 일 1' 항목을 체크한 다음, 화면에 변화가 반영되면 Stop 버튼을 누르자. 그러면 다음과 같이 성능 분석 결과가 나타난다.

* 성능 분석 결과에 나타난 Timing을 열어 보면 각 시간대에 컴포넌트의 어떤 작업이 처리되었는지 확인할 수 있다.

* App [update] 박스에 커서를 올려 보니 이 작업이 처리되는 데 1.02초가 걸렸다고 나타났다(처리 시간은 컴퓨터 환경에 따라 결과가 다르게 나타날 수 있다).

* 데이터가 2,500개 밖에 안 되는데도 불구하고 1.02초나 걸린다는 것은 성능이 매우 나쁘다는 의미이다. 이제 이를 최적화하는 방법을 알아보자.


3. 느려지는 원인 분석

* 컴포넌트는 다음과 같은 상황에서 리렌더링이 발생한다.

1. 자신이 전달받은 props가 변경될 때

2. 자신의 state가 바뀔 때

3. 부모 컴포넌트가 리렌더링 될 때

4. forceUpdate 함수가 실행될 때

* 지금 상황을 분석해 보면, '할 일 1' 항목을 체크할 경우 App 컴포넌트의 state가 변경되면서 App 컴포넌트가 리렌더링된다. 부모 컴포넌트가 리렌더링되었으니 TodoList 컴포넌트가 리렌더링되고 그 안의 무수한 컴포넌트들도 리렌더링된다.

* '할 일 1' 항목은 리렌더링되어야 하는 것이 맞지만, '할 일 2'부터 '할 일 2500' 까지는 리렌더링을 안 해도 되는 상황인데 모두 리렌더링되고 있으므로 이렇게 느린 것이다. 컴포넌트의 개수가 많지 않다면 모든 컴포넌트를 리렌더링해도 느려지지 않는데, 지금처럼 약 2,000개가 넘어가면 성능이 저하된다.

* 이럴 때는 컴포넌트 리렌더링 성능을 최적화해 주는 작업을 해 주어야 한다. 즉, 리렌더링이 불필요할 때는 리렌더링을 방지해 주어야 하는데, 어떻게 방지하는지 알아보도록 하자.