cache
cache๋ฅผ ํตํด ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ ์ฐ์ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํฉ๋๋ค.
const cachedFn = cache(fn);๋ ํผ๋ฐ์ค
cache(fn)
์ปดํฌ๋ํธ ์ธ๋ถ์์ cache๋ฅผ ํธ์ถํด ์บ์ฑ ๊ธฐ๋ฅ์ ๊ฐ์ง ํจ์์ ํ ๋ฒ์ ์ ๋ง๋ค ์ ์์ต๋๋ค.
import {cache} from 'react';
import calculateMetrics from 'lib/metrics';
const getMetrics = cache(calculateMetrics);
function Chart({data}) {
const report = getMetrics(data);
// ...
}getMetrics๊ฐ ์ฒ์ data๋ฅผ ํธ์ถํ ๋, getMetrics๋ calculateMetrics(data)๋ฅผ ํธ์ถํ๊ณ ์บ์์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํฉ๋๋ค. getMetrics๊ฐ ๊ฐ์ data์ ํจ๊ป ๋ค์ ํธ์ถ๋๋ฉด, calculateMetrics(data)๋ฅผ ๋ค์ ํธ์ถํ๋ ๋์ ์ ์บ์ฑ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
์๋ ์์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
๋งค๊ฐ๋ณ์
fn: ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ๊ณ ์ถ์ ํจ์.fn์ ์ด๋ค ์ธ์๋ ๋ฐ์ ์ ์๊ณ ์ด๋ ํ ๊ฒฐ๊ณผ๋ ๋ฐํํ ์ ์์ต๋๋ค.
๋ฐํ๊ฐ
cache๋ ๊ฐ์ ํ์
์๊ทธ๋์ฒ๋ฅผ ๊ฐ์ง fn์ ์บ์ฑ๋ ๋ฒ์ ์ ๋ฐํํฉ๋๋ค. ์ด ๊ณผ์ ์์ fn์ ํธ์ถํ์ง ์์ต๋๋ค.
์ฃผ์ด์ง ์ธ์์ ํจ๊ป cachedFn์ ํธ์ถํ ๋, ์บ์์ ์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์๋์ง ๋จผ์ ํ์ธํฉ๋๋ค. ๋ง์ฝ ์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ๋ง์ฝ ์๋ค๋ฉด, ๋งค๊ฐ๋ณ์์ ํจ๊ป fn์ ํธ์ถํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์บ์์ ์ ์ฅํ๊ณ ๊ฐ์ ๋ฐํํฉ๋๋ค. fn์ด ์ ์ผํ๊ฒ ํธ์ถ๋๋ ๊ฒฝ์ฐ๋ ์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ์
๋๋ค.
์ฃผ์ ์ฌํญ
- React will invalidate the cache for all memoized functions for each server request.
- Each call to
cachecreates a new function. This means that callingcachewith the same function multiple times will return different memoized functions that do not share the same cache. cachedFnwill also cache errors. Iffnthrows an error for certain arguments, it will be cached, and the same error is re-thrown whencachedFnis called with those same arguments.cacheis for use in Server Components only.
์ฌ์ฉ๋ฒ
๊ณ ๋น์ฉ ์ฐ์ฐ ์บ์ฑํ๊ธฐ
๋ฐ๋ณต ์์
์ ํผํ๊ธฐ ์ํด cache๋ฅผ ์ฌ์ฉํ์ธ์.
import {cache} from 'react';
import calculateUserMetrics from 'lib/user';
const getUserMetrics = cache(calculateUserMetrics);
function Profile({user}) {
const metrics = getUserMetrics(user);
// ...
}
function TeamReport({users}) {
for (let user in users) {
const metrics = getUserMetrics(user);
// ...
}
// ...
}๊ฐ์ user ๊ฐ์ฒด๊ฐ Profile๊ณผ TeamReport์์ ๋ ๋๋ง๋ ๋, ๋ ์ปดํฌ๋ํธ๋ ์์
์ ๊ณต์ ํ๊ณ , user๋ฅผ ์ํ calculateUserMetrics๋ฅผ ํ ๋ฒ๋ง ํธ์ถํฉ๋๋ค.
If the same user object is rendered in both Profile and TeamReport, the two components can share work and only call calculateUserMetrics once for that user.
Assume Profile is rendered first. It will call getUserMetrics, and check if there is a cached result. Since it is the first time getUserMetrics is called with that user, there will be a cache miss. getUserMetrics will then call calculateUserMetrics with that user and write the result to cache.
When TeamReport renders its list of users and reaches the same user object, it will call getUserMetrics and read the result from cache.
If calculateUserMetrics can be aborted by passing an AbortSignal, you can use cacheSignal() to cancel the expensive computation if React has finished rendering. calculateUserMetrics may already handle cancellation internally by using cacheSignal directly.
๋ฐ์ดํฐ์ ์ค๋ ์ท ๊ณต์ ํ๊ธฐ
To share a snapshot of data between components, call cache with a data-fetching function like fetch. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.
import {cache} from 'react';
import {fetchTemperature} from './api.js';
const getTemperature = cache(async (city) => {
return await fetchTemperature(city);
});
async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}
async function MinimalWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}If AnimatedWeatherCard and MinimalWeatherCard both render for the same city, they will receive the same snapshot of data from the memoized function.
AnimatedWeatherCard์ MinimalWeatherCard๊ฐ ๋ค๋ฅธ city๋ฅผ getTemperature์ ์ธ์๋ก ๋ฐ๊ฒ ๋๋ค๋ฉด, fetchTemperature๋ ๋ ๋ฒ ํธ์ถ๋๊ณ ํธ์ถ๋ง๋ค ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ฒ๋ฉ๋๋ค.
city๊ฐ ์บ์ ํคKey์ฒ๋ผ ๋์ํ๊ฒ ๋ฉ๋๋ค.
์ฌ์ ์ ๋ฐ์ดํฐ ๋ฐ์๋๊ธฐ
๊ธด ์คํ ์๊ฐ์ด ์์๋๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์บ์ฑํ๋ฉด, ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ธฐ ์ ์ ๋น๋๊ธฐ ์์ ์ ์์ํ ์ ์์ต๋๋ค.
const getUser = cache(async (id) => {
return await db.user.query(id);
});
async function Profile({id}) {
const user = await getUser(id);
return (
<section>
<img src={user.profilePic} />
<h2>{user.name}</h2>
</section>
);
}
function Page({id}) {
// โ
Good: ์ฌ์ฉ์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์์ํฉ๋๋ค.
getUser(id);
// ... ๋ช๋ช์ ๊ณ์ฐ ์์
๋ค
return (
<>
<Profile id={id} />
</>
);
}Page๋ฅผ ๋ ๋๋งํ ๋, ์ปดํฌ๋ํธ๋ getUser๋ฅผ ํธ์ถํ์ง๋ง, ๋ฐํ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ ์ ์ ์ ์ํ์ธ์. ์ด ์ด๊ธฐ getUser ํธ์ถ์ ํ์ด์ง๊ฐ ๋ค๋ฅธ ๊ณ์ฐ ์์
์ ์ํํ๊ณ ์์์ ๋ ๋๋งํ๋ ๋์ ๋ฐ์ํ๋, ๋น๋๊ธฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ฅผ ์์ํฉ๋๋ค.
Profile์ ๋ ๋๋งํ ๋, getUser๋ฅผ ๋ค์ ํธ์ถํฉ๋๋ค. ์ด๊ธฐ getUser ํธ์ถ์ด ์ด๋ฏธ ์ฌ์ฉ์ ๋ฐ์ดํฐ์ ๋ฐํ๋๊ณ ์บ์ฑ๋์๋ค๋ฉด, Profile์ด ํด๋น ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ณ ๊ธฐ๋ค๋ฆด ๋, ๋ค๋ฅธ ์๊ฒฉ ํ๋ก์์ ํธ์ถ ์์ด ์ฝ๊ฒ ์บ์์์ ์ฝ์ด์ฌ ์ ์์ต๋๋ค. ์ด๊ธฐ ๋ฐ์ดํฐ ์์ฒญ์ด ์๋ฃ๋์ง ์์ ๊ฒฝ์ฐ, ์ด ํจํด์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ฌ ๋ ์๊ธฐ๋ ์ง์ฐ์ด ์ค์ด๋ญ๋๋ค.
์์ธํ ์ดํด๋ณด๊ธฐ
๋น๋๊ธฐ ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, Promise๋ฅผ ๋ฐ์ต๋๋ค. ์ด Promise๋ ์์ ์ ๋ํ ์ํ(๋๊ธฐ, ์๋ฃ, ์คํจ)์ ์ต์ข ์ ์ผ๋ก ํ์ ๋ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
In this example, the asynchronous function fetchData returns a promise that is awaiting the fetch.
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}getData๋ฅผ ์ฒ์ ํธ์ถํ ๋, fetchData์์ ๋ฐํ๋ Promise๊ฐ ์บ์ฑ๋ฉ๋๋ค. ์ดํ ์กฐํ ์, ๊ฐ์ Promise๋ฅผ ๋ฐํํฉ๋๋ค.
์ฒซ ๋ฒ์งธ getData ํธ์ถ์ ๊ธฐ๋ค๋ฆฌ์งawait ์์ง๋ง ๋ ๋ฒ์งธ๋ ๊ธฐ๋ค๋ฆฝ๋๋ค. await์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฐ์ฐ์๋ก, ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํ์ ๋ Promise์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ์ฒซ ๋ฒ์งธ getData๋ ๋จ์ํ ์กฐํํ ๋ ๋ฒ์งธ getData์ ๋ํ Promise๋ฅผ ์บ์ฑํ๊ธฐ ์ํด fetch๋ฅผ ์คํํฉ๋๋ค.
If by the second call the promise is still pending, then await will pause for the result. The optimization is that while we wait on the fetch, React can continue with computational work, thus reducing the wait time for the second call.
์๋ฃ๋ ๊ฒฐ๊ณผ๋ ์ค๋ฅ์ ๋ํ Promise๊ฐ ์ด๋ฏธ ์ ํด์ง ๊ฒฝ์ฐ, await๋ ์ฆ์ ๊ฐ์ ๋ฐํํฉ๋๋ค. ๋ ๊ฒฐ๊ณผ ๋ชจ๋ ์ฑ๋ฅ์์ ์ด์ ์ด ์์ต๋๋ค.
์์ธํ ์ดํด๋ณด๊ธฐ
์ธ๊ธ๋ ๋ชจ๋ API๋ค์ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ ๊ณตํ์ง๋ง, ๋ฉ๋ชจํ ๋์, ์บ์ ์ ๊ทผ ๊ถํ, ์บ์ ๋ฌดํจํ ์์ ์ ์ฐจ์ด๊ฐ ์์ต๋๋ค.
useMemo
In general, you should use useMemo for caching an expensive computation in a Client Component across renders. As an example, to memoize a transformation of data within a component.
'use client';
function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record), record);
// ...
}
function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}However, useMemo does ensure that if App re-renders and the record object doesnโt change, each component instance would skip work and use the memoized value of avgTemp. useMemo will only cache the last computation of avgTemp with the given dependencies.
cache
์ผ๋ฐ์ ์ผ๋ก cache๋ ์๋ฒ ์ปดํฌ๋ํธ์์ ์ปดํฌ๋ํธ ๊ฐ์ ๊ณต์ ํ ์ ์๋ ์์
์ ๋ฉ๋ชจํํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
const cachedFetchReport = cache(fetchReport);
function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}
function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}์ด์ ์์๋ฅผ cache๋ฅผ ์ด์ฉํด ์ฌ์์ฑํ๋ฉด, ์ด ๊ฒฝ์ฐ์ WeatherReport์ ๋ ๋ฒ์งธ ์ธ์คํด์ค๋ ์ค๋ณต ์์
์ ์๋ตํ๊ณ ์ฒซ ๋ฒ์งธ WeatherReport์ ๊ฐ์ ์บ์๋ฅผ ์ฝ๊ฒ ๋ฉ๋๋ค. ์ด์ ์์์ ๋ค๋ฅธ ์ ์ ๊ณ์ฐ์๋ง ์ฌ์ฉ๋๋ useMemo์ ๋ฌ๋ฆฌ cache๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ๋ฉ๋ชจํํ๋ ๋ฐ๋ ๊ถ์ฅ๋๋ค๋ ์ ์
๋๋ค.
์ด๋, cache๋ ์๋ฒ ์ปดํฌ๋ํธ์์๋ง ์ฌ์ฉํด์ผ ํ๋ฉฐ ์บ์๋ ์๋ฒ ์์ฒญ ์ ์ฒด์์ ๋ฌดํจํ๊ฐ ๋ฉ๋๋ค.
memo
memo๋ ํ๋กํผํฐ๊ฐ ๋ณ๊ฒฝ๋์ง ์์์ ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
'use client';
function WeatherReport({record}) {
const avgTemp = calculateAvg(record);
// ...
}
const MemoWeatherReport = memo(WeatherReport);
function App() {
const record = getRecord();
return (
<>
<MemoWeatherReport record={record} />
<MemoWeatherReport record={record} />
</>
);
}In this example, both MemoWeatherReport components will call calculateAvg when first rendered. However, if App re-renders, with no changes to record, none of the props have changed and MemoWeatherReport will not re-render.
useMemo์ ๋น๊ตํ๋ฉด memo๋ ํ๋กํผํฐ์ ํน์ ๊ณ์ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ปดํฌ๋ํธ ๋ ๋๋ง์ ๋ฉ๋ชจํํฉ๋๋ค. useMemo์ ์ ์ฌํ๊ฒ, ๋ฉ๋ชจํ๋ ์ปดํฌ๋ํธ๋ ๋ง์ง๋ง ํ๋กํผํฐ ๊ฐ์ ๋ํ ๋ง์ง๋ง ๋ ๋๋ง์ ์บ์ฑํฉ๋๋ค. ํ๋กํผํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด, ์บ์๋ ๋ฌดํจํ๋๊ณ ์ปดํฌ๋ํธ๋ ๋ค์ ๋ ๋๋ง๋ฉ๋๋ค.
๋ฌธ์ ํด๊ฒฐ
๋์ผํ ์ธ์๋ก ํจ์๋ฅผ ํธ์ถํด๋ ๋ฉ๋ชจ๋ ํจ์๊ฐ ๊ณ์ ์คํ๋ฉ๋๋ค
์์ ์ธ๊ธ๋ ์ฃผ์ ์ฌํญ๋ค์ ํ์ธํ์ธ์.
- ๋ค๋ฅธ ๋ฉ๋ชจํ๋ ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋ค๋ฅธ ์บ์์์ ์ฝ์ต๋๋ค.
- ์ปดํฌ๋ํธ ์ธ๋ถ์์ ๋ฉ๋ชจํ๋ ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์บ์๊ฐ ์ฌ์ฉ๋์ง ์์ต๋๋ค.
์์ ์ด๋ ๊ฒ๋ ํด๋นํ์ง ์๋๋ค๋ฉด, React๊ฐ ์บ์์ ๋ฌด์์ด ์กด์ฌํ๋์ง ํ์ธํ๋ ๋ฐฉ์์ ๋ฌธ์ ๊ฐ ์์ ์ ์์ต๋๋ค.
์ธ์๊ฐ ์์ ๊ฐ(๊ฐ์ฒด, ํจ์, ๋ฐฐ์ด ๋ฑ)์ด ์๋๋ผ๋ฉด, ๊ฐ์ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๋๊ฒผ๋์ง ํ์ธํ์ธ์.
๋ฉ๋ชจํ๋ ํจ์ ํธ์ถ ์, React๋ ์ ๋ ฅ๋ ์ธ์๊ฐ์ ์กฐํํด ๊ฒฐ๊ณผ๊ฐ ์ด๋ฏธ ์บ์ฑ๋์ด ์๋์ง ํ์ธํฉ๋๋ค. React๋ ์ธ์๋ค์ ์์ ๋๋ฑ์ฑ์ ์ฌ์ฉํด ์บ์ ํํธ๊ฐ ์๋์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// ๐ฉ Wrong: ์ธ์๊ฐ ๋งค ๋ ๋๋ง๋ง๋ค ๋ณ๊ฒฝ๋๋ ๊ฐ์ฒด์
๋๋ค.
const length = calculateNorm(props);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}์ด ๊ฒฝ์ฐ ๋ MapMarker๋ ๋์ผํ ์์
์ ์ํํ๊ณ ๋์ผํ ๊ฐ์ธ {x: 10, y: 10, z:10}์ ํจ๊ป calculateNorm๋ฅผ ํธ์ถํ๋ ๋ฏ ๋ณด์
๋๋ค. ๊ฐ์ฒด์ ๋์ผํ ๊ฐ์ด ํฌํจ๋์ด ์๋๋ผ๋ ๊ฐ ์ปดํฌ๋ํธ๊ฐ ์์ฒด ํ๋กํผํฐ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฏ๋ก, ๋์ผํ ๊ฐ์ฒด ์ฐธ์กฐ๊ฐ ์๋๋๋ค.
React๋ ์
๋ ฅ์์ Object.is๋ฅผ ํธ์ถํด ์บ์ ํํธ๊ฐ ์๋์ง ํ์ธํฉ๋๋ค.
import {cache} from 'react';
const calculateNorm = cache((x, y, z) => {
// ...
});
function MapMarker(props) {
// โ
Good: ๋ฉ๋ชจํ ํจ์์ ์ธ์๋ก ์์๊ฐ ์ ๊ณตํ๊ธฐ
const length = calculateNorm(props.x, props.y, props.z);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ํ ๊ฐ์ง ๋ฐฉ๋ฒ์ ๋ฒกํฐ ์ฐจ์์ calculateNorm์ ์ ๋ฌํ๋ ๊ฒ์
๋๋ค. ์ฐจ์ ์์ฒด๊ฐ ์์ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํฉ๋๋ค.
๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋ฒกํฐ ๊ฐ์ฒด๋ฅผ ์ปดํฌ๋ํธ์ ํ๋กํผํฐ๋ก ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ ์ปดํฌ๋ํธ ์ธ์คํด์ค์ ๋์ผํ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// โ
Good: ๋์ผํ `vector` ๊ฐ์ฒด๋ฅผ ๋๊ฒจ์ค๋๋ค.
const length = calculateNorm(props.vector);
// ...
}
function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}