[React] Optimizing Performance

Jeongyeon Kim·2022년 2월 3일
0

React

목록 보기
1/5
post-thumbnail

1. createBulkTodos 함수를 만들어 많은 데이터 렌더링

👣 10장을 학습할 때 만든 Todo-app은 데이터가 적어 랙(lag)이 발생하지 않는다.
👣 랙을 경험하기 위해 createBulkTodos 함수를 만들어 많은 데이터를 렌더링하도록 한다.

//App.js

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

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

👀 useState(createBulkTodos) : 파라미터를 함수 형태로 넣어 주어 컴포넌트가 처음 렌더링될 때만 createBulkTodos 함수 실행

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

👣 React DevTools(리액트 전용 개발자 도구)를 사용해 성능 분석

3. 느려지는 원인 분석

👣 리렌더링이 발생하는 경우
🤖 자신이 전달받은 props가 변경될 때
🤖 자신의 state가 바뀔 때
🤖 부모 컴포넌트가 리렌더링될 때
🤖 forceUpdate 함수가 실행될 때

4. React.memo를 사용하여 컴포넌트 성능 최적화

👣 컴포넌트의 리렌더링 방지 - shouldComponentUpdate 라이프사이클 사용
👣 함수 컴포넌트에서는 라이프 사이클 사이클을 사용할 수 없다.
👀 따라서 React.memo 함수 사용

//TodoListItem.js

(...)
export default React.memo(TodoListItem);

5. onToggle, onRemove 함수가 바뀌지 않게 하기

👀 useState의 함수형 업데이트
🤖 함수형 업데이트 : setTodos에 상태 업데이트를 어떻게 할지 정의해주는 함수를 파라미터로 넣음

(...)
const onInset = useCallback(text => {
	const todo = {
    	id: next.current,
        text,
        checked: false,
    };
  	setTodos(todos => todos.concat(todo));
 }, []);
 
 const onRemove = useCallback(id => {
 	setTodos(todos => todos.filter(todo => todo.id !== id));
 }, []);
 
 const onToggle = useCallback(id => {
 	setTodos(todos =>
    		todos.map(todo =>
        		todo.id === id ? { ...todo, checked: !todo.checked } : todo,
     	),
  	);
 }, []);
 (...)

👀 useReducer 사용하기

//App.js
function todoReducer(todos, action) {
	switch (action.type) {
    	case 'INSERT':
        	return todos.concat(action.todo);
        case 'REMOVE':
        	return todos.filter(todo => todo.id !== action.id);
        case 'TOGGLE':
        	return todos.map(todo =>
            	todo.id === action.id ? { ...todo, checked: !todo.checked } : todo,
            );
        default:
        	return todos;
    }
}

const App = () => {
	const [todos, dispatch] = useReducer(todoReducer, undefined, createulkTodos);
 (...)

🤖 useReducer의 두 번째 파라미터에 undefined 넣고, 세 번째 파라미터에 createBulkTodos를 넣으면 컴포넌트가 맨 처음 렌더링될 때만 createBulkTodos 함수가 호출된다.

6. 불변성의 중요성

👣 리액트 컴포넌트에서 상태 업데이트를 할 때 불변성을 지키는 것은 매우 중요하다.
👣 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'라고 한다.
👣 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.

👣 전개 연산자(... 문법)을 사용하여 객체나 배열을 복사하면 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽에 있는 값만 복사된다.

👀 배열이나 객체 구조가 복잡한 경우 immer라는 라이브러리의 도움을 받을 수 있다.

7. TodoList 컴포넌트 최적화하기

👣 리스트에 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트도 최적화해야하고, 리스트로 사용되는 컴포넌트 자체도 최적화해 주는 것이 좋다.

8. react-virtualized를 사용한 렌더링 최적화

👀 최적화 준비

$ yarn add react-virtualized

👣 최적화 수행 전 각 항목의 실제 크기를 px단위로 알아내야 한다.

👀 TodoList 수정

//TodoList.js
import React, { useCallback } from 'react';
import { List } from 'react-virtualized';
import TodoListItem from './TodoListItem';
import './TodoList.scss';

const TodoList = ({ todos, onRemove, onToggle }) => {
	const rowRenderer = useCallback(
    ({ index, key, style }) => {
      const todo = todos[index];
      return (
        <TodoListItem
          todo={todo}
          key={key}
          onRemove={onRemove}
          onToggle={onToggle}
          style={style}
        />
      );
    },
    [onRemove, onToggle, todos],
  );

  return (
    <List
      className="TodoList"
      width={512}
      height={513}
      rowCount={todos.length}
      rowHeight={57}
      rowRenderer={rowRenderer}
      list={todos}
      style={{ outline: 'none' }}
    />
  );
};

export default React.memo(TodoList);

👣 rowRenderer 함수는 react-virtualized의 List 컴포넌트에서 각 TodoItem을 렌더링할 때 사용하고, 이 함수를 List 컴포넌트의 props로 설정해주어야 한다.
👣 rowRenderer 함수는 파라미터에 index, key, style 값을 객체 타입으로 받아와서 사용한다.

👀 TodoListItem 수정

//TodoListItem.js
import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';

const TodoListItem = ({ todo, onRemove, onToggle, style }) => {
  const { id, text, checked } = todo;

  return (
    <div className="TodoListItem-virtualized" style={style}>
      <div className="TodoListItem">
        <div
          className={cn('checkbox', { checked })}
          onClick={() => onToggle(id)}
        >
          {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
          <div className="text">{text}</div>
        </div>
        <div className="remove" onClick={() => onRemove(id)}>
          <MdRemoveCircleOutline />
        </div>
      </div>
    </div>
  );
};

export default React.memo(TodoListItem);

👣 TodoListItem-virtualized 클래스를 만든 것은 컴포넌트 사이에 테두리를 제대로 쳐 주고, 홀/짝수 번째 항목에 다른 배경 색상을 설정하기 위해서이다.

//TodoListItem.scss
.TodoListItem-virtualized {
  & + & {
    border-top: 1px solid #dee2e6;
  }
  &:nth-child(even) {
    background: #f8f9fa;
  }
}
(...)
profile
Backend Developer👩🏻‍💻

0개의 댓글