7c. 컴포넌트 업데이트 최적화

리액트에선 부모 컴포넌트가 업데이트 되면, 버추얼 돔에 일단 새로 그립니다. 버추올 돔에 그리는 것 자체는 실제 브라우저 렌더링 엔진이랑 관계없이, 자바스크립트만 실행 시키는 것이기 때문에 브라우저를 크게 과부하 시키지는 않지만, 렌더링 될 컴포넌트가 엄청나게 많다면, 잠재적인 렉 현상을 초래시킬 수도 있습니다.

한번, 그 과부하 현상을 직접 경험해봅시다.

Edit React Basics

미리보기 페이지

App.js

import React, { Component } from 'react';
import CreateForm from './components/CreateForm';
import TodoList from './components/TodoList';

import './App.css';

// 함수를 선언후 바로 호출하는, IIFE 패턴
const bulkTodos = (() => {
  const array = [];
  for (let i = 0; i < 5000; i++) {
    array.push({
      id: i,
      text: `Todo #${i}`,
      checked: false
    });
  }
  return array;
})();

class App extends Component {
  id = 5000;
  state = {
    todos: bulkTodos
  };

  handleCreate = text => {
    const todoData = {
      id: this.id,
      text,
      checked: false
    };
    this.id += 1;
    this.setState({
      todos: this.state.todos.concat(todoData)
    });
  };

  handleCheck = id => {
    this.setState(({ todos }) => ({
      todos: todos.map(
        todo => (todo.id === id ? { ...todo, checked: !todo.checked } : todo)
      )
    }));
  };

  handleRemove = id => {
    this.setState(({ todos }) => ({
      todos: todos.filter(todo => todo.id !== id)
    }));
  };

  render() {
    return (
      <div className="App">
        <div className="header">
          <h1>오늘 뭐할까?</h1>
        </div>
        <CreateForm onSubmit={this.handleCreate} />
        <div className="white-box">
          <TodoList
            todos={this.state.todos}
            onCheck={this.handleCheck}
            onRemove={this.handleRemove}
          />
        </div>
      </div>
    );
  }
}

export default App;

기본 값을 5000 개로 만들었습니다.

한번 아이템중 아무거나 눌러보세요. 좀 느릴 것입니다.

리액트 개발모드에서는, 개발자 도구를 통해 성능을 직접 확인 할 수도 있습니다.

개발자도구에서 Performance 를 누르세요. 그리고 좌측 상단의 녹화 버튼을 누른다음에 투두 아이템을 토글하고 다시 녹화버튼을 누릅니다. 그러면, 이렇게 결과를 볼 수 있습니다.

리액트 관련 성능은, User Timing 부분에서 볼 수있는데, 하나 업데이트하는데 자바스크립트 부분에 429ms 가 소요됩니다.

현재 개발 모드이기때문에 429ms 나 소요 된 것이고, 프로덕션에서는 실제로는 43ms 정도 소요됩니다.

이렇게 수많은 데이터를 렌더링 할 일이 발생한다면, shouldComponentUpdate 를 통하여 최적화를 해줘야합니다.

우선 TodoItemList 에서 TodoItem 에 todo 값을 통째로 props 로 전달하세요.

사실 지금의 상황에선 단순히 checked 값만 바뀌었는지 확인하면 되긴 하지만, 실제로 여러분이 비슷한 작업을 하게 될 때에는 여러 값을 비교해야 될 지도 모릅니다. 그러한 상황에서는 전달받은 props 를 모두 비교를 하는게 아니라, 우리가 불변성을 유지하면서 데이터를 업데이트하기에, 해당 컴포넌트에서 값을 받아오는 객체 자체를 비교하는게 효율적입니다.

components/TodoList.js

import React, { Fragment } from 'react';
import TodoItem from './TodoItem';

const TodoList = ({ todos, onCheck, onRemove }) => {
  const todoList = todos.map(todo => (
    <TodoItem
      key={todo.id}
      id={todo.id}
      checked={todo.checked}
      text={todo.text}
      onCheck={onCheck}
      onRemove={onRemove}
      todo={todo}
    />
  ));

  return todoList;
};

export default TodoList;

그리고, 기존의 TodoItem 컴포넌트는 라이프사이클 API 를 사용하기 위하여, 함수형 컴포넌트에서 클래스형 컴포넌트로 변환해주세요. 그리고 shouldComponentUpdate 를 다음과 같이 구현합니다.

components/TodoItem.js

import React, { Component } from 'react';
import './TodoItem.css';

class TodoItem extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.todo !== this.props.todo;
  }
  render() {
    const { checked, text, id, onCheck, onRemove } = this.props;
    return (
      <div
        className={`TodoItem ${checked && 'active'}`}
        onClick={() => onCheck(id)}
      >
        <div className="check">&#10004;</div>
        <div className="text">{text}</div>
        <div
          className="remove"
          onClick={e => {
            e.stopPropagation();
            onRemove(id);
          }}
        >
          [지우기]
        </div>
      </div>
    );
  }
}

export default TodoItem;

이렇게 최적화를 하고 나면 약 290ms 가 걸립니다.

Edit Bulk Todo, Optimized

지금은 최적화를 통해 대략 32% 정도의 성능 개선이 일어났는데요, 만약에 데이터의 수가 더 많고, 프로덕션 모드에서 실행된다면 더욱 큰 차이가 발생합니다.

15000 개의 데이터 최적화 전후:

전: https://pumped-behavior.surge.sh

후: https://stereotyped-division.surge.sh

85% 정도의 성능 개선이 이뤄졌습니다.

정리

리액트에서는 부모 컴포넌트가 리렌더링 되면 내부 컴포넌트들도 리렌더링 됩니다. 비록 이 작업이 버추얼 돔 에서 이뤄진다고 해도, 그 컴포넌트의 갯수가 많으면 자바스크립트 처리에 불필요한 자원을 낭비하게 될 수도 있습니다. 수많은 컴포넌트를 렌더링해야 하는 경우 이런식으로 shouldComponentUpdate 를 사용하시면, 보여지는 데이터 그리고 발생하는 업데이트의 수가 많아도, 앱의 성능을 지켜낼 수 있을 것입니다.

results matching ""

    No results matching ""