前端性能优化实用方案(六):智能缓存策略减少90%重复请求
【摘要】 6. 数据缓存策略前端性能优化中,数据缓存是个绕不开的话题。用好了能让页面飞起来,用不好就是给自己挖坑。缓存就像家里的冰箱,什么东西该放进去,什么时候该扔掉,这都有讲究。放错了东西,不仅占地方,还可能坏掉影响其他食物。 6.1 接口数据缓存的门道 6.1.1 哪些数据适合缓存不是所有数据都适合往 localStorage 或 cookies 里塞。只有那些不怎么变化或者有固定更新周期的数据...
6. 数据缓存策略
前端性能优化中,数据缓存是个绕不开的话题。用好了能让页面飞起来,用不好就是给自己挖坑。
缓存就像家里的冰箱,什么东西该放进去,什么时候该扔掉,这都有讲究。放错了东西,不仅占地方,还可能坏掉影响其他食物。
6.1 接口数据缓存的门道
6.1.1 哪些数据适合缓存
不是所有数据都适合往 localStorage 或 cookies 里塞。只有那些不怎么变化或者有固定更新周期的数据才值得缓存:
- 用户基础信息:token、用户名、用户ID这些
- 系统配置:应用配置、字典数据
- 相对静态的数据:城市列表、商品分类
动态数据就别想了,比如用户的消息列表、实时股价这些,缓存了反而会误导用户。
// 缓存工具类 - 带过期时间的本地存储
class StorageCache {
constructor() {
this.prefix = 'app_cache_';
}
// 存储数据,默认24小时过期
set(key, value, expireTime = 24 * 60 * 60 * 1000) {
const data = {
value,
expire: Date.now() + expireTime,
timestamp: Date.now()
};
try {
localStorage.setItem(this.prefix + key, JSON.stringify(data));
return true;
} catch (error) {
console.warn('localStorage存储失败:', error);
return false;
}
}
// 获取数据,自动检查过期
get(key) {
try {
const item = localStorage.getItem(this.prefix + key);
if (!item) return null;
const data = JSON.parse(item);
// 过期了就删掉
if (Date.now() > data.expire) {
this.remove(key);
return null;
}
return data.value;
} catch (error) {
console.warn('localStorage读取失败:', error);
return null;
}
}
remove(key) {
localStorage.removeItem(this.prefix + key);
}
// 清空所有缓存
clear() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
localStorage.removeItem(key);
}
});
}
// 检查缓存是否还有效
has(key) {
return this.get(key) !== null;
}
}
// 实际使用
const cache = new StorageCache();
// 缓存用户token,7天过期
cache.set('user_token', 'abc123', 7 * 24 * 60 * 60 * 1000);
// 缓存用户信息,1天过期
cache.set('user_info', {
id: 1001,
name: '张三',
avatar: 'avatar.jpg'
}, 24 * 60 * 60 * 1000);
// 获取缓存数据
const token = cache.get('user_token');
const userInfo = cache.get('user_info');
6.1.2 API请求缓存策略
有了基础的缓存工具,我们可以在API层面做更智能的缓存。
// API缓存管理器
class ApiCache {
constructor() {
this.cache = new StorageCache();
}
// 带缓存的请求方法
async request(url, options = {}, cacheConfig = {}) {
const {
useCache = false,
cacheTime = 5 * 60 * 1000, // 默认5分钟
cacheKey = url
} = cacheConfig;
// 先看看缓存里有没有
if (useCache) {
const cachedData = this.cache.get(cacheKey);
if (cachedData) {
console.log('从缓存获取数据:', cacheKey);
return cachedData;
}
}
try {
const response = await fetch(url, options);
const data = await response.json();
// 请求成功了就缓存起来
if (useCache && response.ok) {
this.cache.set(cacheKey, data, cacheTime);
console.log('数据已缓存:', cacheKey);
}
return data;
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
clearCache(cacheKey) {
this.cache.remove(cacheKey);
}
clearAllCache() {
this.cache.clear();
}
}
// 使用示例
const apiCache = new ApiCache();
// 字典数据缓存30分钟,这种数据变化不频繁
const getDictData = () => {
return apiCache.request('/api/dict', {}, {
useCache: true,
cacheTime: 30 * 60 * 1000,
cacheKey: 'dict_data'
});
};
// 用户动态数据不缓存,每次都要最新的
const getUserPosts = (userId) => {
return apiCache.request(`/api/users/${userId}/posts`, {}, {
useCache: false
});
};
6.2 内存缓存 - 快但不持久
内存缓存就是把数据存在JavaScript的变量里,访问速度最快,但页面一刷新就没了。
这种缓存适合存放那些在当前会话中会反复使用的数据。
6.2.1 基础内存缓存实现
// 内存缓存管理器,支持LRU淘汰策略
class MemoryCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
this.accessOrder = new Map(); // 记录访问顺序
}
set(key, value, ttl = 0) {
// 缓存满了就删掉最久没用的
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
this.evictLRU();
}
const item = {
value,
timestamp: Date.now(),
ttl,
expire: ttl > 0 ? Date.now() + ttl : 0
};
this.cache.set(key, item);
this.accessOrder.set(key, Date.now());
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查是否过期
if (item.expire > 0 && Date.now() > item.expire) {
this.delete(key);
return null;
}
// 更新访问时间,用于LRU
this.accessOrder.set(key, Date.now());
return item.value;
}
delete(key) {
this.cache.delete(key);
this.accessOrder.delete(key);
}
// LRU淘汰:删掉最久没访问的数据
evictLRU() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, time] of this.accessOrder) {
if (time < oldestTime) {
oldestTime = time;
oldestKey = key;
}
}
if (oldestKey) {
this.delete(oldestKey);
}
}
clear() {
this.cache.clear();
this.accessOrder.clear();
}
// 查看缓存状态
getStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
keys: Array.from(this.cache.keys())
};
}
}
// 全局内存缓存实例
const memoryCache = new MemoryCache(50);
// 使用示例
memoryCache.set('user_1001', { name: '张三', age: 25 }, 10 * 60 * 1000); // 10分钟过期
memoryCache.set('api_result_123', { data: [...] }, 5 * 60 * 1000); // 5分钟过期
const userData = memoryCache.get('user_1001');
6.2.2 Vue项目中的缓存实现
在Vue项目里,我们通常把缓存放在Vuex store中管理。
// store/modules/cache.js
const state = {
apiCache: new Map(),
userCache: new Map(),
configCache: new Map()
};
const mutations = {
SET_API_CACHE(state, { key, data, expire = 0 }) {
state.apiCache.set(key, {
data,
timestamp: Date.now(),
expire: expire > 0 ? Date.now() + expire : 0
});
},
SET_USER_CACHE(state, { key, data }) {
state.userCache.set(key, {
data,
timestamp: Date.now()
});
},
CLEAR_CACHE(state, cacheType) {
if (cacheType === 'api') {
state.apiCache.clear();
} else if (cacheType === 'user') {
state.userCache.clear();
} else if (cacheType === 'all') {
state.apiCache.clear();
state.userCache.clear();
state.configCache.clear();
}
}
};
const getters = {
getApiCache: (state) => (key) => {
const item = state.apiCache.get(key);
if (!item) return null;
// 检查过期
if (item.expire > 0 && Date.now() > item.expire) {
state.apiCache.delete(key);
return null;
}
return item.data;
},
getUserCache: (state) => (key) => {
const item = state.userCache.get(key);
return item ? item.data : null;
}
};
const actions = {
// 带缓存的API请求
async fetchWithCache({ commit, getters }, { url, cacheKey, cacheTime = 5 * 60 * 1000 }) {
// 先从缓存找
const cachedData = getters.getApiCache(cacheKey);
if (cachedData) {
return cachedData;
}
try {
const response = await fetch(url);
const data = await response.json();
// 存到缓存
commit('SET_API_CACHE', {
key: cacheKey,
data,
expire: cacheTime
});
return data;
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
};
export default {
namespaced: true,
state,
mutations,
getters,
actions
};
6.2.3 React项目中的缓存Hook
React项目里,我们可以封装一个自定义Hook来处理缓存逻辑。
// hooks/useCache.js
import { useState, useEffect, useRef } from 'react';
// 全局缓存,所有组件共享
const globalCache = new Map();
export const useCache = (key, fetcher, options = {}) => {
const {
cacheTime = 5 * 60 * 1000, // 默认5分钟
staleTime = 0, // 数据新鲜时间
enabled = true
} = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const mountedRef = useRef(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
const fetchData = async (forceRefresh = false) => {
if (!enabled) return;
const cached = globalCache.get(key);
const now = Date.now();
if (!forceRefresh && cached) {
const isStale = now - cached.timestamp > staleTime;
const isExpired = now > cached.expire;
if (!isExpired) {
setData(cached.data);
// 数据有点旧了,后台偷偷更新一下
if (isStale) {
fetchFreshData();
}
return;
}
}
await fetchFreshData();
};
const fetchFreshData = async () => {
setLoading(true);
setError(null);
try {
const result = await fetcher();
if (mountedRef.current) {
setData(result);
// 更新缓存
globalCache.set(key, {
data: result,
timestamp: Date.now(),
expire: Date.now() + cacheTime
});
}
} catch (err) {
if (mountedRef.current) {
setError(err);
}
} finally {
if (mountedRef.current) {
setLoading(false);
}
}
};
useEffect(() => {
fetchData();
}, [key, enabled]);
const refetch = () => fetchData(true);
const invalidate = () => globalCache.delete(key);
return {
data,
loading,
error,
refetch,
invalidate
};
};
// 使用示例
const UserProfile = ({ userId }) => {
const {
data: user,
loading,
error,
refetch
} = useCache(
`user_${userId}`,
() => fetch(`/api/users/${userId}`).then(res => res.json()),
{
cacheTime: 10 * 60 * 1000, // 10分钟缓存
staleTime: 2 * 60 * 1000 // 2分钟内认为数据新鲜
}
);
if (loading) return <div>加载中...</div>;
if (error) return <div>加载失败</div>;
return (
<div>
<h1>{user?.name}</h1>
<button onClick={refetch}>刷新</button>
</div>
);
};
6.3 智能缓存策略
实际项目中,我们往往需要结合多种缓存策略。内存缓存速度快,持久化缓存能保存更久,组合使用效果最好。
// 智能缓存管理器 - 多级缓存
class SmartCache {
constructor() {
this.memoryCache = new MemoryCache(100);
this.storageCache = new StorageCache();
this.requestQueue = new Map(); // 防止重复请求
}
async get(key, fetcher, options = {}) {
const {
cacheLevel = 'memory', // 'memory' | 'storage' | 'both'
cacheTime = 5 * 60 * 1000,
forceRefresh = false
} = options;
if (!forceRefresh) {
// 第一层:内存缓存
if (cacheLevel === 'memory' || cacheLevel === 'both') {
const memoryData = this.memoryCache.get(key);
if (memoryData) {
return memoryData;
}
}
// 第二层:持久化缓存
if (cacheLevel === 'storage' || cacheLevel === 'both') {
const storageData = this.storageCache.get(key);
if (storageData) {
// 同时放到内存缓存里,下次更快
this.memoryCache.set(key, storageData, cacheTime);
return storageData;
}
}
}
// 防止同时发起多个相同请求
if (this.requestQueue.has(key)) {
return this.requestQueue.get(key);
}
const promise = this.fetchAndCache(key, fetcher, options);
this.requestQueue.set(key, promise);
try {
const result = await promise;
return result;
} finally {
this.requestQueue.delete(key);
}
}
async fetchAndCache(key, fetcher, options) {
const { cacheLevel = 'memory', cacheTime = 5 * 60 * 1000 } = options;
try {
const data = await fetcher();
// 根据配置存储到不同级别的缓存
if (cacheLevel === 'memory' || cacheLevel === 'both') {
this.memoryCache.set(key, data, cacheTime);
}
if (cacheLevel === 'storage' || cacheLevel === 'both') {
this.storageCache.set(key, data, cacheTime);
}
return data;
} catch (error) {
console.error('数据获取失败:', error);
throw error;
}
}
// 清除指定缓存
clear(key, level = 'both') {
if (level === 'memory' || level === 'both') {
this.memoryCache.delete(key);
}
if (level === 'storage' || level === 'both') {
this.storageCache.remove(key);
}
}
}
// 全局智能缓存实例
const smartCache = new SmartCache();
// 使用示例
const getUserData = async (userId) => {
return smartCache.get(
`user_${userId}`,
() => fetch(`/api/users/${userId}`).then(res => res.json()),
{
cacheLevel: 'both',
cacheTime: 30 * 60 * 1000 // 30分钟
}
);
};
const getConfigData = async () => {
return smartCache.get(
'app_config',
() => fetch('/api/config').then(res => res.json()),
{
cacheLevel: 'storage',
cacheTime: 24 * 60 * 60 * 1000 // 24小时
}
);
};
小结
选择合适的缓存类型
-
内存缓存:适合频繁访问的临时数据
- 优点:速度最快
- 缺点:页面刷新就没了
- 适用:API结果、计算结果、组件状态
-
持久化缓存:适合长期有效的数据
- 优点:能保存很久
- 缺点:存储空间有限,可能被浏览器清理
- 适用:用户信息、配置数据、字典数据
-
多级缓存:结合两者优势
- 内存缓存做一级缓存
- 持久化缓存做二级缓存
- 提供最佳性能和用户体验
通过合理的缓存策略,可以大幅减少网络请求,让页面响应更快,用户体验更好。但要记住,缓存是为了提升性能,不能因为缓存导致数据不一致的问题。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)