useMemo 는 재렌더링 간에 계산 결과를 cache할 수 있는 리액트 훅이다.
const cachedValue = useMemo(calculateValue, dependencies)
- Useage
- 비용이 비싼 재계산 skip
- 컴포넌트 re-rendering skip
- 다른 Hook의 의존성 기억
- 함수 기억
- Reference
- useMemo(calculateValue, dependencies)
Useage
비용이 비싼 재 계산 skip
re-rendering 간에 계산을 cache 하려면 useMemo 구성 요소의 최상위 수준에서 호출로 감싼다.
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tabs]);
// ...
}
useMemo 엔 두 가지를 전달해야 한다.
- ( )=> 과 같이 인수를 받지 않는 함수, return
- 계산 내에서 사용되는 구성 요소 내의 모든 값을 포함하는 dependencies
첫 번째 render에서 useMemo에서 얻을 수 있는 값은 계산 함수의 결과 값이다.
이후의 render에서 React는 dependencies들을 마지막에 전달 받은 dependencies들 과 비교한다.
만약 변화가 없다면 useMemo 는 이미 계산된 결과값을 return한다. 아니라면 새로 계산하여 값을 return한다.
다시 말해 useMemo는 dependencies들이 변화할때까지 계산된 결과를 저장한다.
example)
TodoList가 새로운 props를 받거나 state가 update되면, filterTodos 함수 또한 재 실행 될 것이다.
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}
대부분의 경우에서는 빠를 수 있지만, 긴 배열이나 비용이 많이드는 계산에서는 성능이 저하될 수 있다.
useMemo 는 성능최적화로만 사용해야한다. useMemo 가 없이도 코드가 작동하지 않으면, 다른 문제를 수정해야한다. 그 뒤 퍼포먼스 향상을 위해 useMemo를 사용하다.
‘비용이 많이든다’의 정의가 무엇인가?
console log를 추가하여 시간을 측정해보자
console.time('filter array'); const visibleTodos = filterTodos(todos, tab); console.timeEnd('filter array');
1ms가 넘을경우 useMemo사용을 권장한다.
또 useMemo 는 첫번째 render를 빠르게 만들지 않는다. 단지 불필요한 업데이트만 건너뛰게 할 뿐이다.
컴포넌트들의 re-rendering skip
useMemo는 자식 컴포넌트 re-rendering의 성능을 향상시킨다.
아래는 TodoList 컴포넌트에서 List컴포넌트로 visibleTodos를 prop로 전달한다.
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
컴포넌트가 re-render할때, React는 모든 자식컴포넌트들을 re-render 한다.
TodoList가 re-render할때마다 List도 re-render한다는 의미다. 만약 List를 re-render할때 느리다면 memo를 통해 skip해주도록 하자.
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
위와같이 변경하면 props가 동일할 경우 re-render를 skip한다.
export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
}
위 같은 경우엔, vicibleTodos가 항상 새로운 배열을 생성해서 이전에 만들어둔 memo가 제 역할을 못한다.
useMemo를 어디서 사용하는지 중요하다.
export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
}
다른 Hook의 의존성 기억
컴포넌트에서 직접 생성된 객체에 의존하는 계산이 있다고 하자.
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가 새로 생성될때마다 re-render된다. 이때 searchOptions를 useMemo로 감싸주자.
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ...
더 깔끔하게 아래와 같이 바꾸는것이 좋겠다.
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
// ...
함수 기억
Form컴포넌트가 memo로 감싸져있고 props로 함수를 넘긴다고 가정하자.
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
useMemo로 함수를 감싸보자.
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
함수와 같은경우는 위보단 useCallback을 사용하자.
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
이전과 동일한 기능이지만, useMemo를 사용할때처럼 중첩함수를 사용하지 않아도 된다.
Reference
useMemo(calculateValue, dependencies)
memo된 값을 선언하려면 useMemo를 구성요소의 최상위에서 호출한다.
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
매개변수
- caculateValue : 메모하고 싶은 값을 계산하는 함수. 순수함수여야 한다. React는 초기 렌더링 중에 함수를 호출한다. 이후에 변경되지 않은 경우 동일한 값을 다시 반환한다. dependencies가 동일하지 않으면 다시 호출하여 결과를 반환하고 나중에 재사용 할 수 있도록 저장한다.
- dependencies : calculateValue 코드에서 참조하는 모든 value들. 반응형 값에는 props, state 및 구성요소에 직접 선언된 모든 변수와 함수가 포함된다. [dep1, dep2, dep3]과 같이 작성되어야 한다.
Returns
초기 렌더링에서 useMemo는 인수 없이 호출한 calculateValue결과를 반환한다.
후속 렌더링 중에 마지막 렌더링에서 종속성이 변경되지 않은 경우 이미 저장된 값을 반환하거나 변경이 되었다면, calculateValue다시 호출하고 반환 된 결과를 calculateValue반환한다.
주의
- useMeme는 Hook이므로 컴포넌트의 최상위 또는 자체 Hook에서만 호출할 수 있다. 반복문이나 조건문에서는 호출 불가.
- React는 특별하지 않은 경우 cache된 값을 버리지 않는다.
'React.js + Next.js > 번역하기' 카테고리의 다른 글
03. useState (0) | 2024.04.02 |
---|---|
01. memo (0) | 2023.12.04 |