Skip to content

Commit 446939a

Browse files
authored
feat(cache): support customKey, cache statistics and unstable_shouldDisable for the cache function (#7058)
1 parent b60d21c commit 446939a

File tree

7 files changed

+769
-32
lines changed

7 files changed

+769
-32
lines changed

.changeset/little-mirrors-design.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/runtime-utils': patch
3+
---
4+
5+
feat(cache): support customKey for cache
6+
feat(cache): 为缓存支持 customKey 函数

.changeset/swift-cows-brush.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/runtime-utils': patch
3+
---
4+
5+
feat: support unstable_shouldDisable
6+
feat: 支持 unstable_shouldDisable

.changeset/yellow-dolls-fail.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/runtime-utils': patch
3+
---
4+
5+
feat: support cache statistics
6+
feat: 支持缓存命中率统计

packages/document/main-doc/docs/en/guides/basic-features/data/data-cache.mdx

+127-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sidebar_position: 4
44
---
55
# Data Caching
66

7-
The `cache` function allows you to cache the results of data fetching or computation.
7+
The `cache` function allows you to cache the results of data fetching or computation, Compared to full-page [rendering cache](/guides/basic-features/render/ssr-cache), it provides more fine-grained control over data granularity and is applicable to various scenarios such as Client-Side Rendering (CSR), Server-Side Rendering (SSR), and API services (BFF).
88

99
:::info
1010
X.65.5 and above versions are required
@@ -34,6 +34,7 @@ const loader = async () => {
3434
- `tag`: Tag to identify the cache, which can be used to invalidate the cache
3535
- `maxAge`: Cache validity period (milliseconds)
3636
- `revalidate`: Time window for revalidating the cache (milliseconds), similar to HTTP Cache-Control's stale-while-revalidate functionality
37+
- `customKey`: Custom cache key function
3738

3839
The type of the `options` parameter is as follows:
3940

@@ -42,6 +43,11 @@ interface CacheOptions {
4243
tag?: string | string[];
4344
maxAge?: number;
4445
revalidate?: number;
46+
customKey?: <Args extends any[]>(options: {
47+
params: Args;
48+
fn: (...args: Args) => any;
49+
generatedKey: string;
50+
}) => string | symbol;
4551
}
4652
```
4753

@@ -153,6 +159,126 @@ revalidateTag('dashboard-stats'); // Invalidates the cache for both getDashboard
153159
```
154160

155161

162+
#### `customKey` parameter
163+
164+
The `customKey` parameter is used to customize the cache key. It is a function that receives an object with the following properties and returns a string or Symbol type as the cache key:
165+
166+
- `params`: Array of arguments passed to the cached function
167+
- `fn`: Reference to the original function being cached
168+
- `generatedKey`: Cache key automatically generated by the framework based on input parameters
169+
170+
This is very useful in some scenarios, such as when the function reference changes , but you want to still return the cached data.
171+
172+
```ts
173+
import { cache } from '@modern-js/runtime/cache';
174+
import { fetchUserData } from './api';
175+
176+
// Different function references, but share the same cache via customKey
177+
const getUserA = cache(
178+
fetchUserData,
179+
{
180+
maxAge: CacheTime.MINUTE * 5,
181+
customKey: ({ params }) => {
182+
// Return a stable string as the cache key
183+
return `user-${params[0]}`;
184+
},
185+
}
186+
);
187+
188+
// Even if the function reference changes,
189+
// as long as customKey returns the same value, the cache will be hit
190+
const getUserB = cache(
191+
(...args) => fetchUserData(...args), // New function reference
192+
{
193+
maxAge: CacheTime.MINUTE * 5,
194+
customKey: ({ params }) => {
195+
// Return the same key as getUserA
196+
return `user-${params[0]}`;
197+
},
198+
}
199+
);
200+
201+
// You can also use Symbol as a cache key (usually used to share cache within the same application)
202+
const USER_CACHE_KEY = Symbol('user-cache');
203+
const getUserC = cache(
204+
fetchUserData,
205+
{
206+
maxAge: CacheTime.MINUTE * 5,
207+
customKey: () => USER_CACHE_KEY,
208+
}
209+
);
210+
211+
// You can utilize the generatedKey parameter to modify the default key
212+
const getUserD = cache(
213+
fetchUserData,
214+
{
215+
customKey: ({ generatedKey }) => `prefix-${generatedKey}`,
216+
}
217+
);
218+
```
219+
220+
#### `onCache` Parameter
221+
222+
The `onCache` parameter allows you to track cache statistics such as hit rate. It's a callback function that receives information about each cache operation, including the status, key, parameters, and result.
223+
224+
```ts
225+
import { cache, CacheTime } from '@modern-js/runtime/cache';
226+
227+
// Track cache statistics
228+
const stats = {
229+
total: 0,
230+
hits: 0,
231+
misses: 0,
232+
stales: 0,
233+
hitRate: () => stats.hits / stats.total
234+
};
235+
236+
const getUser = cache(
237+
fetchUserData,
238+
{
239+
maxAge: CacheTime.MINUTE * 5,
240+
onCache({ status, key, params, result }) {
241+
// status can be 'hit', 'miss', or 'stale'
242+
stats.total++;
243+
244+
if (status === 'hit') {
245+
stats.hits++;
246+
} else if (status === 'miss') {
247+
stats.misses++;
248+
} else if (status === 'stale') {
249+
stats.stales++;
250+
}
251+
252+
console.log(`Cache ${status} for key: ${String(key)}`);
253+
console.log(`Current hit rate: ${stats.hitRate() * 100}%`);
254+
}
255+
}
256+
);
257+
258+
// Usage example
259+
await getUser(1); // Cache miss
260+
await getUser(1); // Cache hit
261+
await getUser(2); // Cache miss
262+
```
263+
264+
The `onCache` callback receives an object with the following properties:
265+
266+
- `status`: The cache operation status, which can be:
267+
- `hit`: Cache hit, returning cached content
268+
- `miss`: Cache miss, executing the function and caching the result
269+
- `stale`: Cache hit but data is stale, returning cached content while revalidating in the background
270+
- `key`: The cache key, which is either the result of `customKey` or the default generated key
271+
- `params`: The parameters passed to the cached function
272+
- `result`: The result data (either from cache or newly computed)
273+
274+
This callback is only invoked when the `options` parameter is provided. When using the cache function without options (for SSR request-scoped caching), the `onCache` callback is not called.
275+
276+
The `onCache` callback is useful for:
277+
- Monitoring cache performance
278+
- Calculating hit rates
279+
- Logging cache operations
280+
- Implementing custom metrics
281+
156282
### Storage
157283

158284
Currently, both client and server caches are stored in memory.

packages/document/main-doc/docs/zh/guides/basic-features/data/data-cache.mdx

+131-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sidebar_position: 4
44
---
55
# 数据缓存
66

7-
`cache` 函数可以让你缓存数据获取或计算的结果。
7+
`cache` 函数可以让你缓存数据获取或计算的结果,相比整页[渲染缓存](/guides/basic-features/render/ssr-cache),它提供了更精细的数据粒度控制,并且适用于客户端渲染(CSR)、服务端渲染(SSR)、API 服务(BFF)等多种场景
88

99
:::info
1010
需要 x.65.5 及以上版本
@@ -33,6 +33,7 @@ const loader = async () => {
3333
- `tag`: 用于标识缓存的标签,可以基于这个标签使缓存失效
3434
- `maxAge`: 缓存的有效期 (毫秒)
3535
- `revalidate`: 重新验证缓存的时间窗口(毫秒),与 HTTP Cache-Control 的 stale-while-revalidate 功能一致
36+
- `customKey`: 自定义缓存键生成函数,用于在函数引用变化时保持缓存
3637

3738
`options` 参数的类型如下:
3839

@@ -41,6 +42,11 @@ interface CacheOptions {
4142
tag?: string | string[];
4243
maxAge?: number;
4344
revalidate?: number;
45+
customKey?: <Args extends any[]>(options: {
46+
params: Args;
47+
fn: (...args: Args) => any;
48+
generatedKey: string;
49+
}) => string | symbol;
4450
}
4551
```
4652

@@ -146,6 +152,129 @@ const getComplexStatistics = cache(
146152
revalidateTag('dashboard-stats'); // 会使 getDashboardStats 函数和 getComplexStatistics 函数的缓存都失效
147153
```
148154

155+
#### `customKey` 参数
156+
157+
`customKey` 参数用于定制缓存的键,它是一个函数,接收一个包含以下属性的对象,返回值必须是字符串或 Symbol 类型,将作为缓存的键:
158+
159+
- `params`:调用缓存函数时传入的参数数组
160+
- `fn`:原始被缓存的函数引用
161+
- `generatedKey`:框架基于入参自动生成的原始缓存键
162+
163+
这在某些场景下非常有用,比如当函数引用发生变化时,但你希望仍然返回缓存的数据。
164+
165+
```ts
166+
import { cache } from '@modern-js/runtime/cache';
167+
import { fetchUserData } from './api';
168+
169+
// 不同的函数引用,但是通过 customKey 可以使它们共享一个缓存
170+
const getUserA = cache(
171+
fetchUserData,
172+
{
173+
maxAge: CacheTime.MINUTE * 5,
174+
customKey: ({ params }) => {
175+
// 返回一个稳定的字符串作为缓存的键
176+
return `user-${params[0]}`;
177+
},
178+
}
179+
);
180+
181+
// 即使函数引用变了,只要 customKey 返回相同的值,也会命中缓存
182+
const getUserB = cache(
183+
(...args) => fetchUserData(...args), // 新的函数引用
184+
{
185+
maxAge: CacheTime.MINUTE * 5,
186+
customKey: ({ params }) => {
187+
// 返回与 getUserA 相同的键
188+
return `user-${params[0]}`;
189+
},
190+
}
191+
);
192+
193+
// 即使 getUserA 和 getUserB 是不同的函数引用,但由于它们的 customKey 返回相同的值
194+
// 所以当调用参数相同时,它们会共享缓存
195+
const dataA = await getUserA(1);
196+
const dataB = await getUserB(1); // 这里会命中缓存,不会再次发起请求
197+
198+
// 也可以使用 Symbol 作为缓存键(通常用于共享同一个应用内的缓存)
199+
const USER_CACHE_KEY = Symbol('user-cache');
200+
const getUserC = cache(
201+
fetchUserData,
202+
{
203+
maxAge: CacheTime.MINUTE * 5,
204+
customKey: () => USER_CACHE_KEY,
205+
}
206+
);
207+
208+
// 可以利用 generatedKey 参数在默认键的基础上进行修改
209+
const getUserD = cache(
210+
fetchUserData,
211+
{
212+
customKey: ({ generatedKey }) => `prefix-${generatedKey}`,
213+
}
214+
);
215+
```
216+
217+
#### `onCache` 参数
218+
219+
`onCache` 参数允许你跟踪缓存统计信息,例如命中率。这是一个回调函数,接收有关每次缓存操作的信息,包括状态、键、参数和结果。
220+
221+
```ts
222+
import { cache, CacheTime } from '@modern-js/runtime/cache';
223+
224+
// 跟踪缓存统计
225+
const stats = {
226+
total: 0,
227+
hits: 0,
228+
misses: 0,
229+
stales: 0,
230+
hitRate: () => stats.hits / stats.total
231+
};
232+
233+
const getUser = cache(
234+
fetchUserData,
235+
{
236+
maxAge: CacheTime.MINUTE * 5,
237+
onCache({ status, key, params, result }) {
238+
// status 可以是 'hit'、'miss' 或 'stale'
239+
stats.total++;
240+
241+
if (status === 'hit') {
242+
stats.hits++;
243+
} else if (status === 'miss') {
244+
stats.misses++;
245+
} else if (status === 'stale') {
246+
stats.stales++;
247+
}
248+
249+
console.log(`缓存${status === 'hit' ? '命中' : status === 'miss' ? '未命中' : '陈旧'},键:${String(key)}`);
250+
console.log(`当前命中率:${stats.hitRate() * 100}%`);
251+
}
252+
}
253+
);
254+
255+
// 使用示例
256+
await getUser(1); // 缓存未命中
257+
await getUser(1); // 缓存命中
258+
await getUser(2); // 缓存未命中
259+
```
260+
261+
`onCache` 回调接收一个包含以下属性的对象:
262+
263+
- `status`: 缓存操作状态,可以是:
264+
- `hit`: 缓存命中,返回缓存内容
265+
- `miss`: 缓存未命中,执行函数并缓存结果
266+
- `stale`: 缓存命中但数据陈旧,返回缓存内容同时在后台重新验证
267+
- `key`: 缓存键,可能是 `customKey` 的结果或默认生成的键
268+
- `params`: 传递给缓存函数的参数
269+
- `result`: 结果数据(来自缓存或新计算的)
270+
271+
这个回调只在提供 `options` 参数时被调用。当使用不带选项的缓存函数(用于 SSR 请求范围内的缓存)时,不会调用 `onCache` 回调。
272+
273+
`onCache` 回调对以下场景非常有用:
274+
- 监控缓存性能
275+
- 计算命中率
276+
- 记录缓存操作
277+
- 实现自定义指标
149278

150279
### 存储
151280

@@ -164,3 +293,4 @@ configureCache({
164293
maxSize: CacheSize.MB * 10, // 10MB
165294
});
166295
```
296+

0 commit comments

Comments
 (0)