# 🚨 useShopInfo 无限循环问题修复 ## 问题描述 `useShopInfo` Hook 出现无限循环请求的问题,控制台不断输出 `shopInfo` 请求日志。 ## 🔍 问题分析 ### 根本原因 Hook中存在循环依赖导致的无限循环: ```typescript // 问题代码 ❌ 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的依赖** #### 修复前 ❌ ```typescript const fetchShopInfo = useCallback(async (forceRefresh = false) => { // 如果不是强制刷新,先尝试从缓存加载 if (!forceRefresh && loadShopInfoFromStorage()) { return shopInfo; // ❌ 依赖shopInfo导致循环 } // ... }, [shopInfo, loadShopInfoFromStorage, saveShopInfoToStorage]); ``` #### 修复后 ✅ ```typescript 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. **重构初始化逻辑** #### 修复前 ❌ ```typescript useEffect(() => { fetchShopInfo(); // ❌ 依赖fetchShopInfo导致循环 }, [fetchShopInfo]); ``` #### 修复后 ✅ ```typescript useEffect(() => { const initShopInfo = async () => { // 先尝试从缓存加载 const hasCache = loadShopInfoFromStorage(); // 如果没有缓存,则从服务器获取 if (!hasCache) { await fetchShopInfo(); } }; initShopInfo(); }, []); // ✅ 空依赖数组,只执行一次 ``` ### 3. **独立的刷新函数** #### 修复前 ❌ ```typescript const refreshShopInfo = useCallback(() => { return fetchShopInfo(true); // ❌ 依赖fetchShopInfo }, [fetchShopInfo]); ``` #### 修复后 ✅ ```typescript 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. **避免循环依赖** ```typescript // ❌ 避免这样的依赖关系 const funcA = useCallback(() => { // 使用stateB }, [stateB]); const funcB = useCallback(() => { funcA(); }, [funcA]); useEffect(() => { funcB(); }, [funcB]); ``` ### 2. **合理使用useCallback依赖** ```typescript // ✅ 只依赖真正需要的值 const fetchData = useCallback(async () => { // 不要在依赖数组中包含会变化的状态 }, [/* 只包含稳定的依赖 */]); ``` ### 3. **useEffect依赖管理** ```typescript // ✅ 初始化逻辑使用空依赖数组 useEffect(() => { // 初始化逻辑 }, []); // 只执行一次 // ✅ 响应式逻辑明确依赖 useEffect(() => { // 响应某个值的变化 }, [specificValue]); ``` ## 📈 性能改进 ### 请求优化 - ✅ **减少网络请求**:从无限循环到按需请求 - ✅ **智能缓存**:30分钟缓存机制正常工作 - ✅ **内存优化**:避免不必要的重渲染 ### 用户体验 - ✅ **页面流畅**:消除卡顿问题 - ✅ **快速加载**:缓存数据立即可用 - ✅ **错误处理**:网络失败时使用缓存 ## 🎉 总结 通过重构Hook的依赖关系和执行流程,成功修复了无限循环问题: - ✅ **移除循环依赖**:fetchShopInfo不再依赖shopInfo - ✅ **优化初始化**:useEffect只执行一次 - ✅ **独立刷新函数**:避免函数间的循环依赖 - ✅ **保持功能完整**:所有原有功能正常工作 - ✅ **性能提升**:从无限请求到智能缓存 **现在useShopInfo Hook工作正常,不再有无限循环问题!** 🚀