[Todo-List] 5. 컴포넌트 성능 개선 (2)

🏃Dekay (JuniorDeveloper)·2021년 10월 7일
0

ToyProject

목록 보기
5/5
post-thumbnail

앞서 컴포넌트의 성능을 개선하기 위해 했던 과정들은 [Todo-List] 4. 컴포넌트 성능 개선 (1)를 참고하면 된다.

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

1.1 useReducer 사용하기

  • useState함수형 업데이트를 사용하는 대신, useReducer를 사용해도 onRemoveonToggle가 계속 새로워지는 문제를 해결할 수 있다.
  • App.js를 다음과 같이 수정해보자.
//App.js
import React, { useReducer, useRef, useCallback } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';


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

function todoReducer(todos, action) {
  switch (action.type) {
    case 'INSERT':  // 새로 추가
      //{type: 'INSERT', todo: { id: 1, text: 'todo', checked: false }}
      return todos.concat(action.todo);
    case 'REMOVE':  // 제거
      //{type: 'REMOVE', id: 1 }
      return todos.filter(todo => todo.id !== action.id);
    case 'TOGGLE':  // 토글
      //{type: 'TOGGLE', id: 1}
      return todos.map(todo =>
        todo.id === action.id ? { ...todo, checked: !todo.checked } : todo);
    default:
      return todos;
  }
}


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

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

  const onInsert = useCallback(text => {
    const todo = {
      id: nextId.current,
      text,
      checked: false,
    };
    dispatch({type: 'INSERT', todo});
    nextId.current += 1; // id 값 1씩 증가
  }, []);

  const onRemove = useCallback(id => {
    dispatch({type: 'ONREMOVE', id});
  }, []);

  const onToggle = useCallback(id => {
    dispatch({type: 'TOGGLE', id});
  }, []);


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

export default App;
  • useReducer를 사용할 때는 원래 두 번째 파라미터초기 상태를 넣어주어야 한다.
  • 지금은 두 번째 파라미터undefined를 넣고, 세 번째 파라미터초기 상태를 만들어 주는 함수를 넣어 사용했다.
    👉 컴포넌트가 맨 처음 렌더링될 때만 createBulkTodos 함수를 호출한다.

정리 ❗

useReducer기존 코드를 많이 고쳐야 하지만, 상태를 업데이트 하는 로직을 컴포넌트 바깥에 둘 수 있어서 가독성이 좋아지는 것 같다.
👉 둘 다 같은 기능이기 때문에 상황에 맞춰서 사용할 수 있도록 하자.(나는 useState가 더 편한듯..)

2. TodoList 최적화

  • 리스트에 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트도 최적화 해야하고, 리스트로 사용되는 컴포넌트 자체도 최적화 하는 것이 좋다.
  • 따라서, TodoList.js를 다음과 같이 수정하였다.
// TodoList.js
import React from "react";
import TodoListItem from "./TodoListItem";
import './TodoList.scss';

const TodoList = ({ todos, onRemove, onToggle }) => {
    (...)
};

export default React.memo(TodoList);

3. react-virtualized를 통한 렌더링 최적화

  • react-virtualized를 사용하면 리스트 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 할 수 있다.
    👉 쉽게 말해서 맨 처음 렌더링될 때 2,000개 컴포넌트 중 스크롤하기 전에 보이지 않는 컴포넌트가 렌더링 되는 것은 매우 비효율적이다.
// yarn을 사용하여 설치해보자.
$ yarn add react-virtualized
  • react-virtualized에서 제공하는 리스트 컴포넌트를 사용하여 TodoList 컴포넌트 성능을 최적화 해보자.

  • 위의 사진 처럼 각 항목의 실제 크기를 px 단위로 알아낼 수 있다.
    👉 가로 495.2px, 세로 57px

주의 ❗❗

크기를 확인할 때 두 번째 항목을 확인해야한다.
👉 두 번째 항목부터 테두리가 포함되어 있기 때문임.

  • 각 항목의 크기를 알아낸 뒤 TodoList.js를 다음과 같이 수정했다.
//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={495.2} // 전체 크기
        height={57} // 전체 높이
        rowCount={todos.length} // 항목 개수
        rowHeight={57} // 항목 높이
        rowRenderer={rowRenderer} // 항목을 렌더링할 때 사용하는 함수
        list={todos} // 배열
        style={{ outline: 'none' }} // List에 기본 적용되는 outline 스타일 제거
        />
    );
};

export default React.memo(TodoList);
  • 리스트 컴포넌트를 사용하기 위해 rowRenderer라는 함수를 사용했는데, 이 함수는 react-virtualized리스트 컴포넌트에서 각 TodoItem을 렌더링할 때 사용한다.
    👉 즉, 리스트 컴포넌트props로 설정해 주어야 하는데 주의할 점으로 파라미터index, key, style 값을 객체 타입으로 받아와야 한다.
    추가로, 해당 리스트의 전체 크기, 각 항목의 높이, 각 항목을 렌더링할 때 사용해야 하는 함수, 배열을 props로 넣어주면 전달받은 props를 사용하여 자동으로 최적화해준다.

3.1 TodoListItem 수정

  • TodoListItem.js를 다음과 같이 수정했다.
//TodoListItem.js
import React from "react";
import {
    MdCheckBox,
    MdRemoveCircleOutline,
    MdCheckBoxOutlineBlank,
} 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);
  • render 함수에서 기존에 보여 주던 내용을 TodoListItem-virtualized라는 className으로 설정하여 div로 한번 감싸주었고, props로 받아온 style을 적용시켰다.
  • 하지만 위와 같이 코드를 사용하면, 기존에 설정했던 style을 적용받지 못하기 때문에 TodoListItem.scss을 다음과 같이 수정했다.
//TodoListItem.scss
.TodoListItem-virtualized {
    & + & {
        border-top: 1px solid #dee2e6;
    }
    &:nth-child(even) {
        background: #f8f9fa;
    }
}

(...)
  • 수정한 뒤, 정상적으로 리스트 사이사이에 테두리와, 홀수/짝수 항목에 다른 배경 색상이 적용된 것을 확인할 수 있다.

4. 결과

  • 성능을 최적화 하지 않았을 때와 성능을 최적화 하였을 때의 성능을 비교해 보면 차이가 엄청 많이 난다.
    👉 최적화는 선택이 아닌 필수라는 말이 괜한 말이 아니였다. 최적화 전과의 속도 차이가 느껴진다..🤦‍♂️
  • 하지만, React.memo를 일일이 사용해야 한다기 보다는 리스트와 관련된 컴포넌트를 만들 때 보여줄 항목이 많거나, 업데이트가 자주 발생하면 오늘 해보았던 방법을 사용하여 최적화 하도록 하자.

end

profile
Believe you can & you're half way there 🙏

0개의 댓글