WeakMap의 키를 “입력 배열의 참조 자체”로 쓰면 캐시 무효화 로직이 0줄이 된다 — 무효화를 참조 동등성에 위임.

언제: 같은 배열을 이름 같은 키로 반복 조회해서 O(n) 스캔이 계속 도는데, 그 배열이 언제 바뀌는지 추적하기는 싫을 때.

절차

const indexCache = new WeakMap<Item[], Map<string, string>>()

// name → id 역인덱스를 1회 빌드 (O(n))
const buildIndex = (items: Item[]) =>
  new Map(items.map((it) => [it.name, it.id]))

function lookup(items: Item[], name: string): string | null {
  let index = indexCache.get(items)
  if (!index) {
    index = buildIndex(items) // 캐시 미스일 때만 빌드
    indexCache.set(items, index)
  }
  return index.get(name) ?? null
}

확인: 같은 배열 참조로 두 번 부른다. buildIndex한 번만 돌면 된 것이다.

함정: 호출 측 items 참조가 stable해야 작동한다. 매번 [...data]·data.filter()로 새 배열을 넘기면 항상 miss라 무의미하다. (TanStack Query의 data/select 결과는 참조 안정성을 보장하므로 그대로 넘기면 OK)

왜 되는가

  • 같은 참조로 재호출 → cache hit, O(1)
  • 데이터가 새 참조로 교체(refetch 등) → 자동 miss → 새 index 빌드. 옛 index는 옛 items와 함께 GC 대상
  • 키가 weak reference라 items가 어디서도 안 잡히면 entry도 같이 사라짐. 일반 Map이면 참조가 영구히 붙들려 메모리 누수

참고