React useMemo: 상품 필터 계산을 정말 재사용해야 하는가
관련 없는 테마 변경에도 느린 상품 필터가 반복되는지 측정하고, 의존성이 같을 때 계산 결과를 재사용한 뒤 같은 상호작용을 다시 비교한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
useMemo는 의존성이 이전 Render와 같을 때 계산 결과를 재사용하는 성능 최적화다. 모든 계산에 먼저
넣지 않는다. 느린 상호작용을 관찰하고, 어떤 계산이 반복되는지 측정하고, 적용 뒤 같은 조건을 다시
측정한다.
캐시가 없어도 기능은 정확해야 한다. React가 결과를 다시 계산해도 같은 UI가 나와야 한다.
이 글을 관통하는 상황: 테마만 바꿨는데 상품 필터가 느리다
다음 예제는 필터 비용을 눈으로 확인하려고 같은 필터를 여러 번 반복하는 인위적 실험이다. 실제 서비스 코드에 반복문을 복사하지 않는다.
실험 전에 React Compiler 활성화 여부를 확인한다. React Compiler는 빌드 단계에서 코드를 분석해
계산 결과와 함수를 자동으로 재사용할 수 있는 도구다. 활성 환경에서 테마 변경 때 필터 비용이 이미
반복되지 않는다면 그것도 유효한 측정 결과이며 수동 useMemo를 추가하지 않는다. 아래 비교는
테마 변경에도 slowFilter 비용이 실제로 반복되는 환경을 전제로 한다.
import { useState } from 'react'
const PRODUCTS = Array.from({ length: 25000 }, (_, index) => ({
id: index,
name: `상품 ${index}`,
}))
function slowFilter(products, query) {
let matches = []
for (let repeat = 0; repeat < 20; repeat += 1) {
matches = products.filter(product => product.name.includes(query))
}
return matches
}
export default function Catalog() {
const [query, setQuery] = useState('')
const [isDark, setIsDark] = useState(false)
const visibleProducts = slowFilter(PRODUCTS, query)
return (
<section data-theme={isDark ? 'dark' : 'light'}>
<input
value={query}
onChange={event => setQuery(event.target.value)}
aria-label="상품 검색"
/>
<button type="button" onClick={() => setIsDark(value => !value)}>
테마 변경
</button>
<p>현재 테마: {isDark ? '어두움' : '밝음'}</p>
<p>검색 결과: {visibleProducts.length}개</p>
</section>
)
}검색어를 상품 24로 바꿀 때 필터가 다시 실행되는 것은 필요하다. 하지만 검색어를 그대로 두고 테마
버튼만 눌러도 같은 필터가 다시 실행돼 버튼 반응이 늦어진다. isDark는 필터 입력이 아니지만 상태
변경은 Catalog 전체의 새 Render를 요청하기 때문이다.
정확한 지연은 기기와 빌드에 따라 다르다. 이 예제가 충분히 느리지 않다면 반복 횟수를 늘리되, 전후 비교에서 같은 값과 같은 환경을 사용한다.
관련 없는 Render도 함수 본문의 계산을 다시 실행한다
먼저 React 개발자 도구의 React Profiler로 두 상호작용을 따로 기록한다. React Profiler는 React가
계측한 committed update의 Render 시간을 보여 주며, actualDuration은 해당 업데이트를 Render하는
데 걸린 시간을 뜻한다.
- 검색어를 바꿀 때
CatalogRender 시간을 기록한다. - 검색어를 그대로 두고 테마만 바꿀 때 같은 시간을 기록한다.
- 테마 변경에도 비슷한 지연이 반복되는지 확인한다.
개발 Strict Mode는 계산 함수를 추가 호출할 수 있고 개발 빌드 자체도 느리다. 최종 판단은 운영용 빌드와 실제 사용자에 가까운 기기에서 한다. React Native도 같은 JavaScript 계산 문제를 겪을 수 있으므로 대상 앱의 release 빌드를 실제 저사양 기기에서 측정한다.
계산이 읽는 반응형 값을 의존성으로 둔다
측정에서 필터 반복이 병목임을 확인했다면 계산을 다음처럼 바꾼다.
import { useMemo, useState } from 'react'
// PRODUCTS와 slowFilter는 이전 예제와 같다.
export default function Catalog() {
const [query, setQuery] = useState('')
const [isDark, setIsDark] = useState(false)
const visibleProducts = useMemo(
() => slowFilter(PRODUCTS, query),
[query],
)
return (
<section data-theme={isDark ? 'dark' : 'light'}>
<input
value={query}
onChange={event => setQuery(event.target.value)}
aria-label="상품 검색"
/>
<button type="button" onClick={() => setIsDark(value => !value)}>
테마 변경
</button>
<p>현재 테마: {isDark ? '어두움' : '밝음'}</p>
<p>검색 결과: {visibleProducts.length}개</p>
</section>
)
}필터 계산이 읽는 반응형 값은 query뿐이다. PRODUCTS는 컴포넌트 바깥 상수이므로 Render 때문에
바뀌지 않는다. 이제 테마 변경에는 이전 결과 배열을 재사용하고, 검색어가 바뀌면 새 결과를 계산한다.
상품 목록을 props로 받는 구조라면 계산이 products도 읽으므로 의존성은 [products, query]다.
부모가 매 Render마다 내용이 같은 새 배열을 만들면 객체 동일성이 달라져 메모화를 깨뜨릴 수 있다.
이때는 무작정 의존성을 빼지 말고 목록이 왜 매번 새 값인지 데이터 경계를 먼저 확인한다.
useMemo는 기능 정확성의 조건이 아니다
따라서 다음 용도로 사용하지 않는다.
- 최초 한 번만 만들어야 하는 외부 연결을 보관
- 반드시 유지되어야 하는 사용자 입력이나 서버 응답을 저장
- Render 중 부수 효과가 한 번만 실행되게 막기
이런 코드는 캐시가 사라지면 기능이 달라진다. useMemo를 제거했을 때 느려질 수는 있어도 결과가
틀리면 안 된다.
같은 상호작용을 전후로 다시 측정한다
교정 뒤에는 다음 네 결과를 함께 본다.
- 최초 화면에서는 필터가 한 번 이상 계산된다.
- 검색어를 바꾸면 결과 수와 필터 계산이 함께 갱신된다.
- 테마만 바꾸면 화면의 테마 이름은 바뀌고 필터 계산은 건너뛴다.
- 의존성 비교와 캐시 비용까지 포함한 전체 상호작용이 실제로 개선된다.
정확한 비교가 필요하면 대상 프레임워크가 지원하는 프로파일링 계측을 포함한 운영 유사 빌드나 React 개발자 도구가 지원하는 대상 환경을 사용한다. 일반 운영 빌드가 항상 같은 계측 정보를 포함한다고 가정하지 않는다. 한 번의 숫자보다 같은 동작을 여러 번 기록해 분포와 느린 구간을 본다.
React Compiler 사용 여부도 현재 환경에서 확인한다
Compiler를 사용한다는 추측만으로 수동 코드를 지우거나 추가하지 않는다. 프로젝트 설정과 컴파일 결과, 린트와 측정값을 확인한다. 실험 앞에서 비용 반복이 없었다면 수동 최적화를 추가하지 않는다. Compiler를 쓰지 않는 프로젝트에도 이 글의 핵심인 “측정한 병목에만 최적화를 적용하고 다시 측정한다”는 판단은 그대로 남는다.
문제 검색어가 그대로인데 테마 변경도 느림
잘못된 판단 계산 입력이 같으면 React가 일반 함수 호출도 자동 생략함
관찰 테마 변경 Render에서도 slowFilter 비용이 반복됨
원인 컴포넌트 함수 본문은 Render마다 다시 실행됨
행동 측정된 필터를 query 의존성의 useMemo로 감쌈
검증 검색 변경은 재계산, 테마 변경은 재사용, 전체 시간이 감소풀 리퀘스트에서는 다음을 확인한다.
- 느린 사용자 상호작용과 반복되는 계산을 실제로 측정했는가?
- 계산 함수는 props·상태를 바꾸지 않는 순수 계산인가?
- 계산이 읽는 모든 반응형 값이 의존성에 있는가?
- 항상 새 객체인 의존성 하나가 메모화를 깨뜨리지 않는가?
- 같은 빌드·기기·상호작용으로 적용 전후를 다시 비교했는가?
- React Compiler 사용 여부를 설정과 출력으로 확인했는가?
기억할 순서는 짧다.
측정하고, 필요한 계산만 재사용하고, 같은 조건에서 다시 측정한다.
다음 글에서는 함수 참조를 재사용하는 useCallback이 자식 컴포넌트와 Effect에 실제로 언제 필요한지
살펴본다.
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01검색어가 그대로인데 테마 버튼만 눌러도 느린 필터가 다시 실행되는 이유는 무엇인가?
정답
테마 상태 변경으로 컴포넌트가 다시 Render되면 함수 본문의 필터 계산도 기본적으로 다시 실행되기 때문이다.
관련 설명 다시 읽기왜 그런가
계산 입력이 같은 것과 React가 자동으로 이전 계산 결과를 재사용하는 것은 별도 보장이다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02상품 목록이 컴포넌트 바깥 상수이고 필터가 query만 읽는다면 useMemo 의존성은 무엇이어야 하는가?
정답
query다. query가 Object.is로 이전 값과 같으면 직전 결과를 재사용하고 달라지면 다시 계산한다.
관련 설명 다시 읽기왜 그런가
계산 함수가 읽는 모든 반응형 값은 의존성에 포함하되 컴포넌트 바깥 상수는 Render로 바뀌지 않는다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03useMemo 캐시가 버려져 계산이 다시 실행되면 기능 결과가 달라져도 될까?
정답
안 된다. useMemo는 성능 최적화이므로 다시 계산해도 같은 결과와 동작이 나와야 한다.
관련 설명 다시 읽기왜 그런가
기능의 정답이나 반드시 유지할 객체는 useMemo 캐시 존재에 의존하지 않는다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04useMemo를 추가한 뒤 무엇을 확인해야 최적화가 유효하다고 말할 수 있는가?
정답
같은 대상 환경과 상호작용에서 계산 시간, committed update의 actualDuration 또는 전체 지연이 실제로 줄고, 검색어 변경 때 결과는 정상 갱신되는지 확인한다.
관련 설명 다시 읽기왜 그런가
첫 Render는 빨라지지 않고 비교 비용도 있으므로 적용 전후 측정이 필요하다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- useMemoReact · 공식 문서 · 확인 2026-08-09
- ProfilerReact · 공식 문서 · 확인 2026-08-09
- React Compiler IntroductionReact · 공식 문서 · 확인 2026-08-09
- Performance OverviewReact Native · 공식 문서 · 확인 2026-08-09