https://ko.react.dev/reference/react/useMemo#skipping-re-rendering-of-components
재렌더링 사이 계산 결과를 캐싱할 수 있도록 React Hook
같은 값을 반환하는 함수를 반복적으로 호출한다면 이전에 계산한 값을 메모리에 저장.
동일한 계산의 반복수행을 제거해서 실행 속도를 빠르게 할 수 있는 목적으로 사용.
DP의 Memoization 을 생각하면 된다.
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
visibleTodos라는 Props가 바뀌지 않는다면, List 컴포넌트는 useMemo 훅을 통해
메모리에 캐싱된 값을 사용하여 리렌더링하지 않는다.
useMemo는 두번째 인수로 의존성 배열을 전달해야 렌더링 될때마다 계산이 다시 실행되지 않는다
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
계산 결과를 메모이제이션 하는 useMemo와 다르게 useCallback은 이벤트 핸들러 등 함수를 메모이제이션
할 때 사용되는 훅.
컴포넌트가 리렌더링할때 함수를 새로 생성하는 것을 막고 이전에 생성한 함수를 재사용하여 불필요한 렌더링을
줄이려는 목적으로 사용.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
JS에서 함수는 객체로 취급, 함수를 동일하게 만들어도 메모리 주소가 다르면 다른 함수로 간주하게 된다.
const add1 = () => x + y;
const add2 = () => x + y;
add1 === add2 // false
동일한 코드지만 일치연산자로 비교하면 false를 반환한다.
const [data, setData] = useState(null);
const fetchData = () =>
fetch(`https://test-api.com/data/${id}`)
.then((response) => response.json())
.then(({ data }) => data);
useEffect(() => {
fetchData().then((data) => setData(data));
}, [fetchData]);
따라서 fetchData 함수를 호출해서 잘 돌아가는 것 처럼 보이나,
함수의 동등성 이슈로 인해 컴포넌트가 렌더링 될때마다 새로운 참조 값을 갖게 된다.
따라서 함수의 변경이 일어났기 때문에 useEffect가 실행되어 무한 루프에 빠지게 된다.
const fetchData = useCallback(
() =>
fetch(`https://test-api.com/data/${id}`)
.then((response) => response.json())
.then(({ data }) => data),
[id]
);
useEffect(() => {
fetchData().then((data) => setData(data));
}, [fetchData]);
이럴때 useCallback을 활용하여 함수의 동등성을 유지하면 된다.
모든 함수, 결과값 마다 useCallback/ useMemo을 사용하는 것은 지양하자.
SW 성능 최적화시 꼭 트레이드 오프를 고려해야하는데,
메모리 사용 증가 혹은 어려운 유지보수 라는 대가를 꼭 기억하고 사용할 것