[React][리액트를 다루는 기술] 컴포넌트 성능 최적화

uddi·2024년 3월 26일
0

React

목록 보기
16/16

이번에는 리액트에서 컴포넌트의 성능을 최적화하는 방법에 대해 알아보려고 한다

📌 컴포넌트 리렌더링이 발생하는 경우

  1. 자신이 전달받은 props가 변경될 때
  2. 자신의 state가 바뀔 때
  3. 부모 컴포넌트가 리렌더링될 때
  4. forceUpdate 함수가 실행될 때

리렌더링이 불필요할 때는 리렌더링을 방지해 컴포넌트 리렌더링 성능을 최적화해 주는 작업이 필요하다

📌 React.memo를 통한 컴포넌트 성능 최적화

React.memo 함수를 사용하여 컴포넌트의 props가 바뀌지 않았다면 리렌더링하지 않도록 설정하여 함수 컴포넌트의 리렌더링 성능을 최적화해 줄 수 있다

export default React.memo(App);

하지만 React.memo를 사용하는 것만으로는 컴포넌트 최적화가 끝나지 않는다

만약, 컴포넌트 내에 만들어 놓은 함수가 의존적인 state에 의해 새로운 함수가 계속 만들어지는 상황이 생길 수 있다

이 상황을 방지하기 위한 두 가지 방법이 있다

  1. useState의 함수형 업데이트 기능 사용
  2. useReducer 사용

성능상으로는 두 가지 방법이 비슷하기 때문에 취향에 따라 선택해 사용하면 된다

🍞 useState의 함수형 업데이트

useState에서 상태를 변경할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있다

이를 함수형 업데이트라고 한다

즉, setNumber(numbe+1)이 아니라 setNumber(prevNumber => prevNumber + 1)처럼 사용하는 것을 말한다

함수형 업데이트를 사용하면 useCallback을 사용할 때 두 번째 파라미터로 넣는 배열에 값을 넣지 않아도 된다

🍞 useReducer

useReducer를 사용할 때 두 번째 파라미터에 초기 상태를 넣어야 한다

하지만 두 번째 파라미터에 undefined를 넣고 세 번째 파라미터에 초기 상태를 만들어 주는 초기화 함수를 넣어 주면 컴포넌트가 맨 처음 렌더링될 때만 초기화 함수가 호출된다

useReducer는 기존 코드를 많이 고쳐야 한다는 단점이 있지만, 상태를 업데이트하는 로직을 모아서 컴포넌트 밖에 둘 수 있다는 장점이 있다

📌 불변성의 중요성

리액트 컴포넌트에서 상태를 업데이트할 때 불변성을 지키는 것은 매우 중요하다

기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 불변성을 지킨다고 한다

불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못하기 때문에 React.memo에서 서로 비교해 최적화하는 것이 불가능하다

불변성을 지키기 위해 스프레드 문법을 이용해 값을 복사해야 한다

스프레드 문법을 사용해 객체나 배열 내부의 값을 복사할 때는 얕은 복사를 하게 된다
👉 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽에 있는 값만 복사된다

내부의 값이 객체 혹은 배열이라면 내부의 값 또한 따로 복사해 주어야 한다

다음 코드를 보면 이해가 쉽다

const todos = [
    { id: 1, checked: true },
    { id: 2, checked: true },
  ];
  const nextTodos = [...todos];

  nextTodos[0].checked = false;
  console.log(todos[0] === nextTodos[0]); // 아직까지는 똑같은 객체를 가리키고 있기 때문에 true

  nextTodos[0] = {
    ...nextTodos[0],
    checked: false,
  };
  console.log(todos[0] === nextTodos[0]); // 새로운 객체를 할당해 주었기에 false

배열이나 객체의 구조가 복잡해지면 immer라는 라이브러리의 도움을 받아 편하게 작업할 수 있다 👉 immer에 대해 더 알고 싶다면 클릭

📌 react-virtualize를 사용한 렌더링 최적화

프로젝트를 하다보면 컴포넌트는 정말 많은데 실제로 화면에 렌더링되는 항목은 몇 안되는 경우가 있을 것이다
나머지 컴포넌트는 스크롤해야만 볼 수 있는데, 렌더링은 이루어지기 때문에 비효율적이다

이때 react-virtualized를 사용하면 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게 하고, 스크롤되면 해당 스크롤 위치에서 보여줘야 할 컴포넌트를 렌더링시킨다

🍞 react-virtualized 사용법

import { List } from 'react-virtualized';

 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' }}	// List에 기본 적용되는 outline 스타일 제거
    />
  );

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

profile
거북이는 느리지만 결국 결승선을 통과한다

0개의 댓글