[리액트를 다루는 기술] useCallback과 react-virtualized의 List 이용하여 최적화

쿼카쿼카·2022년 9월 15일
0

TodoList.js

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

function TodoList({todos, onRemove, onToggle}) {
  // useCallback으로 rowRenderer 선언
  // rowRenderer를 List의 props로 꼭 넣어줘야함
  const rowRenderer = useCallback(({index, key, style}) => {
    const todo = todos[index];
    return (
      <TodoListItem
        todo={todo}
        key={key}
        onRemove={onRemove}
        onToggle={onToggle}
        style={style}
      />
    );
  }, [onRemove, todos, onToggle])
  return (
    // List의 전체 크기, 각 항목의 높이, 각 항목을 렌더링 할 때 사용하는 함수(rowRenderer), 배열을 props로 넣어주면 자동 최적화
    <List
      className='TodoList'
      width={512} // 전체 크기
      height={513} // 전체 높이
      rowCount={todos.length} // 항목 개수
      rowHeight={57} // 항목 높이
      rowRenderer={rowRenderer} // 항목을 렌더링할 때 쓰는 함수
      list={todos} // 배열
      style={{outline: 'none'}} // List에 기본 적용되는 outline 스타일 제거
    />
  )
}

export default React.memo(TodoList); // React.memo 추가

TodoListItem.js

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

function TodoListItem({todo, onRemove, onToggle, style}) {
  const {id, text, checked} = todo;
  return (
    // TodoListItem-virtualized로 묶어줌
    <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.scss

// TodoListItem-virtualized 추가
.TodoListItem-virtualized {
  & + & {
    border-top: 1px solid #dee2e6;
  }
  &:nth-child(even) {
    background: #f8f9fa;
  }
}

.TodoListItem {
  padding: 1rem;
  display: flex;
  align-items: center; // 세로 중앙 정렬

  // TodoListItem-virtualized로 옮김
  // &:nth-child(even) {
  //   // 짝수 순서만 배경색 변경
  //   background: #f8f9fa;
  // }


  .checkbox {
    cursor: pointer;
    flex: 1; // 차지할 수 있는 영역 모두 차지
    display: flex;
    align-items: center; // 세로 중앙 정렬
    svg {
      //아이콘
      font-size: 1.5rem;
    }

    .text {
      margin-left: 0.5rem;
      flex: 1; // 차지할 수 있는 영역 모두 차지
    }

    // 체크되었을 때 보여 줄 스타일
    &.checked {
      svg {
        color: #22b7cf;
      }
      .text {
        color: #adb5bd;
        text-decoration: line-through;
      }
    }
  }

  .remove {
    display: flex;
    align-items: center;
    font-size: 1.5rem;
    color: #ff6b6b;
    cursor: pointer;
    &:hover {
      color: #ff8787;
    }
  }

  // TodoListItem-virtualized로 옮김
  // & + & {
  //   border-top: 1px solid #dee2e6;
  // }
}

react-virtualized 사용 이유

  • 현재 컴포넌트에서 첫 렌덩링 때 2500개 컴포넌트 중 대부분은 스크롤 하기 전에 보이지 않는데 렌더링 일어남(비효율)
  • todos에 변동이 있을 때도 다 TodoList 컴포넌트 내부의 map함수에서 배열의 처음부터 끝까지 컴포넌트로 반환(비효율)
  • react-virtualized 사용하면 리스트 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트들은 렌더링 하지 않고 크기만 차지함

사용법

  • npm i react-virtualized로 하면 오류가 나서 npm i --force로 해줘야함
  • 개발자도구에서 최적화 원하는 컴포넌트 크기 측정
  • rowRenderer 함수와 List 컴포넌트

    • List 컴포넌트에서 각 TodoItem을 렌더링 할 때 사용
    • List 컴포넌트의 props로 설정해야함
    • List 컴포넌트에는 해당 리스트의 전체 크기, 각 항목 높이, 각 항목을 렌더링 할 때 사용해야 하는 함수(rowRenderer), 배열을 props로 넣어줌(자동으로 최적화)
  • div.TodoListItem-virtualized로 감싸주기

    • TodoListItem.js 파일에서 return을 위 className으로 감싸주기(짝수 리스트 색 변경 & 두 번째부터 윗 테투리 넣는 스타일 적용)
    • TodoListItem.scss파일에서 .TodoListItem에 & + &와 &:nth-child(eve) 옮기기

결과

  • 4ms로 더 줄었다.
profile
쿼카에요

0개의 댓글