Repository: alibaba/hooks
Pull request #1348 (2021-12-05 17:03:27)

feat: 增加清除缓存功能
<!--
First of all, thank you for your contribution! 😄

New feature please send a pull request to next branch, and rest to master branch.
Pull requests will be merged after one of the collaborators approve.
Please makes sure that these forms are filled before submitting your pull request, thank you!
-->

[[中文版模板 / Chinese template](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]

### 🤔 这个变动的性质是？

- [x] 新特性提交
- [ ] 日常 bug 修复
- [ ] 站点、文档改进
- [ ] 演示代码改进
- [ ] TypeScript 定义更新
- [ ] 包体积优化
- [ ] 性能优化
- [ ] 功能增强
- [ ] 国际化改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他改动（是关于什么的改动？）

### 🔗 相关 Issue
[#884](https://github.com/alibaba/hooks/issues/884): useRequest clearCache method to force cache clear
[#1344](https://github.com/alibaba/hooks/issues/1344): useRequest 是否有 使缓存失效的功能

<!--
1. 描述相关需求的来源，如相关的 issue 讨论链接。
-->

### 💡 需求背景和解决方案

<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->
背景：目前 `useRequest` 设置了 `cacheKey` 后，无主动清除缓存的方法
解决方案：提供 `clearCache` 方法进行单个、多个、全部缓存的清除

### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

| 语言    | 更新描述 |
| ------- | -------- |
| 🇺🇸 英文 |     use `clearCache`  to clear the cached data    |
| 🇨🇳 中文 |     新增方法 `clearCache` 来清除缓存  |

### ☑️ 请求合并前的自查清单

⚠️ 请自检并全部**勾选全部选项**。⚠️

- [x] 文档已补充或无须补充
- [x] 代码演示已提供或无须提供
- [x] TypeScript 定义已补充或无须补充
- [x] Changelog 已提供或无须提供


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/alibaba/hooks?pullRequest=1348) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/alibaba/hooks?pullRequest=1348) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/alibaba/hooks?pullRequest=1348) it.</sub>

Patch:
diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts
index 7e02461f..5f26ad8d 100644
--- a/packages/hooks/src/index.ts
+++ b/packages/hooks/src/index.ts
@@ -43,7 +43,7 @@ import usePagination from './usePagination';
 import usePrevious from './usePrevious';
 import useRafState from './useRafState';
 import useReactive from './useReactive';
-import useRequest from './useRequest';
+import useRequest, { clearCache } from './useRequest';
 import { configResponsive, useResponsive } from './useResponsive';
 import useSafeState from './useSafeState';
 import useScroll from './useScroll';
@@ -141,4 +141,5 @@ export {
   useFusionTable,
   useInfiniteScroll,
   useGetState,
+  clearCache,
 };
diff --git a/packages/hooks/src/useAntdTable/demo/cache.tsx b/packages/hooks/src/useAntdTable/demo/cache.tsx
index 94e41815..232f56c0 100644
--- a/packages/hooks/src/useAntdTable/demo/cache.tsx
+++ b/packages/hooks/src/useAntdTable/demo/cache.tsx
@@ -1,6 +1,6 @@
 import React, { useState } from 'react';
 import { Button, Col, Form, Input, Row, Table, Select } from 'antd';
-import { useAntdTable } from 'ahooks';
+import { useAntdTable, clearCache } from 'ahooks';
 
 const { Option } = Select;
 
@@ -159,6 +159,15 @@ const Demo = () => {
       >
         {show ? 'Click to destroy' : 'Click recovery'}
       </Button>
+      <Button
+        danger
+        onClick={() => {
+          clearCache('useAntdTableCache');
+        }}
+        style={{ marginBottom: 16, marginLeft: 8 }}
+      >
+        Click to clearCache
+      </Button>
       {show && <UserList />}
     </div>
   );
diff --git a/packages/hooks/src/useRequest/doc/cache/cache.en-US.md b/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
index 17b9ff24..c619b3e3 100644
--- a/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
+++ b/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
@@ -46,16 +46,33 @@ In the following example, we can initialize the `keyword` from the cached `param
 
 <code src="./demo/params.tsx" />
 
+### Clear cache
+
+ahooks provides a `clearCache` method, which can clear the cache data of the specified `cacheKey`.
+
+<code src="./demo/clearCache.tsx" />
+
 ## API
 
 ### Options
 
-| Property  | Description                                                                                                                                                                                                         | Type     | Default  |
-|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------|
-| cacheKey  |  A unique ID of the request. If `cacheKey` is set, we will enable the caching mechanism. The data of the same `cacheKey` is globally synchronized.                                                                      | `string` | `[]`     |
-| cacheTime | <ul><li> Set the cache time. By default, the cached data will be cleared after 5 minutes.</li><li> If set to `-1`, the cached data will never expire</li></ul>                                       | `number` | `300000` |
+| Property  | Description                                                                                                                                                                                   | Type     | Default  |
+|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------|
+| cacheKey  | A unique ID of the request. If `cacheKey` is set, we will enable the caching mechanism. The data of the same `cacheKey` is globally synchronized.                                             | `string` | `[]`     |
+| cacheTime | <ul><li> Set the cache time. By default, the cached data will be cleared after 5 minutes.</li><li> If set to `-1`, the cached data will never expire</li></ul>                                | `number` | `300000` |
 | staleTime | <ul><li> Time to consider the cached data is fresh. Within this time interval, the request will not be re-initiated</li><li> If set to `-1`, it means that the data is always fresh</li></ul> | `number` | `0`      |
 
+### clearCache
+
+```tsx | pure
+import { clearCache } from 'ahooks';
+
+clearCache(cacheKey?: string | string[]);
+```
+
+1. Support clearing a single cache, or a group of caches
+2. If `cacheKey` is empty, all cached data will be cleared
+
 ## Remark
 
 * Only successful request data will be cached
diff --git a/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md b/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
index f5d2bc66..474b0aae 100644
--- a/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
@@ -46,16 +46,34 @@ group:
 
 <code src="./demo/params.tsx" />
 
+### 删除缓存
+
+ahooks 提供了一个 `clearCache` 方法，可以清除指定 `cacheKey` 的缓存数据。
+
+<code src="./demo/clearCache.tsx" />
+
 ## API
 
 ### Options
 
 | 参数      | 说明                                                                                                                                    | 类型     | 默认值   |
 |-----------|-----------------------------------------------------------------------------------------------------------------------------------------|----------|----------|
-| cacheKey  | 请求唯一标识。如果设置了 `cacheKey`，我们会启用缓存机制。同一个 `cacheKey` 的数据全局同步。                                                 | `string` | `[]`     |
+| cacheKey  | 请求唯一标识。如果设置了 `cacheKey`，我们会启用缓存机制。同一个 `cacheKey` 的数据全局同步。                                             | `string` | `[]`     |
 | cacheTime | <ul><li> 设置缓存数据回收时间。默认缓存数据 5 分钟后回收 </li><li> 如果设置为 `-1`, 则表示缓存数据永不过期</li></ul>                    | `number` | `300000` |
 | staleTime | <ul><li> 缓存数据保持新鲜时间。在该时间间隔内，认为数据是新鲜的，不会重新发请求 </li><li> 如果设置为 `-1`，则表示数据永远新鲜</li></ul> | `number` | `0`      |
 
+### clearCache
+
+```tsx | pure
+import { clearCache } from 'ahooks';
+
+clearCache(cacheKey?: string | string[]);
+```
+
+1. 支持清空单个缓存，或一组缓存
+2. 如果 `cacheKey` 为空，则清空所有缓存数据 
+
+
 ## 备注
 
 * 只有成功的请求数据才会缓存
diff --git a/packages/hooks/src/useRequest/doc/cache/demo/clearCache.tsx b/packages/hooks/src/useRequest/doc/cache/demo/clearCache.tsx
new file mode 100644
index 00000000..d7f4f6df
--- /dev/null
+++ b/packages/hooks/src/useRequest/doc/cache/demo/clearCache.tsx
@@ -0,0 +1,68 @@
+import Mock from 'mockjs';
+import React from 'react';
+import { useRequest, clearCache, useBoolean } from 'ahooks';
+import { message } from 'antd';
+
+async function getArticle(): Promise<{ data: string; time: number }> {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        data: Mock.mock('@paragraph'),
+        time: new Date().getTime(),
+      });
+    }, 3000);
+  });
+}
+
+const Article = ({ cacheKey }) => {
+  const { data, loading, refresh } = useRequest(getArticle, {
+    cacheKey,
+  });
+  if (!data && loading) {
+    return <p>Loading</p>;
+  }
+  return (
+    <>
+      <p>Background loading: {loading ? 'true' : 'false'}</p>
+      <p>Latest request time: {data?.time}</p>
+      <p>{data?.data}</p>
+    </>
+  );
+};
+
+const clear = (cacheKey?: string | string[]) => {
+  clearCache(cacheKey);
+  const tips = Array.isArray(cacheKey) ? cacheKey.join('、') : cacheKey;
+  message.success(`Clear ${tips ?? 'All'} finished`);
+};
+
+export default () => {
+  const [state, { toggle }] = useBoolean();
+  return (
+    <div>
+      <p>
+        <button type="button" onClick={() => toggle()}>
+          show/hidden
+        </button>
+      </p>
+      <p>
+        <button style={{ marginRight: 8 }} onClick={() => clear('Article1')}>
+          Clear Article1
+        </button>
+        <button style={{ marginRight: 8 }} onClick={() => clear('Article2')}>
+          Clear Article2
+        </button>
+        <button style={{ marginRight: 8 }} onClick={() => clear(['Article2', 'Article3'])}>
+          Clear Article2 and Article3
+        </button>
+        <button onClick={() => clear()}>Clear All</button>
+      </p>
+      <h2>Article 1</h2>
+      {state && <Article cacheKey="Article1" />}
+      <h2>Article 2</h2>
+      {state && <Article cacheKey="Article2" />}
+      <h2>Article 3</h2>
+      {state && <Article cacheKey="Article3" />}
+    </div>
+  );
+};
diff --git a/packages/hooks/src/useRequest/doc/clearCache/clearCache.en-US.md b/packages/hooks/src/useRequest/doc/clearCache/clearCache.en-US.md
new file mode 100644
index 00000000..4cbcc00f
--- /dev/null
+++ b/packages/hooks/src/useRequest/doc/clearCache/clearCache.en-US.md
@@ -0,0 +1,41 @@
+---
+nav:
+  path: /hooks
+group:
+  path: /use-request
+---
+
+# ClearCache
+
+You can clear the cached data by calling `clearCache`. 
+
+`clearCache` receives a maximum of one parameter, `cacheKey: string` or `cacheKeys: string[]`.
+
+You can also clear all cached data by passing in nothing.
+
+```tsx | pure
+import { clearCache } from 'ahooks';
+
+clearCache(cacheKey);
+clearCache(cacheKeys)
+clearCache();
+```
+
+In the following example, we set three different `cacheKey`. When the component is loaded for the second time, the cached content will be returned first, and then the request will be re-run in background. You can experience clear the cached data by clicking these buttons.
+
+<code src="./demo/clear.tsx" />
+
+## API
+
+### Options
+
+| Property  | Description                                                                                                                                                                                                         | Type     | Default  |
+|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------|
+| cacheKey  |  A unique ID of the request. If `cacheKey` is set, we will enable the caching mechanism. The data of the same `cacheKey` is globally synchronized.                                                                      | `string` | `[]`     |
+| cacheTime | <ul><li> Set the cache time. By default, the cached data will be cleared after 5 minutes.</li><li> If set to `-1`, the cached data will never expire</li></ul>                                       | `number` | `300000` |
+| staleTime | <ul><li> Time to consider the cached data is fresh. Within this time interval, the request will not be re-initiated</li><li> If set to `-1`, it means that the data is always fresh</li></ul> | `number` | `0`      |
+
+## Remark
+
+* Only successful request data will be cached
+* Cached data includes `data` and `params`
diff --git a/packages/hooks/src/useRequest/doc/clearCache/clearCache.zh-CN.md b/packages/hooks/src/useRequest/doc/clearCache/clearCache.zh-CN.md
new file mode 100644
index 00000000..619b62f2
--- /dev/null
+++ b/packages/hooks/src/useRequest/doc/clearCache/clearCache.zh-CN.md
@@ -0,0 +1,34 @@
+---
+nav:
+  path: /hooks
+group:
+  path: /use-request
+---
+
+# 清除缓存
+
+通过调用 `clearCache` 来清除指定的缓存数据。
+
+`clearCache` 最多接收一个参数，`cacheKey: string` 或者 `cacheKeys: string[]`。
+
+你也可以不传参来清空所有缓存数据。
+
+```tsx | pure
+import { clearCache } from 'ahooks';
+
+clearCache(cacheKey);
+clearCache(cacheKeys)
+clearCache();
+```
+
+下面的示例，我们设置了不同的 `cacheKey`，在组件第二次加载时，会优先返回缓存的内容，然后在背后重新发起请求。你可以通过点击按钮来清除指定的缓存。
+
+<code src="./demo/clear.tsx" />
+
+## API
+
+### Options
+
+| 参数      | 说明                                                                                                                                    | 类型     | 默认值   |
+|-----------|-----------------------------------------------------------------------------------------------------------------------------------------|----------|----------|
+| cacheKey  | 请求唯一标识。如果设置了 `cacheKey`，我们会启用缓存机制。同一个 `cacheKey` 的数据全局同步。                                                 | `string` | `[]`     |
diff --git a/packages/hooks/src/useRequest/doc/clearCache/demo/clear.tsx b/packages/hooks/src/useRequest/doc/clearCache/demo/clear.tsx
new file mode 100644
index 00000000..d7f4f6df
--- /dev/null
+++ b/packages/hooks/src/useRequest/doc/clearCache/demo/clear.tsx
@@ -0,0 +1,68 @@
+import Mock from 'mockjs';
+import React from 'react';
+import { useRequest, clearCache, useBoolean } from 'ahooks';
+import { message } from 'antd';
+
+async function getArticle(): Promise<{ data: string; time: number }> {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        data: Mock.mock('@paragraph'),
+        time: new Date().getTime(),
+      });
+    }, 3000);
+  });
+}
+
+const Article = ({ cacheKey }) => {
+  const { data, loading, refresh } = useRequest(getArticle, {
+    cacheKey,
+  });
+  if (!data && loading) {
+    return <p>Loading</p>;
+  }
+  return (
+    <>
+      <p>Background loading: {loading ? 'true' : 'false'}</p>
+      <p>Latest request time: {data?.time}</p>
+      <p>{data?.data}</p>
+    </>
+  );
+};
+
+const clear = (cacheKey?: string | string[]) => {
+  clearCache(cacheKey);
+  const tips = Array.isArray(cacheKey) ? cacheKey.join('、') : cacheKey;
+  message.success(`Clear ${tips ?? 'All'} finished`);
+};
+
+export default () => {
+  const [state, { toggle }] = useBoolean();
+  return (
+    <div>
+      <p>
+        <button type="button" onClick={() => toggle()}>
+          show/hidden
+        </button>
+      </p>
+      <p>
+        <button style={{ marginRight: 8 }} onClick={() => clear('Article1')}>
+          Clear Article1
+        </button>
+        <button style={{ marginRight: 8 }} onClick={() => clear('Article2')}>
+          Clear Article2
+        </button>
+        <button style={{ marginRight: 8 }} onClick={() => clear(['Article2', 'Article3'])}>
+          Clear Article2 and Article3
+        </button>
+        <button onClick={() => clear()}>Clear All</button>
+      </p>
+      <h2>Article 1</h2>
+      {state && <Article cacheKey="Article1" />}
+      <h2>Article 2</h2>
+      {state && <Article cacheKey="Article2" />}
+      <h2>Article 3</h2>
+      {state && <Article cacheKey="Article3" />}
+    </div>
+  );
+};
diff --git a/packages/hooks/src/useRequest/index.ts b/packages/hooks/src/useRequest/index.ts
index ef870b3c..3b36ad29 100644
--- a/packages/hooks/src/useRequest/index.ts
+++ b/packages/hooks/src/useRequest/index.ts
@@ -1,3 +1,6 @@
 import useRequest from './src/useRequest';
+import { clearCache } from './src/utils/cache';
+
+export { clearCache };
 
 export default useRequest;
diff --git a/packages/hooks/src/useRequest/src/utils/cache.ts b/packages/hooks/src/useRequest/src/utils/cache.ts
index 1b3cfa3d..e419ec98 100644
--- a/packages/hooks/src/useRequest/src/utils/cache.ts
+++ b/packages/hooks/src/useRequest/src/utils/cache.ts
@@ -52,4 +52,13 @@ const subscribe = (key: string, listener: Listener) => {
   };
 };
 
-export { getCache, setCache, subscribe };
+const clearCache = (key?: string | string[]) => {
+  if (key) {
+    const cacheKeys = Array.isArray(key) ? key : [key];
+    cacheKeys.forEach((cacheKey) => cache.delete(cacheKey));
+  } else {
+    cache.clear();
+  }
+};
+
+export { getCache, setCache, subscribe, clearCache };


Test patch:
diff --git a/packages/hooks/src/useRequest/__tests__/useCachePlugin.test.ts b/packages/hooks/src/useRequest/__tests__/useCachePlugin.test.ts
index 8b394f0b..061dcf39 100644
--- a/packages/hooks/src/useRequest/__tests__/useCachePlugin.test.ts
+++ b/packages/hooks/src/useRequest/__tests__/useCachePlugin.test.ts
@@ -1,6 +1,6 @@
 import { act, renderHook } from '@testing-library/react-hooks';
 import MockDate from 'mockdate';
-import useRequest from '../index';
+import useRequest, { clearCache } from '../index';
 import { request } from '../../utils/testingHelpers';
 
 describe('useCachePlugin', () => {
@@ -146,4 +146,32 @@ describe('useCachePlugin', () => {
     expect(hook3.result.current.data).toEqual('success');
     hook3.unmount();
   });
+
+  it('clearCache should work', async () => {
+    MockDate.set(0);
+    act(() => {
+      hook = setUp(request, {
+        cacheKey: 'testClearCache',
+      });
+    });
+
+    act(() => {
+      jest.runAllTimers();
+    });
+    await hook.waitForNextUpdate();
+    expect(hook.result.current.loading).toEqual(false);
+    expect(hook.result.current.data).toEqual('success');
+    hook.unmount();
+
+    let hook2;
+    act(() => {
+      clearCache('testClearCache');
+      hook2 = setUp(request, {
+        cacheKey: 'testClearCache',
+      });
+    });
+    expect(hook2.result.current.loading).toEqual(true);
+    expect(hook2.result.current.data).toEqual(undefined);
+    hook2.unmount();
+  });
 });