cache
cache
memungkinkan anda untuk melakukan cache pada data hasil fetch atau komputasi.
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-parameter
fn
: Fungsi yang ingin anda cache hasilnya.fn
dapat meneriman argumen apapun dan mengembalikan nilai apapun.
Returns
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.
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, memanggicache
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. Jikafn
melempar sebuah eror untuk sebuah argumen tertentu, Itu akan ter-cache, dan eror yang sama akan dilempar kembali saatcachedFn
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.
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.
Data pramuat
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 getUser
tetapi 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
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.
Pendalaman
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 komponen server 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.
Troubleshooting
Fungsi ter-memo saya tidak berjalan walaupun saya sudah memanggilnya dengan argumen-argumen yang sama
Lihatlah jebakan yang disebutkan sebelumnya
- Memanggil fungsi ter-memo berbeda akan membaca dari caches yang berbeda.
- Memanggil sebuah fungsi yang ter-memo dari luar sebuah komponen tidak akan menggunakn cache-nya.
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 pada 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} />
</>
);
}