준영이의 성장일기(FrontEnd)
React 공식문서 도장깨기(4) - useMemo 본문
9월 3일에 정리해볼 내용은 성능 최적화, 캐싱, 메모리제이션 개념과 연결되는 useMemo에 대해서 정리하고자 한다.
<참고 레퍼런스>
https://react-ko.dev/reference/react/useMemo
useMemo – React
The library for web and native user interfaces
react-ko.dev
<useMemo>
개념: useMemo는 리렌더링 사이의 계산 결과를 캐시할 수 있는 React 훅이다.
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
✅ calculateValue
-> 연산을 해주는 함수이고 이 함수는 순수함수여야 하며, 인자를 받지 않고, 반드시 어떤 타입이든 값을 반환해야 한다. 위에 코드에서 () => filterTodos(todos,tab)이 이에 해당한다.
-> () => filterTodos(todos, tab) 말고 return 형식으로도 작성 가능(명시적으로 return 작성 해주는게 혼동 방지)
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab);
}, [todos, tab]);
// ...
}
-> 초기 렌더링 중에 함수를 호출하고 이후의 렌더링에서는 의존성이 이전 렌더링 이후 변경되지 않았다면 동일한 값을 반환한다. 즉, 의존성이 변경 되었을 경우에 calculateValue를 호출하고 그 결과를 반환한다.
-> 반환 값을 캐싱하는 것을 메모화라 하며, 이것이 이 훅을 useMemo라고 부르는 이유이다.
✅ dependencies
-> calculateValue 코드 내에서 참조되는 모든 반응형 값들의 목록이다. useEffect에서 사용되는 dependencies개념과 동일한 개념이다.
-> 간단히 말해서, useMemo는 의존성이 변경되기 전까지 계산 결과를 캐시하고 있다가 의존성이 변경되는 순간 다시 함수를 호출하여 새로운 값을 반환받는다.
❗useMemo는 첫 번째 렌더링을 더 빠르게 만드는 것이 아니다. 즉, 업데이트 시에 불필요한 작업을 건너뛰는 데 도움이 되기 때문에 성능 최적화에 적합하다.
<useMemo를 사용하는 시기>
위에 첨부된 이미지를 봐보자. 만약 큰 배열을 필터링하거나, 변환하거나, 고비용의 계산을 수행할 때, 데이터가 변경되지 않았다면 다시 계산하는 것을 사용자는 건너뛰고 싶을 수 있다. 기본적으로 React는 컴포넌트가 다시 렌더링될 때마다 컴포넌트 전체 본문을 다시 실행하기에 해당 코드는 성능 상으로 좋지 않다.
이럴 때 성능 최적화가 필요하고 이럴 때 useMemo를 사용하면 된다. 만약 todos와 tab이 이전 렌더링 때와 동일하다면, 이전처럼 계산을 useMemo로 감싸서 이전에 이미 계산해놓은 visibleTodos를 재사용 할 수 있도록 하자.
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
<useMemo/memo를 이용하여 자식 컴포넌트의 리렌더링 막기>
해당 코드가 있다고 가정 해보자
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
기본적으로 컴포넌트가 리렌더링되면 React는 모든 자식 컴포넌트(List 컴포넌트)를 재귀적으로 리렌더링 한다. 이 때문에 다른 theme로 TodoList가 리렌더링되면 List 컴포넌트도 리렌더링 된다.
만약 리렌더링에 많은 계산이 사용된다면 리렌더링 속도가 느려지는 것을 확인할 수 있다. 이때 자식 컴포넌트가 리렌더링을 건너띄도록 memo로 감싸주는 방법이 있다.(이 방법은 이번에 정리하면서 처음 알게됐다.)
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
넘어온 items라는 props가 변경되지 않았다면 리렌더링을 건너띈다. 이처럼 자식 컴포넌트에 memo로 감싸는 방법 혹은 부모 컴포넌트에서 useMemo훅을 사용하여 계산된 값을 자식 컴포넌트로 넘겨주는 방법이 있다.(이때 또한 계산된 값이 그대로면 리렌더링을 하지 않을것이다.)
<다른 훅의 의존성 메모화>
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// 🚩 주의: 컴포넌트 내부에서 생성한 객체 의존성
// ...
다음 코드에는 문제점이 존재한다. 컴포넌트가 다시 렌더링되면 컴포넌트 본문 내부의 모든 코드가 다시 실행되는데 이때 searchOptions이라는 객체가 렌더링 될 때마다 새롭게 생성된다. 이때마다 새롭게 생성되면 메모리제이션의 이점이 사라지기 때문에 다음과 같은 2가지 방법으로 해결할 수 있다.
- searchOptions객체는 text 변경시에 변경되고 searchOptions 객체가 변경 될 때 visibleItems의 값이 변경된다.(연쇄 작용)
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes
// ✅ text 변경시에만 변경됨
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ✅ allItems 또는 searchOptions 변경시에만 변경됨
// ...
- searchOptions 객체를 별도로 메모이제이션하지 않고, text나 allItems가 변경될 때마다 새로운 searchOptions 객체를 생성하는 방법
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ✅ allItems 또는 text 변경시에만 변경됨
// ...
다음 블로그 글은 useMemo에 이어서 useCallback에 대해서 정리할 예정이다.
'프론트엔드 > React.js' 카테고리의 다른 글
React 공식문서 도장깨기(6) - useTransition (2) | 2024.09.07 |
---|---|
React 공식문서 도장깨기(5) - useCallback (7) | 2024.09.05 |
React 공식문서 도장깨기(3) - useRef (1) | 2024.08.29 |
React 공식문서 도장깨기(2)-2 : useEffect, useLayoutEffect (3) | 2024.08.27 |
React 공식문서 도장깨기(2)-1 : useEffect (0) | 2024.08.26 |