forked from gxwebsoft/mp-10550
- 新增优惠券卡片对齐修复文档 - 新增优惠券状态显示调试文档 - 新增优惠券组件警告修复文档- 更新用ShopInfo Hook字段迁移文档 - 更新Arguments关键字修复文档
255 lines
6.1 KiB
Markdown
255 lines
6.1 KiB
Markdown
# 🚨 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工作正常,不再有无限循环问题!** 🚀
|