useMemo는 재렌더링 사이에 계산 결과를 캐싱할 수 있게 해주는 React Hook이다.
const cachedValue = useMemo(calculateValue, dependencies)
컴포넌트의 최상위 레벨에 있는 'useMemo'를 호출하여 재렌더링 사이의 계산을 캐싱한다.
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
여기 들어가는 매개변수로는
calculateValue
- 캐싱하려는 값을 계산하는 함수.
- 인자를 받지 않고, 모든 타입의 값을 반환할 수 있어야 한다.
- React 초기 렌더링 중 함수를 호출
- 다음 렌더링 할 때 dependencies 가 변경되지 않았을 시 동일한 값을 다시 반환
dependencies
- calculateValue 코드 내에서 참조된 모든 반응형 값들의 목록
- 반응형 값에는 props, state 컴포넌트 바디에 직접 선언된 모든 변수와 함수가 포함된다.
- 인라인 형태로 작성
반환값
초기 렌더링에서 useMemo는 인자없이 calculateValue를 호출한 결과를 반환한다.
다음 렌더링에서, 마지막 렌더링시 저장된 값을 반환하거나 종속값이 변했다면 다시 함수를 호출하고 반환된 값을 저장한다.
주의할점은
useMemo는 Hook 이므로 컴포넌트의 최상위 또는 자체 Hook에서만 호출할 수 있다.
반복문이나 조건문 내부에서는 호출 불가능
Strict Mode에서는 실수로 발생한 오류를 찾기위해 계산 함수를 두 번 호출한다.
이는 개발 환경에서만 동작하는 방식이며, 실제 프로덕션 환경에서는 영향 X
React는 캐싱 된 값을 버려야 할 특별한 이유가 없는 한 버리지 않는다.
이와 같이 반환값을 캐싱하는 것을 memoization 이라고 하며, 이 훅을 useMemo라고 부르는 이유이다.
일반적으로는 대부분 계산이 빠르기 때문에 문제가 되지 않는다.
하지만 큰 배열을 필터링 하거나 변환 등 비용이 많이 드는 계산을 수행하는 경우에 사용을 하면 좋을 것이다.
성능 최적화를 위해서만 useMemo를 사용해야 한다.
만약 이 기능이 없어서 코드가 작동하지 않는다면 근본적으로 문제가 있는 것이다.
비싼 연산인지 어떻게 알까?
일반적으로 수천 개의 개체를 만들거나 반복하는 경우가 아니라면 비용이 많이 들지 않는다.
정확하게 확인하고 싶다면
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
이런식으로 작성해 확인해 보는것도 좋다.
만약 기록된 시간이 크다면 useMemo를 적용 하고 난 후 시간 비교를 해보는것도 좋다.
모든 곳에 useMemo를 추가해야 하나?
useMemo로 최적화하는 것은 몇몇 경우에만 유용하다.
- useMemo에 입력하는 계산이 눈에 띄게 느리고 종속성이 거의 변경되지 않는 경우
- memo로 감싸진 컴포넌트에 prop으로 전달할 경우. 메모이제이션을 사용하면 의존성이 동일하지 않은 경우에만 컴포넌트를 다시 렌더링 할 수 있다.
- 전달한 값을 나중에 일부 Hook의 종속성으로 이용할 경우. 예를 들면 useEffect의 값에 종속되어 있다거나..
이 외는 useMemo를 사용하는데 있어 이득이 없다.
그러나 사용을 해도 크게 문제가 되는 것도 아니라 일부 팀에서는 가능한 한 많이 메모하는 방식을 선택하기도 한다.
하지만 이 경우 단점으로는 코드 가독성이 떨어진다는 것이다.
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
이런 식으로 컴포넌트 자체를 감싸서 사용할수도 있다.
동작 방식은 동일하다. 위 경우는 visibleTodos가 변경되지 않는 경우 List는 다시 렌더링 되지 않는다.
다른 Hook의 종속성 메모화
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 주의: 컴포넌트 본문에서 생성된 객체에 대한 종속성
// ...
이런 코드를 보면 메모이제이션의 목적을 무색하게 만든다.
컴포넌트가 다시 렌더링 되면 컴포넌트 본문 내부의 모든 코드가 다시 실행되기 때문이다.
searchOptions 객체를 생성하는 코드도 다시 레더링 될 때마다 실행이 된다.
이 문제를 해결하기 위해서는 searchOptions 객체 자체를 종속성으로 전달하기 전에 메모해주면 된다.
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ text가 변경될 때만 변경
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ allItems이나 searchOptions이 변경될 때만 변경
// ...
문제는 해결했지만 더 나은 방법으로는 searchOptions를 useMemo 계산 함수의 내부에 선언하는 것이다.
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ allItems이나 text가 변경될 때만 변경
// ...
함수를 메모해두고 싶은 경우
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
이런식으로 함수를 메모해두고 사용하고 싶은 경우가 있다면 React에는 이를 위한 Hook이 따로 있다.
useCallback을 사용하는 것인데
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
이렇게 된다.
위 두 코드는 완전히 동일하게 동작을 한다.
useCallback의 유일한 장점은 내부에 중첩된 함수를 추가로 작성하지 않아도 된다는 점이다.
'리액트 공식문서 읽어보기' 카테고리의 다른 글
| useCallback (0) | 2024.03.02 |
|---|---|
| <Suspense> (1) | 2024.02.25 |
| lazy (0) | 2024.02.25 |
| useState (0) | 2024.02.24 |
| useRef (0) | 2024.02.23 |