Next.js
React에서 useMemo를 지워도 될까
useMemo는 과도한 렌더링을 막기 좋지만, 많은 경우 불필요한 최적화다. 언제 지우고 언제 두어야 할지.
React에서 성능 최적화는 필요하지만, useMemo는 생각보다 필요 없는 경우가 많다. 특히 처음 배울 때는 모든 참조 값에 useMemo를 붙이곤 하는데, 실제로는 자식 컴포넌트가 React.memo를 쓰지 않으면 useMemo는 거의 도움이 안 된다.
언제 useMemo가 정말 필요한가
첫 번째, 계산이 정말 비싼 경우다. 배열 정렬, 복잡한 필터링, 또는 수백 개의 객체를 순회하는 작업이라면 메모이제이션이 의미 있다. 하지만 간단한 객체 생성이나 가벼운 변환은 메모리 오버헤드만 늘린다.
두 번째, 자식 컴포넌트가 React.memo로 래핑되어 있을 때다. 부모가 렌더링되면서 새 참조를 만들면, React.memo는 그것을 감지해서 자식도 리렌더링한다. 부모에서 useMemo로 참조를 유지해야 자식의 메모이제이션이 효과를 본다.
const Parent = () => {
const config = useMemo(() => ({ id: 1, name: 'test' }), []);
return <Child config={config} />;
};
const Child = React.memo(({ config }) => {
console.log('Child rendered');
return <div>{config.name}</div>;
});
위 코드에서 useMemo가 없으면 Parent가 리렌더링될 때마다 Child도 리렌더링된다.
지워도 되는 useMemo
자식 컴포넌트가 React.memo를 쓰지 않으면, 부모의 useMemo는 거의 아무 효과가 없다. 왜냐하면 자식은 부모가 렌더링되는 순간 자동으로 리렌더링되기 때문이다. 참조가 같든 다르든 상관없다.
계산 비용이 낮으면 지우는 게 맞다. 객체 하나 만드는 것, 간단한 배열 필터링 정도는 메모리 관리 비용이 더 클 수 있다. 특히 의존성 배열을 관리하는 번거로움을 생각하면, 간단한 코드가 차라리 낫다.
// 이건 보통 필요 없다
const items = useMemo(() => data.filter(x => x.active), [data]);
// 그냥 이렇게
const items = data.filter(x => x.active);
의존성 배열의 함정
useMemo를 쓸 때 가장 흔한 실수는 의존성 배열을 틀리는 것이다. 의존성을 빼먹으면 값이 제때 업데이트되지 않고, 너무 많으면 메모이제이션이 제대로 동작하지 않는다.
특히 객체나 배열을 의존성에 넣을 때는 주의해야 한다. 새 배열이 생성될 때마다 내부 값을 다시 계산하게 되므로 최적화가 무의미해진다.
// 나쁜 예: dependency가 매번 새로 생성됨
const result = useMemo(() => compute(data), [data || []]);
// 좋은 예: 실제 변경을 감지
const result = useMemo(() => compute(data), [data]);
실제 검증 방법
useMemo를 지워도 되는지 확인하는 가장 좋은 방법은 개발자 도구의 Profiler를 쓰는 것이다. useMemo를 제거했을 때 렌더링 시간이 눈에 띄게 늘어나지 않으면, 지우는 게 더 깔끔한 코드다.
또 다른 방법은 간단하게 값을 출력해보는 것이다. 부모가 리렌더링될 때 자식의 props가 정말 달라지는지 확인하면 된다. 같다면 자식에서 React.memo를 먼저 추가하고, 그 후에 부모의 useMemo를 고려하자.
정리
useMemo의 기본 원칙은 간단하다. 계산이 진짜 비싸고, 자식이 메모이제이션된 컴포넌트일 때만 사용하자. 그 외에는 코드의 복잡도만 늘어난다. React 팀도 useCallback이나 useMemo를 과도하게 쓰는 것을 경고하고 있다. 필요할 때 찾아 쓰는 게 가장 좋은 방식이다.