缓存 & 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