컴포넌트 성능 최적화

Haechan Kim·2022년 1월 17일
0

React

목록 보기
9/13

많은 데이터를 렌더링 할 때 느려짐. lagging

  • 느려지는 원인
    컴포넌트는 다음과 같은 상황에서 리렌더링 발생
  1. 자신이 전달받은 props가 변경될 때
  2. 자신의 state 변경될 때
  3. 부모의 컴포넌트 리렌더링 될 때
  4. forceUpdate 함수 실행될 때

'할 일1'을 체크하면 App 컴포넌트의 state가 변경되면서 리렌더링 됨
부모 컴포넌트(App)가 리렌더링 되었으니 TodoList 컴포넌트도 리렌더링 되고 그 안의 무수한 컴포넌트들도 리렌더링 됨
'할 일1'은 리렌더링 되어야 하지만 나머지들은 안해도 됨

이를 최적화 하는 작업이 필요
불필요한 리렌더링을 방지하는 방법을 알아보자

  • React.memo (함수형 컴포넌트)
    컴포넌트의 props가 바뀌지 않았다면 리렌더링 하지 않도록 설정하여 성능 최적화
    사용법은 컴포넌트를 감싸주기만 하면 됨
// TodoListItem.js
const TodoListItem = ({ todo, onRemove, onToggle }) => {
  ()
};

export default React.memo(TodoListItem);

이제 TodoListItem 컴포넌트는 todo, onRemove, onToggle 이 바뀌지 않으면 리렌더링 하지 않음

  • onToggle, onRemove 함수 바뀌지 않게 하기
    React.memo 만으로 최적화 끝나지 않는다
    현재 프로젝트에서는 todos 배열 업데이트 되면 onRemove, onToggle 함수도 새롭게 바뀌기 때문.
    onRemove, onToggle 함수는 배열 상태 업데이트 과정에서 최신 상태의 todos 참조하기 때문에 todos 배열 바뀔 때마다 함수 새로 만들어짐

이것 방지하기 위해서는

  1. useState의 함수형 업데이트 기능 사용
    기존 setTodos 함수 사용할때는 새로운 상태 파라미터로 넣어 줬음
    새로운 상태 대신 상태 업데이트를 어떻게 할지 정의해주는 업데이트 함수 넣을 수도 있음 (== 함수형 업데이트 라고 부른다)

setNumbers(number+1)을 하는 것이 아니라

const onIncrease = useCallback(() => {
  setNumber(prevNumber => prevNumber + 1), [])

위 코드처럼 어떻게 업데이트 할 지 정의해주는 업데이트 함수 넣어 줌
그러면 useCallback 사용 시 두번째 파라미터로 넣는 배열에 number 안넣어도 됨.

// setTodos를 사용할 때 todos =>만 앞에 넣어 주면 됨
  const onRemove = useCallback(id => {
    setTodos(todos => todos.filter(todo => todo.id !== id));
  }, [],);
  1. useReducer 사용
// App.js
import React, {useReducer, useState, useRef, useCallback} from 'react';

import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';


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

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, createBulkTodos)

  const nextId = useRef(2501); // 고유값으로 사용될 id. ref 사용해 변수 담기

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

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

  // 수정 기능
  const onToggle = useCallback(id => {
    dispatch({type: 'TOGGLE', id});
  }, []);

  return ( 
    <TodoTemplate>
      <TodoInsert onInsert = {onInsert}/> {/*onInsert 함수를 TodoInsert 컴포넌트의 props로 설정*/}
      <TodoList todos = {todos} onRemove = {onRemove} onToggle = {onToggle}/> {/* onRemove를 TodoList의 props로 설정 */}
    </TodoTemplate>
    );
}

export default App;

useReducer 사용시 원래 두번째 파라미터에 초기 상태 넣어 줘야 함
지금은 두번째 파라미터에 undefined 넣고, 세번째에 초기상태 만들어주는 createBulkTodos 넣었다
이렇게 하면 컴포넌트가 맨 ㅍ처은 렌더링될 때만 createBulkTodos함수 호출됨.

useReducer는 상태 어베이트 하는 로직 모아서 컴포넌트 밖에 둘 수 있다는 장점.
둘 중 취향따라 선택. 성능은 비슷

  • 불변성(immutability)의 중요성
    리액트 컴포넌트에서 상태 업데이트 시 불변성 지키는 것은 매우 중요

앞서 useState 사용해 만든 onToggle 함수를 보면 기존 데이터 수정 시 직접 수정X, 새로운 배열 만들고 필요 부분 교체
업데이트 필요한 곳에서는 아예 새로운 객체 만들기 때문에
React.memo 사용했을 때 props가 바꿔었는지 알아내서 리렌더링 성능 최적화 할 수 있다.

불변성 지켜지지 않으면 값 바뀌어도 감지 못함
=> React.memo에서 서로 비교해 최적화하는것 불가능

배열/객체의 구조가 복잡해지면 불변성 유지하며 업데이트 하기 까다로움
이런 경우 immer 라는 라이브러리 사용시 용이

리스트 관련 컴포넌트 최적화 시에는 해당 컴포넌트, 리스트로 사용되는 컴포넌트 자체도 최적화 해줘야 함
리스트 아이템과 리스트, 두가지 컴포넌트를 최적화 해 주는것 잊지 말기

지금까지는 리액트 컴포넌트 리렌더링 성능 최적화하는 방법 알아봤음
ㄴ 최적화시 필요할 때만 리렌더링 하도록.

  • react-virtualized
    이번에는 또 다른 방법
    일정 관리 앱에서 초기에 2500개 데이터가 등록되지만 화면에 보이는 것은 9개 뿐.
    나머지는 스크롤 해야 보임 => 비효율적
    todos 배열에 변동 생길때도 TodoList 내부의 map함수에서 배열의 처음부터 끝까지 컴포넌트로 변환해주는데 2491개는 보이지 않으므로 시스템 자원 낭비.

react-virtualized 사용하면 스크롤되기 전 안보이는 컴포넌트는 렌더링 하지 않고 크기만 차지하게끔 해줌.
스크롤 되면 그때 렌더링

0개의 댓글