前端性能优化实用方案(六):智能缓存策略减少90%重复请求

举报
Yeats_Liao 发表于 2025/11/16 19:17:01 2025/11/16
【摘要】 6. 数据缓存策略前端性能优化中,数据缓存是个绕不开的话题。用好了能让页面飞起来,用不好就是给自己挖坑。缓存就像家里的冰箱,什么东西该放进去,什么时候该扔掉,这都有讲究。放错了东西,不仅占地方,还可能坏掉影响其他食物。 6.1 接口数据缓存的门道 6.1.1 哪些数据适合缓存不是所有数据都适合往 localStorage 或 cookies 里塞。只有那些不怎么变化或者有固定更新周期的数据...

6. 数据缓存策略

前端性能优化中,数据缓存是个绕不开的话题。用好了能让页面飞起来,用不好就是给自己挖坑。

缓存就像家里的冰箱,什么东西该放进去,什么时候该扔掉,这都有讲究。放错了东西,不仅占地方,还可能坏掉影响其他食物。

6.1 接口数据缓存的门道

6.1.1 哪些数据适合缓存

不是所有数据都适合往 localStoragecookies 里塞。只有那些不怎么变化或者有固定更新周期的数据才值得缓存:

  • 用户基础信息: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小时
    }
  );
};

小结

选择合适的缓存类型

  1. 内存缓存:适合频繁访问的临时数据

    • 优点:速度最快
    • 缺点:页面刷新就没了
    • 适用:API结果、计算结果、组件状态
  2. 持久化缓存:适合长期有效的数据

    • 优点:能保存很久
    • 缺点:存储空间有限,可能被浏览器清理
    • 适用:用户信息、配置数据、字典数据
  3. 多级缓存:结合两者优势

    • 内存缓存做一级缓存
    • 持久化缓存做二级缓存
    • 提供最佳性能和用户体验

通过合理的缓存策略,可以大幅减少网络请求,让页面响应更快,用户体验更好。但要记住,缓存是为了提升性能,不能因为缓存导致数据不一致的问题。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。