'리액트를 다루는 기술' 11장, 컴포넌트 성능 최적화

Jake_Young·2020년 8월 24일
0
post-thumbnail

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

Performance 탭의 녹화 버튼

  • 녹화 버튼을 누르고 함수를 실행시킨 다음 Stop 버튼을 누른다.
  • 성능 분석 결과에 나타난 Timings를 열어 본다.

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

  • props가 바뀌지 않았다면, 리렌더링하지 않도록 설정한다.
  • TodoListItem.js의 마지막 부분을 아래처럼 바꾼다.
export default React.memo(TodoListItem)

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

  • onToggle이나 onRemove

useState의 함수형 업데이트

  • setTodos를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣는 것
const [number, setNumber] = useState(0)
// prevNumbers는 현재 number 값을 가리킨다.
const onIncrease = useCallback(
  () => setNumber(prevNumber => prevNumber + 1)
  // not setNumber(number + 1)
  , []
)
  • 이렇게 하면 두 번째 파라미터로 number를 넣지 않아도 된다.
  • 이를 이용해 onInsert 함수를 수정하면 아래와 같다.
// App.js

// ...
function createBulkTodos() {
  const array = [];
  for (let i=1 ;i<=2500; i++){
    array.push({
      id: i,
      text: `Todos content ${i}`,
      checked: false,
    })
  }
  return array;
}
// ...
const App = () => {
  const [todos, setTodos] = useState(createBulkTodos)
  
  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,
      ),
    )
  },[])
  // ...
}
  • setTodos를 사용할 때 그 안에 'todos=>'만 앞에 넣어 주었다.
  • useState(createBulkTodos())로 쓰지 않은 이유는 그렇게 해야 컴포넌트가 처음 렌더링 될 때만 createBulkTodos 함수가 실행되기 때문이다.

useReducer를 사용

// App.js

// ...
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: 'REMOVE', 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)
  
  const onInsert = useCallback(text => {
    const todo = {
      id: nextId.current,
      text,
      checked: false
    };
    dispatch({type: 'INSERT', todo})
    nextId.current += 1;
  },[])
  
  const onRemove = useCallback(id => {
    dispatch({type: 'REMOVE', id})
  }, [])
}
  • 원래는 useReducer의 두 번째 파라미터로 초기 상태를 넣어주어야 한다.
  • 하지만 이번엔 두 번째 파라미터로 undefined를 넣고 세 번째 파라미터에 초기 상태를 만들어주는 createBulkTodos를 넣어주었다.
  • 이렇게 해야 컴포넌트가 맨 처음 렌더링 될 때만 createBulkTodos 함수가 호출된다.

😍 불변성의 중요성

  • 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'고 한다.
  • 상태 업데이트가 필요한 곳에서는 아예 새로운 배열 혹은 새로운 객체를 만들었기 때문에 React.memo를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지를 알아내서 리렌더링 성능을 최적화해 줄 수 있었다.
  • 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.
  • 그러면 React.memo에서 서로 비교하여 최적화하는 것이 불가능하다.

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

  • pagination과 비슷한 기능
  • 화면에 보이지 않는 컴포넌트는 렌더링되지 않고 크기만 차지하게 끔 하는 기술
  • yarn add react-virtualized
  • 최적화를 수행하려면 사전에 각 항목의 실제 크기를 px 단위로 알아내야 한다.
// TodoList.js

import {List} from 'react-virtualized';
// ...
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}
        ></TodoListItem>
      )
    },[onRemove, onToggle, todos])
  return (
    <List
      className="TodoList"
      width={512} // 전체 크기
      height={513} // 전체 높이
      rowCount={todos.length} // 항목 개수
      rowHeight={57} // 항목 높이
      rowRenderer={rowRenderer} // 항목을 렌더링할 때 쓰는 함수
      list={todos} // 배열
      style={{outline: 'none'}} // List에 기본 적용되는 outline 스타일 제거
    ></List>
  )
}

export default React.memo(TodoList);

// TodoListItem.js

// ...
const TodoListItem = ({todo, onRemove, onToggle, style}) => {
  // ...
  return(
    <div className="TodoListItem-virtualized" style={style}>
      // ...
    </div>
  )
}

export default React.memo(
  TodoListItem,
  (prevProps, nextProps) => prevProps.todo === nextProps.todo,
)

// TodoListItem.scss

// 코드 치환하기 (&+& 코드와 &:nth-child(even)를)
.TodoListItem-virtualized{
  & + & {
    border-top: 1px solid #dee2e6;
  }
  &:nth-child(even){
    background: #f8f9fa;
  }
}

😛 꿀팁!!

디버그 모드가 아닌 프로덕션 모드로 테스트하기

  • yarn build
  • yarn global add serve
  • serve -s build
profile
자바스크립트와 파이썬 그리고 컴퓨터와 네트워크

0개의 댓글