useMemo()란?
컴포넌트 내에 함수가 있다면, 컴포넌트가 렌더링 될 때마다 함수는 계속 다시 호출된다. 불필요한 함수 호출을 막기 위해 useMemo를 쓸 수 있는데, useEffect와 사용방법이 비슷하다. 첫 번째 파라미터로 계산하고 싶은 그 함수를 넣어주고, 두 번째 파라미터로 의존성 배열을 넣어준다. 따라서 의존성 배열 내에 들어있는 값이 변경될 때에만 재계산(호출)되고 그렇지 않으면 cache, memoize 된다. (재계산을 하지 않는다.)
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
useMemo()로 기존 코드 리팩토링하기
bookshelf 프로젝트의 코드에 useMemo를 적용해서 성능 최적화를 해보았다.
1.
// 기존 BookShelfPage.tsx
const BookShelfPage = () => {
// ...생략
const allTags: HashTags = [];
bookList.forEach((book) => {
if (book.hashtags) allTags.push(...book.hashtags);
});
allTags = [...new Set(allTags)];
const sortedAndTaggedBooks =
sortedBooks.filter((book) => book.hashtags?.includes(selectedTag!))
.length == 0
? sortedBooks
: sortedBooks.filter((book) => book.hashtags?.includes(selectedTag!));
// ...생략
return // ...
}
기존 코드는 BookShelfPage 컴포넌트가 렌더링될 때마다 sortedBooks가 새롭게 만들어지게 된다는 문제가 있다.
// 코드 리팩토링 후 BookShelfPage.tsx
const BookShelfPage = () => {
const allTags = useMemo(() => {
const tags: HashTags = [];
bookList.forEach((book) => {
if (book.hashtags) tags.push(...book.hashtags);
});
return [...new Set(tags)];
}, [bookList]);
const sortedAndTaggedBooks = useMemo(
() =>
sortedBooks.filter((book) => book.hashtags?.includes(selectedTag!))
.length == 0
? sortedBooks
: sortedBooks.filter((book) => book.hashtags?.includes(selectedTag!)),
[selectedTag, sortedBooks]
);
selectedTag, sortedBooks가 달라질 때만 재계산되고, 그렇지 않을 경우에는 memoized된 값을 사용한다.
2.
// 기존 useSort.tsx
export default function useSort() {
const bookList = useRecoilValue(booksState);
const [sortBy, setSortBy] = useState<SortOptions>("createdAt");
const [sortedBooks, setSortedBooks] = useState(
[...(bookList || [])].sort(
(x, y) => y.createdAt.seconds - x.createdAt.seconds
)
);
useEffect(() => {
const newBooks = [...(bookList || [])].sort((x, y) => {
if (sortBy === "createdAt") {
return x.createdAt.seconds - y.createdAt.seconds;
}
if (sortBy === "author") {
return x.author > y.author ? 1 : x.author < y.author ? -1 : 0;
}
if (sortBy === "title") {
return x.title > y.title ? 1 : x.title < y.title ? -1 : 0;
}
if (sortBy === "rating" && x.rating && y.rating) {
return x.rating > y.rating ? -1 : x.rating < y.rating ? 1 : 0;
}
return 0;
});
setSortedBooks(newBooks);
}, [bookList, sortBy]);
return { sortedBooks, setSortBy };
}
지금 보니 굉장히 불필요한 state들의 향연... 원하는 대로 동작만 하게 하자! 가 목표였기 때문에 코드는 💩일 수 밖에 없었다.
이 코드에서도 마찬가지. 하하..
지금은 다 derived state를 이용해서 쓸 데 없는 state들은 모두 없애주었다.
아무튼 내 책장에 추가한 모든 책인 bookList가 있고, 이 책들을 sort해서 보여주고 싶은데,
처음에 쓴 코드에서는 따로 sortedBooks라는 state를 만들고, sortBy(선택한 분류기준)이 바뀔 때마다 sortedBooks가 업데이트 되도록 했다.
// useMemo로 리팩토링 후 useSort()
export default function useSort() {
const bookList = useRecoilValue(booksState);
const [sortBy, setSortBy] = useState<SortOptions>("createdAt");
const sortedBooks = useMemo(
() =>
[...bookList].sort((x, y) => {
if (sortBy === "author") {
return x.author > y.author ? 1 : x.author < y.author ? -1 : 0;
} else if (sortBy === "title") {
return x.title > y.title ? 1 : x.title < y.title ? -1 : 0;
} else if (sortBy === "rating" && x.rating && y.rating) {
return x.rating > y.rating ? -1 : x.rating < y.rating ? 1 : 0;
} else return x.createdAt.seconds - y.createdAt.seconds;
}),
[bookList, sortBy]
);
return { sortedBooks, setSortBy };
}
'Recap > bookshelf' 카테고리의 다른 글
| bookshelf 프로젝트 회고 (0) | 2024.03.09 |
|---|---|
| lighthouse 점수 올리기 (접근성+SEO 개선) (0) | 2024.03.07 |
| [리팩토링 #02.] 아토믹 디자인 패턴 Atomic Design Pattern 적용하기 (0) | 2024.02.25 |
| [에러 해결] Firebase Error: No document to update (0) | 2024.02.25 |
| [리팩토링 #01.] 중복되는 코드를 Dumb component로 만들어 리팩토링하기 (0) | 2024.02.25 |