cache - This feature is available in the latest Canary

Canary

cache memungkinkan Anda untuk melakukan cache pada hasil pengambilan atau komputasi data.

const cachedFn = cache(fn);

Referensi

cache(fn)

Panggil cache di luar komponen apapun untuk membuat sebuah versi dari fungsi dengan caching.

import {cache} from 'react';
import calculateMetrics from 'lib/metrics';

const getMetrics = cache(calculateMetrics);

function Chart({data}) {
const report = getMetrics(data);
// ...
}

Saat getMetrics pertama kali dipanggil dengan data, getMetrics akan memanggil calculateMetrics(data) dan menyimpan hasilnya di dalam sebuah cache. Jika getMetrics dipanggil lagi dengan data yang sama, maka akan mengembalikan hasil yang telah ter-cache alih-alih memamggil calculateMetrics(data) lagi.

Lihat lebih banyak contoh dibawah.

Parameter

  • fn: Fungsi yang ingin Anda cache hasilnya. fn dapat meneriman argumen apapun dan mengembalikan nilai apapun.

Kembalian

cache mengembalikan versi ter-cache dari fn dengan tanda tangan tipe yang sama. Ia tidak memanggil fn dalam prosesnya.

Saat memanggil cachedFn dengan argumen yang diberikan, Pertama ia akan mengecek apakah terdapat nilai yang telah ter-cache sebelumnya. Jika nilai tersebut tersedia, maka kembalikan nilai tersebut. Jika tidak, panggil fn dengan argumen tersebut, simpan nilai tersebut di dalam cache, dan kembalikan nilai tersebut. Satu-satunya waktu fn dipanggil adalah ketika ada cache yang terlewat.

Catatan

Optimalisasi dari melakukan cache pada nilai kembalian berdasarkan masukkan dikenal sebagai memoization. Kita mengacu mengacu pada fungsi yang dikembalikan dari cache sebagai fungsi yang di-memo.

Peringatan

  • React akan menginvalidasi cache untuk setiap fungsi yang di-memo untuk setiap permintaan server.
  • Setiap pemanggil cache membentuk sebuah fungsi baru. Hal ini berarti, memanggil cache dengan fungsi yang sama berkali-kali akan mengembalikan fungsi ter-memo berbeda yang tidak berbagi cache yang sama
  • cachedFn juga akan men-cache eror-eror. Jika fn melempar sebuah eror untuk sebuah argumen tertentu, Itu akan ter-cache, dan eror yang sama akan dilempar kembali saat cachedFn dipanggil dengan argumen yang sama tersebut.
  • cache hanya dapat digunakan di Server Components.

Penggunaan

Melakukan cache pada computasi mahal

Gunakan cache untuk melwati pekerjaan yang berulang

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);
// ...
}
// ...
}

Jika objek user yang sama di-render di Profile dan TeamReport, kedua komponen dapat berbagi pekerjaan dan hanya memanggil calculateUserMetrics sekali untuk user tersebut.

Asumsikan Profile di-render pertama kali. Ia akan memanggil getUserMetrics, dan mengecek apakah terdapat nilai yang ter-cache sebelumnya. Mengingat ini adalah pertama kalinya getUserMetrics dipanggil oleh user tersebut, maka akan terdapat sebuah cache miss. getUserMetrics kemudian akan memanggil calculateUserMetrics dengan user tersebut dan menyimpan hasil tersebut dalam sebuah cache.

Saat TeamReport me-render daftar users-nya dan menjangkau objek user yang sama, Ia akan memanggil getUserMetrics dan membaca hasilnya dari cache.

Sandungan

Memanggil fungsi ter-memo berbeda akan membaca dari caches yang berbeda.

Untuk mengakses cache yang sama, komponen-komponen harus memanggil fungsi ter-memo yang sama.

// Temperature.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

export function Temperature({cityData}) {
// 🚩 Salah: Memanggil `cache` dalam komponent membentuk `getWeekReport` yang baru untuk setiap render
const getWeekReport = cache(calculateWeekReport);
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

// 🚩 Salah: `getWeekReport` hanya dapat diakses oleh komponen `Precipitation`.
const getWeekReport = cache(calculateWeekReport);

export function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}

Pada contoh diatas, Precipitation dan Temperature masing-masing memanggil cache untuk membuat sebuah fungsi ter-memo baru dengan cache look-up-nya sendiri. Jika kedua komponen di-render untuk cityData yang sama, maka mereka akan menduplikat pekerjaannya untuk memanggil calculateWeekReport.

Sebagai tambahan, Temperature membentuk sebuah fungsi ter-memo baru setiap kali komponen ter-render yang tidak memperbolehkannya untuk berbagi cache.

Untuk memaksimalkan cache dan mengurangi pekerjaan, kedua komponen tersebut harus memanggil fungsi ter-memo yang sama untuk mengakses cache yang sama. kedua komponen harus memanggil fungsi memo yang sama untuk mengakses cache yang sama. Sebagai gantinya, tentukan fungsi ter-memo dalam modul khusus yang dapat impor-ed di seluruh komponen.

// 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);
// ...
}

Disini, kedua komponen memanggil fungsi ter-memo sama yang diekspor dari ./getWeekReport.js untuk membaca dan menulis pada cache yang sama.

Membagikan cuplikan data

Untuk membagikan cuplikan data antar komponen, panggil cache dengan fungsi data-fetching seperti fetch. Saat beberapa komponen melakukan pengambilan data yang sama, hanya satu proses request yang akan dilakukan dan data yang dikembalikan adalah data ter-cache dan dibagikan ke seluruh komponen. Semua komponen ini Semua komponen mengacu pada cuplikan data yang sama di seluruh render server.

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);
// ...
}

Jika AnimatedWeatherCard dan MinimalWeatherCard keduanya merender city yang sama, mereka akan menerima cuplikan data yang sama dari fungsi yang ter-memo.

Jika AnimatedWeatherCard dan MinimalWeatherCard menggunakan argument city yang berbeda pada getTemperature, maka fetchTemperature akan dipanggil dua kali dan setiap pemanggilan akan menerima data yang berbeda.

city berperan sebagai sebuah kunci cache.

Catatan

pe-render-an Asinkron hanya mendukung komponent server.

async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}

Pramuat data

Dengan melakukan cache pada data hasil fetch yang panjang, Anda dapat memulai proses asinkron sebelum me-render komponen.

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}) {
// ✅ Baik: mulai mengambil data user
getUser(id);
// ... beberapa proses komputasi
return (
<>
<Profile id={id} />
</>
);
}

Saat me-render Page, komponen tersebut memanggil getUsertetapi perhatikan bahwa ia tidak menggunakan data yang dikembalikan. Pemanggilan getUser awal ini memulai query asinkron database basis data yang terjadi saat Page sedang melakukan komputasi lainnya dan me-render children.

Saat me-render Profile, kita dapat memanggil getUser lagi. Jika pemanggilan awal dari getUser telah mengembalikan sebuah nilai dan melakukan cache pada data user, saat Profile meminta dan menunggu data tersebut, ia dapat hanya membaca langsung dari cache tanpa memerlukan pemanggilan prosedur remote lainnya. Jika proses awal pemanggilan data belum dapat diselesaikan, pemuatan awal data dalam pola ini dapat mengurangi penundaan pemanggilan data.

Pendalaman

Melakukan cache pada proses asinkron

Saat mengevaluasi sebuah fungsi asinkron, ada akan menerima sebuah Promise untuk proses tersebut. Promise memegang status dari proses tersebut (pending, fulfilled, failed) dan hasil akhirnya yang telah diselesaikan.

Dalam contoh ini, fungsi asinkron fetchData mengembalikan sebuah promise yang menantikan proses fetch.

async function fetchData() {
return await fetch(`https://...`);
}

const getData = cache(fetchData);

async function MyComponent() {
getData();
// ... beberapa proses komputasi
await getData();
// ...
}

Saat memanggil getData untuk pertama kalinya, promise-nya mengembalikan nilai dari fetchData yang telah ter-cache. Pencarian selanjutnya akan menghasilkan promise yang sama

Perhatikan bahwa, pemanggilan getData pertama kali tidak melalui await sedangkan yang kedua melaluinya. await adalah sebuah operator JavaScript yang akan menunggu dan mengembalikan hasil akhir dari sebuah promise. Pemanggilan pertama getData hanya menginisiasi fetch untuk melakukan cache pada promise yang digunakan pada pemanggilan getData kedua untuk dicari.

Jika pada pemanggilan kedua promise tersebut masih berstatus pending, maka await akan dihentikan untuk mendapatkan hasilnya. Optimalisasinya adalah saat menunggu fetch, React masih dapat melanjutkan proses komputasi, sehingga mengurangi waktu yang diperlukan untuk melakukan pemanggilan kedua.

Jika promise telah diselesaikan, antara mendapatkan eror atau fulfilled sebagai hasilnya, await akan mengembalikan nilai tersebut secara langsung. Dalam kedua hasil tersebut, ada keuntungan kinerja.

Sandungan

Memanggil sebuah fungsi yang ter-memo dari luar sebuah komponen tidak akan menggunakn cache-nya.
import {cache} from 'react';

const getUser = cache(async (userId) => {
return await db.user.query(userId);
});

// 🚩 Salah: Memanggil fungsi ter-memo dari luar komponen tidak akan ter-memo.
getUser('demo-id');

async function DemoProfile() {
// ✅ Baik: `getUser` akan ter-memo.
const user = await getUser('demo-id');
return <Profile user={user} />;
}

React hanya menyediakan akses terhadap cache pada fungsi ter-memo dalam sebuah komponen. Saat memanggil getUser dari luar komponen, ia akan tetap mengevaluasi fungsi tersebut tetapi tidak melakukan proses membaca ataupun memperbarui cache.

Hal ini karena akses dari cache disediakan melalui context yang hanya dapat diakses oleh sebuah komponent.

Pendalaman

Kapan saya harus menggunakan cache, memo, atau useMemo?

Semua API yang disebutkan di atas menawarkan memo, tetapi perbedaannya adalah apa yang dimaksudkan untuk memo, siapa yang dapat mengakses cache, dan kapan cache mereka tidak valid.

useMemo

Secara umum, anda harus menggunakan useMemo untuk melakukan cache untuk sebuah komputasi yang mahal pada sebuah komponen klien di seluruh render. Sebagai contoh, untuk me-memo sebuah transformasi dari sebuah data di dalam komponen.

'use client';

function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record)), record);
// ...
}

function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}

Pada contoh ini, App me-render dua WeatherReport dengan catatan yang sama. Meskipun kedua komponen melakukan pekerjaan yang sama, mereka tidak dapat berbagi pekerjaan. Cache useMemo hanya bersifat lokal pada komponen tersebut.

Akan tetapi, useMemo memastikan bahwa jika App ter-render ulang dan objek record tidak berubah, setiap instance komponen akan melewatkan pekerjaan dan menggunakan nilai ter-memo dari avgTemp. useMemo akan hanya melakukan cache komputasi terakhir dari avgTemp dengan dependencies yang diberikan.

cache

Secara umum, Anda seharusnya menggunakan cache dalam Server Component untuk me-memo pekerjaan yang dapat dibagikan ke seluruh komponen.

const cachedFetchReport = cache(fetchReport);

function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}

function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}

Menulis ulang contoh sebelumnya dengan menggunakan cache, dalam kasus ini contoh kedua dari WeatherReport akan dapat melewati pekerjaan yang berulan dan membaca dari cache yang sama dengan WeatherReport pertama. Perbedaan lainnya dari contoh sebelumnya adalah cache juga direkomendasikan untuk me-memo pengambilan data, tidak seperti useMemo yang seharusnya hanya digunakan untuk komputasi.

Sementara waktu, cache hanya digunakan pada komponen server dan cache-nya akan hanya diinvalidasi di seluruh permintaan server.

memo

Anda seharusnya menggunakan memo untuk mencegah proses pe-render-an ulang dari sebuah komponen jika props-nya tidak berubah.

'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} />
</>
);
}

Dalam contoh ini, kedua komponen MemoWeatherReport akan memanggil calculateAvg saat di-render pertama kalinya. Akan tetapi, jika App di-render ulang, tanpa andanya perubaha pada record, tidak ada props yang berubah dan MemoWeatherReport tidak akan di-render ulang.

Apabila dibandingkan dengan useMemo, memo me-memo komponen tersebut di-render berdasarkan props vs. komputasi spesifik. Mirip dengan useMemo, komponen yang di-memo hanya akan men-cache render terakhir dengan menggunakan nilai props terakhir. Setelah props berubah, cache tersebut terinvalidasi dan komponennya di render ulang.


Pemecahan masalah

Fungsi ter-memo saya tidak berjalan walaupun saya sudah memanggilnya dengan argumen-argumen yang sama

Lihatlah jebakan yang disebutkan sebelumnya

Jika yang disebutkan di atas tidak berlaku, mungkin ada masalah dengan cara React memeriksa apakah ada sesuatu yang ada di dalam cache.

Jika argumen-argumen Anda bukan primitives (contoh: objek, fungsi, array), pastikan Anda memberikan referensi objek yang sama.

Saat memanggil fungsi yang ter-memo, React akan mencari argumen masukkan untuk melihat apakah sebuah nilai telah di-cache. React akan menggunakan shallow equality pada argumen-argumen untuk menentukan apakah ada cache hit.

import {cache} from 'react';

const calculateNorm = cache((vector) => {
// ...
});

function MapMarker(props) {
// 🚩 Salah: props adalah sebuah objek yang berubah disetiap render.
const length = calculateNorm(props);
// ...
}

function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}

Dalam kasus ini dua MapMarker terlihat seperti melakuakn pekerjaan yang sama dan memanggil calculateNorm dengan nilai {x: 10, y: 10, z:10}. Walaupun menggunakan objek dengan nilai yang sama, keduanya bukan objek dengan referensi yang sama karena kedua komponen membentuk objek props-nya masing-masing.

React akan memanggil Object.is di masukkan untuk verifikasi jika terdapat cache hit.

import {cache} from 'react';

const calculateNorm = cache((x, y, z) => {
// ...
});

function MapMarker(props) {
// ✅ Baik: Mengoper primitives untuk fungsi ter-memo
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} />
</>
);
}

Salah satu cara untuk mengatasi hal ini adalah dengan mengoper dimensi vektor ke calculateNorm. Hal ini bekerja karena dimensi itu sendiri adalah primitives.

Solusi lain adalah mengoper objek vektor itu sendiri sebagai penopang komponen. Kita harus mengoper objek yang sama ke kedua instance komponen.

import {cache} from 'react';

const calculateNorm = cache((vector) => {
// ...
});

function MapMarker(props) {
// ✅ Baik: Megoper objek `vector` yang sama
const length = calculateNorm(props.vector);
// ...
}

function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}