时里院子市集
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

6.1 KiB

🚨 useShopInfo 无限循环问题修复

问题描述

useShopInfo Hook 出现无限循环请求的问题,控制台不断输出 shopInfo 请求日志。

🔍 问题分析

根本原因

Hook中存在循环依赖导致的无限循环:

// 问题代码 ❌
const fetchShopInfo = useCallback(async (forceRefresh = false) => {
  if (!forceRefresh && loadShopInfoFromStorage()) {
    return shopInfo; // 依赖shopInfo
  }
  // ...
}, [shopInfo, loadShopInfoFromStorage, saveShopInfoToStorage]); // 依赖shopInfo

useEffect(() => {
  fetchShopInfo(); // 依赖fetchShopInfo
}, [fetchShopInfo]); // 当fetchShopInfo变化时重新执行

循环链路

  1. useEffect 依赖 fetchShopInfo
  2. fetchShopInfo 依赖 shopInfo
  3. shopInfo 更新时,fetchShopInfo 重新创建
  4. fetchShopInfo 变化触发 useEffect 重新执行
  5. useEffect 再次调用 fetchShopInfo
  6. 无限循环 🔄

🔧 修复方案

1. 移除fetchShopInfo对shopInfo的依赖

修复前

const fetchShopInfo = useCallback(async (forceRefresh = false) => {
  // 如果不是强制刷新,先尝试从缓存加载
  if (!forceRefresh && loadShopInfoFromStorage()) {
    return shopInfo; // ❌ 依赖shopInfo导致循环
  }
  // ...
}, [shopInfo, loadShopInfoFromStorage, saveShopInfoToStorage]);

修复后

const fetchShopInfo = useCallback(async (forceRefresh = false) => {
  try {
    setLoading(true);
    setError(null);
    
    const data = await getShopInfo();
    setShopInfo(data);
    
    // 保存到本地存储
    saveShopInfoToStorage(data);
    
    return data;
  } catch (error) {
    // 错误处理...
  } finally {
    setLoading(false);
  }
}, [saveShopInfoToStorage]); // ✅ 移除shopInfo依赖

2. 重构初始化逻辑

修复前

useEffect(() => {
  fetchShopInfo(); // ❌ 依赖fetchShopInfo导致循环
}, [fetchShopInfo]);

修复后

useEffect(() => {
  const initShopInfo = async () => {
    // 先尝试从缓存加载
    const hasCache = loadShopInfoFromStorage();
    
    // 如果没有缓存,则从服务器获取
    if (!hasCache) {
      await fetchShopInfo();
    }
  };
  
  initShopInfo();
}, []); // ✅ 空依赖数组,只执行一次

3. 独立的刷新函数

修复前

const refreshShopInfo = useCallback(() => {
  return fetchShopInfo(true); // ❌ 依赖fetchShopInfo
}, [fetchShopInfo]);

修复后

const refreshShopInfo = useCallback(async () => {
  try {
    setLoading(true);
    setError(null);
    
    const data = await getShopInfo();
    setShopInfo(data);
    
    // 保存到本地存储
    saveShopInfoToStorage(data);
    
    return data;
  } catch (error) {
    // 错误处理...
  } finally {
    setLoading(false);
  }
}, [saveShopInfoToStorage]); // ✅ 独立实现,避免循环依赖

📊 修复对比

项目 修复前 修复后 说明
fetchShopInfo依赖 [shopInfo, ...] [saveShopInfoToStorage] 移除shopInfo依赖
useEffect依赖 [fetchShopInfo] [] 只执行一次初始化
缓存检查 在fetchShopInfo中 在useEffect中 分离关注点
刷新函数 依赖fetchShopInfo 独立实现 避免循环依赖
请求次数 无限循环 按需请求 性能优化

修复效果

修复前

🔄 无限循环请求
📊 控制台不断输出shopInfo日志
⚡ 性能问题,浪费网络资源
🐛 用户体验差,页面卡顿

修复后

✅ 只在需要时请求一次
📊 控制台日志正常
⚡ 性能优化,智能缓存
🚀 用户体验良好,页面流畅

🎯 Hook执行流程

修复后的正确流程

1. 组件挂载
   ↓
2. useEffect执行(只执行一次)
   ↓
3. 检查本地缓存
   ↓
4. 如果有缓存 → 使用缓存数据,结束
   ↓
5. 如果无缓存 → 调用fetchShopInfo
   ↓
6. 获取数据,更新状态,保存缓存
   ↓
7. 结束,不再重复请求

🧪 验证方法

1. 控制台检查

  • 不再有重复的shopInfo请求日志
  • 只在初始化时请求一次
  • 刷新时才会重新请求

2. 网络面板检查

  • Network面板中只有必要的请求
  • 没有重复的/shop/getShopInfo请求
  • 缓存机制正常工作

3. 功能验证

  • 商店信息正常显示
  • Logo和网站名称正确
  • 缓存机制工作正常
  • 手动刷新功能正常

🛠️ 预防措施

1. 避免循环依赖

// ❌ 避免这样的依赖关系
const funcA = useCallback(() => {
  // 使用stateB
}, [stateB]);

const funcB = useCallback(() => {
  funcA();
}, [funcA]);

useEffect(() => {
  funcB();
}, [funcB]);

2. 合理使用useCallback依赖

// ✅ 只依赖真正需要的值
const fetchData = useCallback(async () => {
  // 不要在依赖数组中包含会变化的状态
}, [/* 只包含稳定的依赖 */]);

3. useEffect依赖管理

// ✅ 初始化逻辑使用空依赖数组
useEffect(() => {
  // 初始化逻辑
}, []); // 只执行一次

// ✅ 响应式逻辑明确依赖
useEffect(() => {
  // 响应某个值的变化
}, [specificValue]);

📈 性能改进

请求优化

  • 减少网络请求:从无限循环到按需请求
  • 智能缓存:30分钟缓存机制正常工作
  • 内存优化:避免不必要的重渲染

用户体验

  • 页面流畅:消除卡顿问题
  • 快速加载:缓存数据立即可用
  • 错误处理:网络失败时使用缓存

🎉 总结

通过重构Hook的依赖关系和执行流程,成功修复了无限循环问题:

  • 移除循环依赖:fetchShopInfo不再依赖shopInfo
  • 优化初始化:useEffect只执行一次
  • 独立刷新函数:避免函数间的循环依赖
  • 保持功能完整:所有原有功能正常工作
  • 性能提升:从无限请求到智能缓存

现在useShopInfo Hook工作正常,不再有无限循环问题! 🚀