缓存 & SWR
如果设置了 options.cacheKey
,useRequest
会将当前请求成功的数据缓存起来。下次组件初始化时,如果有缓存数据,我们会优先返回缓存数据,然后在背后发送新请求,也就是 SWR 的能力。
你可以通过 options.staleTime
设置数据保持新鲜时间,在该时间内,我们认为数据是新鲜的,不会重新发起请求。
你也可以通过 options.cacheTime
设置数据缓存时间,超过该时间,我们会清空该条缓存数据。
接下来通过几个例子来体验缓存这些功能。
SWR
下面的示例,我们设置了 cacheKey
,在组件第二次加载时,会优先返回缓存的内容,然后在背后重新发起请求。你可以通过点击按钮来体验效果。
- React
- Vue
network/useRequest/cache/cacheKey
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field } from '@taroify/core';
import { useRequest } from 'taro-hooks';
import { useBoolean } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(): Promise<{ data: string; time: number }> {
console.log('cacheKey');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph(4)'),
time: new Date().getTime(),
});
}, 1000);
});
}
const Article = () => {
const { data, loading } = useRequest(getArticle, {
cacheKey: 'cacheKey-demo',
});
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>{data?.data}</Field>
</>
);
};
export default () => {
const [state, { toggle }] = useBoolean();
return (
<DemoContent title="缓存 & SWR">
<Field align="center">
<Button color="primary" size="small" block onClick={() => toggle()}>
show / hidden
</Button>
</Field>
{state && <Article />}
</DemoContent>
);
};
network/useRequest/cache/cacheKey
<template>
<block>
<demo-content title="缓存 & SWR">
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="toggle()"
>
show / hidden
</nut-button>
</nut-row>
<block v-show="state">
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</block>
</demo-content>
</block>
</template>
<script>
import { useRequest } from 'taro-hooks';
import { escapeState } from '@taro-hooks/shared';
import { useToggle } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle() {
console.log('cacheKey-key');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
export default {
setup() {
const [state, action] = useToggle(false);
const request = useRequest(getArticle, {
cacheKey: 'cacheKey-demo',
});
const toggle = () => {
// 为了明显一点, vue放在方法里去掉用
if (!escapeState(state)) {
escapeState(request).run();
}
escapeState(action).toggle();
};
return { state, toggle, request };
},
};
</script>
数据保持新鲜
通过设置 staleTime
,我们可以指定数据新鲜时间,在这个时间内,不会重新发起请求。下面的示例设置了 5s 的新鲜时间,你可以通过点击按钮来体验效果
- React
- Vue
network/useRequest/cache/staleTime
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field } from '@taroify/core';
import { useRequest } from 'taro-hooks';
import { useBoolean } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(): Promise<{ data: string; time: number }> {
console.log('cacheKey-staleTime');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
const Article = () => {
const { data, loading } = useRequest(getArticle, {
cacheKey: 'staleTime-demo',
staleTime: 5000,
});
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>{data?.data}</Field>
</>
);
};
export default () => {
const [state, { toggle }] = useBoolean();
return (
<DemoContent title="缓存 & SWR - 数据保持新鲜">
<Field align="center">
<Button color="primary" size="small" block onClick={() => toggle()}>
show / hidden
</Button>
</Field>
{state && <Article />}
</DemoContent>
);
};
network/useRequest/cache/staleTime
<template>
<block>
<demo-content title="缓存 & SWR - 数据保持新鲜">
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="toggle()"
>
show / hidden
</nut-button>
</nut-row>
<block v-show="state">
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</block>
</demo-content>
</block>
</template>
<script>
import { useRequest } from 'taro-hooks';
import { escapeState } from '@taro-hooks/shared';
import { useToggle } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle() {
console.log('cacheKey-staleTime');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
export default {
setup() {
const [state, action] = useToggle(false);
const request = useRequest(getArticle, {
cacheKey: 'staleTime-demo',
staleTime: 5000,
});
const toggle = () => {
// 为了明显一点, vue放在方法里去掉用
if (!escapeState(state)) {
escapeState(request).run();
}
escapeState(action).toggle();
};
return { state, toggle, request };
},
};
</script>
数据共享
同一个 cacheKey
的内容,在全局是共享的,这会带来以下几个特性
- 请求
Promise
共享,相同的cacheKey
同时只会有一个在发起请求,后发起的会共用同一个请求Promise
- 数据同步,任何时候,当我们改变其中某个
cacheKey
的内容时,其它相同cacheKey
的内容均会同步
下面的示例中,初始化时,两个组件只会发起一个请求。并且两篇文章的内容永远是同步的。
- React
- Vue
network/useRequest/cache/share
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field } from '@taroify/core';
import { useRequest } from 'taro-hooks';
import Mock from 'mockjs';
async function getArticle(): Promise<{ data: string; time: number }> {
console.log('cacheKey-share');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 3000);
});
}
const Article = () => {
const { data, loading, refresh } = useRequest(getArticle, {
cacheKey: 'cacheKey-share',
});
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field align="center">
<Button
loading={loading}
color="primary"
size="small"
block
onClick={() => refresh()}
>
更新
</Button>
</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>{data?.data}</Field>
</>
);
};
export default () => {
return (
<DemoContent title="缓存 & SWR - 数据共享">
<Field>Article 1</Field>
<Article />
<Field>Article 2</Field>
<Article />
</DemoContent>
);
};
network/useRequest/cache/share
<template>
<block>
<demo-content title="缓存 & SWR - 数据共享">
<template v-for="(request, index) in [request1, request2]" :key="index">
<view>Article {{ index + 1 }}</view>
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="request.refresh()"
>
更新
</nut-button>
</nut-row>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</template>
</demo-content>
</block>
</template>
<script>
import { useRequest } from 'taro-hooks';
import Mock from 'mockjs';
async function getArticle() {
console.log('cacheKey-share');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 3000);
});
}
export default {
setup() {
const request1 = useRequest(getArticle, {
cacheKey: 'cacheKey-share',
});
const request2 = useRequest(getArticle, {
cacheKey: 'cacheKey-share',
});
return { request1, request2 };
},
};
</script>
参数缓存
缓存的数据包括 data
和 params
,通过 params
缓存机制,我们可以记忆上一次请求的条件,并在下次初始化。
下面的示例中,我们可以从缓存的 params
中初始化 keyword
- React
- Vue
network/useRequest/cache/params
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field, Input } from '@taroify/core';
import { useRequest } from 'taro-hooks';
import { useState } from '@taro-hooks/core';
import { useBoolean } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(
keyword: string,
): Promise<{ data: string; time: number }> {
console.log('cacheKey', keyword);
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
const Article = () => {
const { data, params, loading, run } = useRequest(getArticle, {
cacheKey: 'cacheKey-demo',
});
const [keyword, setKeyword] = useState(params[0] || '');
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field align="center">
<Input value={keyword} onChange={(e) => setKeyword(e.detail.value)} />
<Button color="primary" size="small" onClick={() => run(keyword)}>
Get
</Button>
</Field>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>Keyword: {keyword}</Field>
<Field>{data?.data}</Field>
</>
);
};
export default () => {
const [state, { toggle }] = useBoolean();
return (
<DemoContent title="缓存 & SWR - 参数缓存">
<Field align="center">
<Button color="primary" size="small" block onClick={() => toggle()}>
show / hidden
</Button>
</Field>
{state && <Article />}
</DemoContent>
);
};
network/useRequest/cache/params
<template>
<block>
<demo-content title="缓存 & SWR - 参数缓存">
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="toggle()"
>
show / hidden
</nut-button>
</nut-row>
<block v-show="state">
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<nut-input
placeholder="keyword"
@update:model-value="setKeyword($event)"
>
<template #button>
<nut-button
:loading="request.loading"
size="small"
type="primary"
shape="square"
@click="request.run(keyword)"
>Get</nut-button
>
</template>
</nut-input>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</block>
</demo-content>
</block>
</template>
<script>
import { useRequest } from 'taro-hooks';
import { useState } from '@taro-hooks/core';
import { escapeState } from '@taro-hooks/shared';
import { useToggle } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(keyword) {
console.log('cacheKey-demo', keyword);
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
export default {
setup() {
const [state, action] = useToggle(false);
const request = useRequest(getArticle, {
cacheKey: 'cacheKey-demo',
});
const [keyword, setKeyword] = useState(
escapeState(request).params[0] || '',
);
const toggle = () => {
// 为了明显一点, vue放在方法里去掉用
if (!escapeState(state)) {
escapeState(request).run();
}
escapeState(action).toggle();
};
return { state, toggle, request, keyword, setKeyword };
},
};
</script>
删除缓存
taro-hooks 提供了一个 clearCache
方法,可以清除指定 cacheKey
的缓存数据。
警告
vue中清除缓存的例子不太好实现. 组件卸载原则的问题.
- React
- Vue
network/useRequest/cache/clearCache
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field } from '@taroify/core';
import { useRequest, clearCache } from 'taro-hooks';
import { showToast } from '@tarojs/taro';
import { useBoolean } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(): Promise<{ data: string; time: number }> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph(4)'),
time: new Date().getTime(),
});
}, 3000);
});
}
const Article = ({ cacheKey }) => {
const { data, loading } = useRequest(getArticle, {
cacheKey,
});
console.log(data, Mock.mock('@paragraph()'));
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>{data?.data}</Field>
</>
);
};
const clear = (cacheKey?: string | string[]) => {
clearCache(cacheKey);
const tips = Array.isArray(cacheKey) ? cacheKey.join('、') : cacheKey;
showToast({
title: 'Clear ${tips ?? 'All'} finished',
icon: 'success',
mask: true,
});
};
export default () => {
const [state, { toggle }] = useBoolean();
return (
<DemoContent title="缓存 & SWR - 删除缓存">
<Field align="center">
<Button color="primary" size="small" block onClick={() => toggle()}>
show / hidden
</Button>
</Field>
<Field align="center">
<Button
color="primary"
size="small"
block
onClick={() => clear('Article1')}
>
clear Article1
</Button>
</Field>
<Field align="center">
<Button
color="primary"
size="small"
block
onClick={() => clear('Article2')}
>
clear Article2
</Button>
</Field>
<Field align="center">
<Button
color="primary"
size="small"
block
onClick={() => clear(['Article2', 'Article3'])}
>
clear Article2 and Article3
</Button>
</Field>
<Field align="center">
<Button color="primary" size="small" block onClick={() => clear()}>
clear all
</Button>
</Field>
<Field>Article 1</Field>
{state && <Article cacheKey="Article1" />}
<Field>Article 2</Field>
{state && <Article cacheKey="Article2" />}
<Field>Article 3</Field>
{state && <Article cacheKey="Article3" />}
</DemoContent>
);
};
network/useRequest/cache/clearCache
<template>
<block>
<demo-content title="缓存 & SWR - 删除缓存">
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="toggle()"
>
show / hidden
</nut-button>
</nut-row>
<nut-row
justify="center"
v-for="cacheKey in cacheKeyList"
:key="cacheKey"
>
<nut-button
size="small"
type="primary"
shape="square"
block
@click="clear(cacheKey)"
>
clear {{ cacheKey }}
</nut-button>
</nut-row>
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="clear(['Article2', 'Article3'])"
>
clear Article2 and Article3
</nut-button>
</nut-row>
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="clear()"
>
clear all
</nut-button>
</nut-row>
<template
v-for="(request, index) in [request1, request2, request3]"
:key="index"
>
<view>Article {{ index + 1 }}</view>
<block v-show="state">
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</block>
</template>
</demo-content>
</block>
</template>
<script>
import { useRequest, clearCache } from 'taro-hooks';
import { showToast } from '@tarojs/taro';
import { useToggle } from '@taro-hooks/ahooks';
import { escapeState } from '@taro-hooks/shared';
import Mock from 'mockjs';
async function getArticle() {
console.log('cacheKey-share');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 3000);
});
}
export default {
setup() {
const [state, action] = useToggle(false);
const cacheKeyList = ['Article1', 'Article2', 'Article3'];
const request1 = useRequest(getArticle, {
cacheKey: cacheKeyList[0],
});
const request2 = useRequest(getArticle, {
cacheKey: cacheKeyList[1],
});
const request3 = useRequest(getArticle, {
cacheKey: cacheKeyList[2],
});
const clear = (cacheKey) => {
clearCache(cacheKey);
const tips = Array.isArray(cacheKey) ? cacheKey.join('、') : cacheKey;
showToast({
title: 'Clear ${tips ?? 'All'} finished',
icon: 'success',
mask: true,
});
};
const toggle = () => {
// 为了明显一点, vue放在方法里去掉用
if (!escapeState(state)) {
escapeState(request1).run();
escapeState(request2).run();
escapeState(request3).run();
}
escapeState(action).toggle();
};
return { state, request1, request2, request3, cacheKeyList, clear, toggle };
},
};
</script>
自定义缓存
通过配置 setCache
和 getCache
,可以自定义数据缓存,比如可以将数据存储到 localStorage
、IndexDB
等。
请注意:
setCache
和getCache
需要配套使用。- 在自定义缓存模式下,
cacheTime
和clearCache
不会生效,请根据实际情况自行实现。
- React
- Vue
network/useRequest/cache/setCache
import React from 'react';
import DemoContent from '@src/components/DemoContent';
import { Button, Field } from '@taroify/core';
import { setStorageSync, getStorageSync } from '@tarojs/taro';
import { useRequest } from 'taro-hooks';
import { useBoolean } from '@taro-hooks/ahooks';
import Mock from 'mockjs';
async function getArticle(): Promise<{ data: string; time: number }> {
console.log('cacheKey');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
const cacheKey = 'setCache-demo';
const Article = () => {
const { data, loading } = useRequest(getArticle, {
cacheKey,
setCache: (responseData) =>
setStorageSync(cacheKey, JSON.stringify(responseData)),
getCache: () => JSON.parse(getStorageSync(cacheKey) || '{}'),
});
if (!data && loading) {
return <Field>Loading</Field>;
}
return (
<>
<Field>Background loading: {loading ? 'true' : 'false'}</Field>
<Field>Latest request time: {data?.time}</Field>
<Field>{data?.data}</Field>
</>
);
};
export default () => {
const [state, { toggle }] = useBoolean();
return (
<DemoContent title="缓存 & SWR - 自定义缓存">
<Field align="center">
<Button color="primary" size="small" block onClick={() => toggle()}>
show / hidden
</Button>
</Field>
{state && <Article />}
</DemoContent>
);
};
network/useRequest/cache/setCache
<template>
<block>
<demo-content title="缓存 & SWR - 自定义缓存">
<nut-row justify="center">
<nut-button
size="small"
type="primary"
shape="square"
block
@click="toggle()"
>
show / hidden
</nut-button>
</nut-row>
<block v-show="state">
<view v-if="!request.data && request.loading">Loading...</view>
<template v-else>
<view>
Background loading:
{{ request.loading ? 'true' : 'false' }}
</view>
<view>Latest request time: {{ request.data?.time }}</view>
<view>{{ request.data?.data }}</view>
</template>
</block>
</demo-content>
</block>
</template>
<script>
import { useRequest } from 'taro-hooks';
import { escapeState } from '@taro-hooks/shared';
import { useToggle } from '@taro-hooks/ahooks';
import { setStorageSync, getStorageSync } from '@tarojs/taro';
import Mock from 'mockjs';
async function getArticle() {
console.log('cacheKey');
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: Mock.mock('@paragraph()'),
time: new Date().getTime(),
});
}, 1000);
});
}
const cacheKey = 'setCache-demo';
export default {
setup() {
const [state, action] = useToggle(false);
const request = useRequest(getArticle, {
cacheKey,
setCache: (responseData) =>
setStorageSync(cacheKey, JSON.stringify(responseData)),
getCache: () => JSON.parse(getStorageSync(cacheKey) || '{}'),
});
const toggle = () => {
// 为了明显一点, vue放在方法里去掉用
if (!escapeState(state)) {
escapeState(request).run();
}
escapeState(action).toggle();
};
return { state, toggle, request };
},
};
</script>
API
interface CachedData<TData, TParams> {
data: TData;
params: TParams;
time: number;
}
Options
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
cacheKey | 请求唯一标识。如果设置了 cacheKey ,我们会启用缓存机制。同一个 cacheKey 的数据全局同步。 | string | - |
cacheTime |
| number | 300000 |
staleTime |
| number | 0 |
setCache |
| (data: CachedData) => void; | - |
getCache | 自定义读取缓存 | (params: TParams) => CachedData | - |
clearCache
import { clearCache } from 'taro-hooks';
clearCache(cacheKey?: string | string[]);
- 支持清空单个缓存,或一组缓存
- 如果
cacheKey
为空,则清空所有缓存数据
备注
- 只有成功的请求数据才会缓存
- 缓存的数据包括
data
和params