cache
кеш
використовується лише з Компонентами сервера React. Дивіться фреймворки, які підтримують Компоненти сервера React.cache
доступний лише в каналах React Canary та experimental. Будь ласка, переконайтеся, що ви розумієте обмеження, перш ніж використовуватиcache
у виробництві. Дізнайтеся більше про канали випуску React тут.
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
вперше викликається з даними
, getMetrics
викличе calculateMetrics(data)
і збереже результат у кеші. Якщо getMetrics
буде викликано повторно з тими самими даними
, він поверне кешований результат замість повторного виклику calculateMetrics(data)
.
Дивіться більше прикладів нижче.
Параметри
fn
: Функція, для якої потрібно кешувати результати.fn
може приймати будь-які аргументи і повертати будь-яке значення.
Повернення
cache
повертає кешовану версію fn
з тією ж сигнатурою типу. Вона не викликає fn
у процесі виконання.
При виклику cachedFn
з заданими аргументами, він спочатку перевіряє, чи існує кешований результат в кеші. Якщо кешований результат існує, повертається результат. Якщо ні, вона викликає fn
з аргументами, зберігає результат у кеші і повертає результат. Єдиний раз, коли викликається fn
, це коли є помилка кешу.
Оптимізація кешування значень, що повертаються, на основі вхідних даних відома як мемоїзація. Ми називаємо функцію, повернуту з кешу
, запам'ятовуваною функцією.
Застереження
- React буде анулювати кеш для всіх запам'ятовуваних функцій для кожного запиту сервера.
- Кожен виклик
cache
створює нову функцію. Це означає, що викликcache
з однією і тією ж функцією декілька разів поверне різні запам'ятовувані функції, які не мають спільного кешу. cachedFn
також кешує помилки. Якщоfn
видасть помилку для певних аргументів, її буде кешовано, і ця ж помилка буде повторно видана при викликуcachedFn
з тими ж аргументами.кеш
призначено лише для використання у Компонентах сервера React.
Використання
Кешування складних обчислень
Використовуйте кеш
для уникнення дублювання роботи.
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
, ці два компоненти можуть розділити роботу і викликати calculateUserMetrics
лише один раз для цього user
.
Припустимо, що Profile
буде відрендерено першим. Він викличе getUserMetrics
викликається вперше з цим користувачем
, буде пропуск кешу. getUserMetrics
викличе calculateUserMetrics
з цим користувачем
і запише результат до кешу.
Коли TeamReport
рендерить свій список користувачів
і досягає того самого об'єкта user
, він викличе
Виклик різних запам'ятованих функцій зчитуватиме дані з різних кешів.
Щоб отримати доступ до того самого кешу, компоненти повинні викликати ту саму запам'ятовувану функцію.
// Temperature.js
import {cache} from 'react';
import {calculateWeekReport} from './report';
export function Temperature({cityData}) {
// 🚩 Wrong: Calling `cache` in component creates new `getWeekReport` for each render
const getWeekReport = cache(calculateWeekReport);
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import {cache} from 'react';
import {calculateWeekReport} from './report';
// 🚩 Wrong: `getWeekReport` is only accessible for `Precipitation` component.
const getWeekReport = cache(calculateWeekReport);
export function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}
У наведеному вище прикладі cache
для створення нової запам'ятовуваної функції з власним пошуком у кеші. Якщо обидва компоненти рендеритимуть один і той самий cityData
, вони виконуватимуть дублюючу роботу для виклику calculateWeekReport
.
Крім того, Temperature
створює
// getWeekReport.js
import {cache} from 'react';
import {calculateWeekReport} from './report';
export default cache(calculateWeekReport);
// Temperature.js
import getWeekReport from './getWeekReport';
export default function Temperature({cityData}) {
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import getWeekReport from './getWeekReport';
export default function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}
Тут обидва компоненти викликають одну й ту саму запам'ятовану функцію
Надайте знімок даних
Щоб поділитися знімком даних між компонентами, викличте cache
з функцією отримання даних на зразок fetch
. Коли декілька компонентів виконують один і той самий запит, робиться лише один запит, а повернуті дані кешуються і використовуються всіма компонентами. Усі компоненти посилаються на один і той самий знімок даних під час рендерингу на сервері.
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);
// ...
}
Якщо AnimatedWeatherCard
і MinimalWeatherCard
обидва рендерять для одного і того ж MinimalWeatherCard
надають різні fetchTemperature
буде викликано двічі, і кожен сайт виклику отримає різні дані.
Кешуючи довготривалу вибірку даних, ви можете запустити асинхронну роботу перед рендерингом компонента. Під час рендерингу Під час рендерингу При виклику Зверніть увагу, що перший Якщо при React надає лише кеш-доступ до запам'ятованої функції в компоненті. При виклику Це тому, що доступ до кешу надається через контекст, який доступний лише з компонента. Усі згадані API пропонують кешування, але різниця полягає у тому, що саме вони мають намір запам'ятовувати, хто може отримати доступ до кешу та коли їхній кеш стає недійсним. Взагалі, для кешування важких обчислень у клієнтському компоненті між рендерами слід використовувати У цьому прикладі Однак, Загалом, вам слід використовувати Переписування попереднього прикладу для використання Наразі Вам слід використовувати У цьому прикладі обидва компоненти У порівнянні з Дивіться раніше згадані пастки Якщо жоден з наведених вище варіантів не підходить, це може бути проблемою з тим, як React перевіряє, чи існує щось у кеші. Якщо ваші аргументи не є примітивами (наприклад, об'єкти, функції, масиви), переконайтеся, що ви передаєте те саме посилання на об'єкт. При виклику функції, що запам'ятовується, React перегляне вхідні аргументи, щоб побачити, чи результат вже закешований. React буде використовувати неглибоку рівність аргументів, щоб визначити, чи є кешування. У цьому випадку два React викличе Одним із способів вирішення цієї проблеми може бути передача розмірів вектора до Іншим рішенням може бути передача самого векторного об'єкта як пропу компоненту. Нам потрібно буде передати той самий об'єкт обом екземплярам компонента.Перезавантажити дані
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: start fetching the user data
getUser(id);
// ... some computational work
return (
<>
<Profile id={id} />
</>
);
}
Page
компонент викликає Page
іншої обчислювальної роботи та рендерингу дочірніх елементів.Profile
ми викликаємо Profile
fetch
.async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}
чекає
тоді як fetch
для кешування обіцянки для другого fetch
, React може продовжувати обчислювальну роботу, таким чином зменшуючи час очікування на Виклик запам'ятованої функції поза компонентом не використовуватиме кеш.
import {cache} from 'react';
const getUser = cache(async (userId) => {
return await db.user.query(userId);
});
// 🚩 Wrong: Calling memoized function outside of component will not memoize.
getUser('demo-id');
async function DemoProfile() {
// ✅ Good: `getUser` will memoize.
const user = await getUser('demo-id');
return <Profile user={user} />;
}
Коли слід використовувати
cache
, memo
або useMemo
?useMemo
useMemo
. Наприклад, для запам'ятовування перетворення даних всередині компонента.'use client';
function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record)), record);
// ...
}
function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}
App
рендерить два WeatherReport
з однаковим записом. Хоча обидва компоненти виконують однакову роботу, вони не можуть ділитися нею. Кеш useMemo
є локальним лише для компонента.useMemo
гарантує, що якщо App
буде рендеритися повторно і об'єкт record
не зміниться, кожен екземпляр компонента пропустить роботу і використає запам'ятоване значення avgTemp
. useMemo
кешуватиме лише останнє обчислення avgTemp
із заданими залежностями.cache
кеш
у серверних компонентах для запам'ятовування роботи, яку можна спільно використовувати між компонентами."], [3, 13, "<WeatherReport city={city} />"], [2, 1, "cache(fetchReport)"]]" class="language-js">const cachedFetchReport = cache(fetchReport);
function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}
function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}
кешу
, у цьому випадку кеш
також рекомендується для кеш
слід використовувати лише у серверних компонентах, і кеш буде недійсним для серверних запитів.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} />
</>
);
}
MemoWeatherReport
викличуть calculateAvg
при першому рендері. Однак, якщо App
повторно відрендерить без змін запис
, жоден з пропсів не зміниться і MemoWeatherReport
не буде повторно відрендерений.useMemo
, memo
запам'ятовує рендеринг компонента на основі пропсів, а не конкретних обчислень. Подібно до useMemo
, запам'ятований компонент кешує лише останній рендеринг з останніми значеннями пропсів. Щойно пропси змінюються, кеш стає недійсним, і компонент рендериться заново.
Налагодження
Моя запам'ятована функція все ще працює, хоча я викликав її з тими самими аргументами
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// 🚩 Wrong: props is an object that changes every render.
const length = calculateNorm(props);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}
MapMarker
виглядають так, ніби вони виконують однакову роботу і викликають calculateNorm
з однаковим значенням {x: 10, y: 10, z:10}
. Незважаючи на те, що об'єкти містять однакові значення, вони не є одним і тим самим посиланням на об'єкт, оскільки кожен компонент створює власний об'єкт props
.Object.is
на вході, щоб перевірити, чи є попадання в кеш.import {cache} from 'react';
const calculateNorm = cache((x, y, z) => {
// ...
});
function MapMarker(props) {
// ✅ Good: Pass primitives to memoized function
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: Pass the same `vector` object
const length = calculateNorm(props.vector);
// ...
}
function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}