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
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变化时重新执行
循环链路
useEffect
依赖fetchShopInfo
fetchShopInfo
依赖shopInfo
- 当
shopInfo
更新时,fetchShopInfo
重新创建 fetchShopInfo
变化触发useEffect
重新执行useEffect
再次调用fetchShopInfo
- 无限循环 🔄
🔧 修复方案
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工作正常,不再有无限循环问题! 🚀