[React] memo 사용하여 최적화하기

예구·2024년 1월 25일
0

React

목록 보기
2/3

Intro

react-beautiful-dnd를 이용하여 to do 앱을 개발 중에 불필요한 렌더링이 발생하는 문제를 발견했다. parent가 수정될 때 자동으로 children이 리렌더링되는 문제였다. 이 부분을 최적화하면서 memo를 알게 되었다. (현재 React 버전 18.2 기준으로 React.memo에서 memo로 표시되어 있다.)


memo

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

memo는 컴포넌트의 props가 변경되지 않았을 때 컴포넌트의 리렌더링을 건너뛸 수 있게 해줍니다.

  • 고차 컴포넌트(Higher Order Component)
  • 컴포넌트가 동일한 props로 동일한 결과를 렌더링한다면, memo를 호출하고 결과를 메모이징(Memoizing)하여 경우에 따라 성능을 향상시킬 수 있음
  • React는 컴포넌트를 리렌더링하지 않고 마지막으로 렌더링된 결과를 재사용함
  • memo는 props 변화에만 영향을 받음
  • memo로 감싸진 함수 컴포넌트에 useState 또는 useReducer, useContext 훅을 사용한다면 여전히 statecontext가 변할 때 리렌더링됨
  • 이 메서드는 오직 성능 최적화를 위하여 사용됨
  • 버그가 생길 수 있기 때문에, 렌더링을 "방지"하기 위하여 사용하진 말아야 함

수정 전

App.tsxDraggableCard.tsx 코드는 다음과 같다.

// App.tsx

import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd";
import { useRecoilState } from "recoil";
import styled from "styled-components";
import { toDoState } from "./atoms";
import DraggableCard from "./components/DraggableCard";

const Wrapper = styled.div`
  // ...
`;

const Boards = styled.div`
  // ...
`;

const Board = styled.div`
  // ...
`;

function App() {
  const [toDos, setToDos] = useRecoilState(toDoState);

  const onDragEnd = ({ draggableId, destination, source }: DropResult) => {
    if (!destination) return;

    setToDos((oldTodos) => {
      const toDosCopy = [...oldTodos];
      toDosCopy.splice(source.index, 1);
      toDosCopy.splice(destination?.index, 0, draggableId);
      return toDosCopy;
    });
  };

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Wrapper>
        <Boards>
          <Droppable droppableId="one">
            {(provided) => (
              <Board ref={provided.innerRef} {...provided.droppableProps}>
                {toDos.map((toDo, index) => (
                  <DraggableCard key={toDo} index={index} toDo={toDo} />
                ))}
                {provided.placeholder}
              </Board>
            )}
          </Droppable>
        </Boards>
      </Wrapper>
    </DragDropContext>
  );
}

export default App;

우선 App.tsx에서 DraggableCard 컴포넌트에 prop으로 toDoindex를 보내주고 있다.


// DraggableCard.tsx

import { Draggable } from "react-beautiful-dnd";
import styled from "styled-components";

const Card = styled.div`
  // ...
`;

interface IDraggableCardProps {
  toDo: string;
  index: number;
}

const DraggableCard = ({ toDo, index }: IDraggableCardProps) => {
  console.log(toDo, "has been rendered");

  return (
    <Draggable key={toDo} draggableId={toDo} index={index}>
      {(provided) => (
        <Card
          ref={provided.innerRef}
          {...provided.dragHandleProps}
          {...provided.draggableProps}
        >
          {toDo}
        </Card>
      )}
    </Draggable>
  );
};

export default DraggableCard;

DraggableCard 컴포넌트에서 toDoindex를 prop으로 받고 있다. 렌더링을 확인하기 위해 console.log(toDo, "has been rendered");을 작성했고, 6개의 DraggableCard 컴포넌트가 모두 렌더링되는 것을 확인할 수 있었다.

수정하기 전


수정 후

DraggableCard 컴포넌트가 동일한 index와 동일한 todo prop을 받으면 리렌더링되지 않도록 하기 위해 memo를 아래와 같이 적용했다.

// DraggableCard.tsx

import { memo } from "react"; // 추가한 부분

// ...

const DraggableCard = ({ toDo, index }: IDraggableCardProps) => {
  console.log(toDo, "has been rendered");

  return (
    <Draggable key={toDo} draggableId={toDo} index={index}>
      // ...
    </Draggable>
  );
};

export default memo(DraggableCard); // 수정한 부분

위와 같이 memoimport 하고, DraggableCard 컴포넌트를 memo로 감싸줬다. 코드를 수정 후에는 변경된 DraggableCard 컴포넌트만 렌더링되는 것을 확인할 수 있었다.

수정 후


Reference

https://react.dev/reference/react/memo

profile
우당탕탕 FE 성장기

0개의 댓글