useMemo는 계산 결과를 캐싱해주는 React Hook이다.
const cachedValue= useMemo(cacluateValue, dependencies)
useMemo는 처음에는 calculateValue를 호출한 결과를 반환하자만, 다음 렌더링에서는 저장된 값을 반환하거나 calculateValue를 다시 호출하고 반환된 값을 저장한다.
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(()=>filterTodos(todos, tab), [todos, tab]);
}
//todos, tab이 변경된다면
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab), [todos, tab]
}
대부분의 계산은 매우 빠르기때문에 useMemo가 필요하지 않지만, 매우 큰 배열을 필터링하거나 비용이 많이 드는 계산을 수행하는 경우 사용하는 것이 좋다.
또한 성능 최적화를 위해서만 useMemo를 사용해야 하고, 먼저 기능을 구현하고 useMemo를 사용하여 성능을 개선해야한다.
console.time('filter array')
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array')
위와같이 소요되는 시간을 측정할 수 있고, chrome의 CPU 스로틀링 옵션을 사용하여 인위적으로 낮능 성능으로 테스트할 수도 있다.
이외의 경우에는 useMemo로 감싸는 것에 대한 이득이 매우 적을 수 있다.
React는 컴포넌트가 다시 렌더링될때, 모든 자식 컴포넌트를 재귀적으로 다시 렌더링한다. 그렇기 때문에 자식컴포넌트의 렌더링이 느리다면 자식컴포넌트를 memo 로 감싸서 props가 마지막 렌더링 시점과 동일한 경우 다시 렌더링 하는 것을 생략할 수 있다.
사실 컴포넌트를 memo로 감싸는 것은 useMemo로 감싸도 된다. 다만 편리한 방법이 아니기 때문에 memo로 감싸는 것을 추천.
function Dropdown({ allItems, text}) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(()=> {
searchItems(allItems, searchOptions)
}, [allItems, searchOptions])
}
위 코드와 같이 어떤 객체에 의존하게 되면 컴포넌트가 렌더링 될 때마다 모든 코드가 다시 실행되기 때문에(searchOptions가 매번 바뀐다) 메모이제이션의 효과가 나타나지 않는다.
이 때는 searchOptions또한 메모이제이션 하면
function Dropdown({ allItems, text}) {
const searchOptions = useMemo(()=>{ matchMode: 'whole-word', text }, [text]);
const visibleItems = useMemo(()=> {
searchItems(allItems, searchOptions)
}, [allItems, searchOptions])
}
text가 변하지 않는다면 → searchOptions가 변하지 않는다 → visibleItems가 변하지 않는다.
이 구조를 조금 개선한다면
function Dropdown({ allItems, text}) {
const visibleItems = useMemo(()=> {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions)
}, [allItems, text])
}
함수를 만드는 것은 문제가 아니고 피해야하는 일은 아니다. 다만 정의된 함수는 자식 컴포넌트를 렌더링하게 만들 여지가 있다.
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
위의 ProductPage 컴포넌트가 렌더링 되면서, handleSubmit은 매번 새로운 함수가 되기 때문에 Form 컴포넌트 또한 매번 렌더링 되게 된다.
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
useMemo를 사용하여 새로운 함수를 메모이제이션하여 사용할 수 있는데, 이때 이렇게 사용하는 것보다 새로운 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을 정리해보겠다!