react-beautiful-dnd
를 이용하여 to do 앱을 개발 중에 불필요한 렌더링이 발생하는 문제를 발견했다. parent
가 수정될 때 자동으로 children
이 리렌더링되는 문제였다. 이 부분을 최적화하면서 memo
를 알게 되었다. (현재 React 버전 18.2 기준으로 React.memo
에서 memo
로 표시되어 있다.)
memo
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
memo
는 컴포넌트의 props가 변경되지 않았을 때 컴포넌트의 리렌더링을 건너뛸 수 있게 해줍니다.
memo
를 호출하고 결과를 메모이징(Memoizing)하여 경우에 따라 성능을 향상시킬 수 있음memo
는 props 변화에만 영향을 받음memo
로 감싸진 함수 컴포넌트에 useState
또는 useReducer
, useContext
훅을 사용한다면 여전히 state
나 context
가 변할 때 리렌더링됨App.tsx
와 DraggableCard.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으로 toDo
와 index
를 보내주고 있다.
// 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
컴포넌트에서 toDo
와 index
를 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); // 수정한 부분
위와 같이 memo
를 import
하고, DraggableCard
컴포넌트를 memo
로 감싸줬다. 코드를 수정 후에는 변경된 DraggableCard
컴포넌트만 렌더링되는 것을 확인할 수 있었다.