무엇
cache는 함수를 감싸, 같은 인자로 다시 부르면 계산을 되풀이하지 않고 앞선 결과를 돌려주게 만드는 함수다. React 서버 컴포넌트 전용이라 클라이언트 컴포넌트에서는 동작하지 않는다.
돌려받은 함수는 원래 함수와 같은 타입을 갖는다. cache를 부르는 시점에는 fn을 실행하지 않는다.
import { cache } from 'react'
const getMetrics = cache(calculateMetrics)
무슨 일이 일어나나
cache가 돌려준 함수는 호출될 때 인자를 열쇠 삼아 캐시를 확인한다. 그 인자로 이미 계산한 결과가 있으면 fn을 부르지 않고 저장된 값을 준다. 없으면 fn을 실행하고 결과를 캐시에 넣은 뒤 돌려준다.
캐시는 서버 요청 하나의 범위다. 매 요청마다 비워지므로 요청 사이에 값이 남지 않는다. 인자 비교는 Object.is 얕은 비교라, 값이 같아도 참조가 다른 객체를 넘기면 캐시가 맞지 않는다 — 기본형이나 같은 참조를 넘겨야 적중한다. fn이 특정 인자에서 예외를 던지면 그 예외도 캐시되어, 같은 인자로 다시 부르면 다시 던진다.
사용법
cache 호출은 컴포넌트 밖, 모듈 최상위에 두고 결과를 여러 컴포넌트가 import해서 함께 쓴다. cache를 부를 때마다 별개의 캐시가 생기므로, 같은 함수를 여러 번 감싸면 서로 캐시를 공유하지 못한다.
import { cache } from 'react'
// 한 번만 감싸 export — import하는 쪽이 같은 캐시를 공유한다
export const getUser = cache(async (id: string) => {
return await db.user.query(id)
})
import { getUser } from './getUser'
// 서버 컴포넌트 — 같은 id면 실제 조회는 한 번만 나간다
export async function Profile({ id }: { id: string }) {
const user = await getUser(id)
return <h1>{user.name}</h1>
}
실무 예시
한 페이지의 서로 다른 컴포넌트가 같은 데이터를 필요로 할 때, 각자 조회하면 같은 요청이 여러 번 나간다. cache로 감싸 두면 첫 조회만 실제로 나가고 나머지는 캐시에서 읽는다.
import { getTemperature } from './getTemperature'
export async function AnimatedWeather({ city }: { city: string }) {
const t = await getTemperature(city)
return <BigCard temp={t} />
}
export async function MiniWeather({ city }: { city: string }) {
const t = await getTemperature(city) // 같은 city — 캐시 재사용
return <SmallCard temp={t} />
}
왜 중요한가
서버 컴포넌트에서 같은 데이터를 여러 번 긷는 낭비를 막는 깔끔한 도구지만, 서버 렌더라는 특정 맥락에서만 쓸 수 있다. 서버 컴포넌트를 쓰는 프로젝트라면 유용하고, 아니면 마주칠 일이 없다. 쓰임이 좁은 도구다.
Reference