From b07a3525dbb52fbab545e23e1be413cf177c63b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sat, 4 Jul 2026 15:34:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(user):=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=A1=B5=E9=9D=A2=E4=BD=99=E9=A2=9D=E7=A7=AF=E5=88=86?= =?UTF-8?q?=E7=AD=89=E6=95=B0=E6=8D=AE=E5=8A=A0=E8=BD=BD=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useRequest 改为 stale-while-revalidate 机制,缓存命中时立即展示数据并后台刷新 - 增加本地存储持久化,冷启动时先展示存储数据,避免骨架屏 - user 页面使用 mutate 结合本地缓存即时更新界面 - 网络请求失败时保留缓存数据,仅清除 loading 状态 - 优化加载逻辑,保障 30 秒缓存内数据可快速响应且自动刷新 --- .workbuddy/memory/2026-07-04.md | 23 +++++++++++++++++++++++ src/hooks/useRequest.ts | 18 ++++++++++++++---- src/pages/user/user.tsx | 33 ++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/.workbuddy/memory/2026-07-04.md b/.workbuddy/memory/2026-07-04.md index e4e738b..a6af165 100644 --- a/.workbuddy/memory/2026-07-04.md +++ b/.workbuddy/memory/2026-07-04.md @@ -167,3 +167,26 @@ - 点击商品跳转详情页,点击加购按钮调用 `addToCart` API - 复用现有 VIP 价格逻辑(`isVipMember` + `dealerPrice`) - 编译通过 + +### 15. 用户页余额/积分/优惠券/订单数加载优化 + +#### 问题分析 +- `useRequest` 的缓存是内存级 `Map`,冷启动丢失 → 永远显示骨架屏 +- 缓存命中后直接 `return` 不发起网络请求 → 30s 内数据不更新(注释说"静默刷新"但实际没做) +- 无 localStorage 持久化 → 每次打开页面都要等网络 + +#### 修复 useRequest 缓存为 stale-while-revalidate +- 修改 `src/hooks/useRequest.ts` 的 `doRequest` 方法 +- 缓存命中时:立即展示缓存数据(loading=false),**不 return**,继续发网络请求后台刷新 +- 无缓存时:正常显示 loading skeleton +- 网络失败时:有缓存则保留缓存数据不清除,仅清 loading 状态 + +#### 添加 localStorage 持久化 +- 修改 `src/pages/user/user.tsx` +- `useRequest` 增加 `onSuccess` 回调:请求成功后 `Taro.setStorageSync` 持久化 +- `useDidShow` 和 `useEffect(isLoggedIn)` 中:先读 `Taro.getStorageSync` 并用 `mutate` 即时展示,再调用 `run` 后台刷新 +- 效果:冷启动 0ms 展示上次数据 → 后台静默更新 + +#### 涉及文件 +- 修改:`src/hooks/useRequest.ts` +- 修改:`src/pages/user/user.tsx` diff --git a/src/hooks/useRequest.ts b/src/hooks/useRequest.ts index d3866b3..b941396 100644 --- a/src/hooks/useRequest.ts +++ b/src/hooks/useRequest.ts @@ -217,16 +217,21 @@ export function useRequest( abortControllerRef.current?.abort() abortControllerRef.current = new SimpleAbortController() - // 检查缓存 + // 检查缓存 - stale-while-revalidate: 立即展示缓存数据,同时后台刷新 + let servedFromCache = false if (cacheKey) { const cached = getCache(cacheKey) if (cached !== null) { setState({ data: cached, loading: false, error: null, initialized: true }) - return cached + servedFromCache = true + // 不 return,继续发网络请求静默刷新 } } - setState(prev => ({ ...prev, loading: true, error: null })) + // 无缓存时才显示 loading skeleton + if (!servedFromCache) { + setState(prev => ({ ...prev, loading: true, error: null })) + } try { let result!: T @@ -268,7 +273,12 @@ export function useRequest( } catch (err: unknown) { if (!isMountedRef.current) return const error = err instanceof Error ? err : new Error(String(err)) - setState({ data: null, loading: false, error, initialized: true }) + // 有缓存时网络失败不清除数据,仅清除 loading 状态 + if (!servedFromCache) { + setState({ data: null, loading: false, error, initialized: true }) + } else { + setState(prev => ({ ...prev, loading: false })) + } onErrorRef.current?.(error, p) return undefined } diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index 7ebff8b..e5afc15 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -22,23 +22,37 @@ const UserPage: React.FC = () => { const [userRoles, setUserRoles] = useState([]) // 获取用户卡片统计(余额/积分/优惠券/礼品卡) - // 使用 cacheKey 缓存 30s,页面返回时先显示缓存数据再静默刷新 - const { data: cardStats, run: runCardStats, loading: cardStatsLoading } = useRequest(getUserCardStats, { + // stale-while-revalidate: 缓存命中即时展示 + 后台静默刷新 + // localStorage 持久化: 冷启动先读 storage 即时展示,避免骨架屏 + const { data: cardStats, run: runCardStats, loading: cardStatsLoading, mutate: mutateCardStats } = useRequest(getUserCardStats, { manual: true, cacheKey: 'user_card_stats', cacheTime: 30 * 1000, + onSuccess: (data) => { + // 请求成功后持久化到本地存储,下次冷启动可直接展示 + Taro.setStorageSync('user_card_stats_v2', data) + }, }) - // 获取用户订单统计(缓存 30s,先缓存后静默刷新) - const { data: orderStats, run: runOrderStats, loading: orderStatsLoading } = useRequest(getUserOrderStats, { + // 获取用户订单统计(同上 stale-while-revalidate + localStorage 持久化) + const { data: orderStats, run: runOrderStats, loading: orderStatsLoading, mutate: mutateOrderStats } = useRequest(getUserOrderStats, { manual: true, cacheKey: 'user_order_stats', cacheTime: 30 * 1000, + onSuccess: (data) => { + Taro.setStorageSync('user_order_stats_v2', data) + }, }) // 登录后加载数据 useEffect(() => { if (isLoggedIn) { + // 冷启动优化:先从 localStorage 读取缓存即时展示,消除骨架屏 + const cachedCardStats = Taro.getStorageSync('user_card_stats_v2') + if (cachedCardStats) mutateCardStats(cachedCardStats) + const cachedOrderStats = Taro.getStorageSync('user_order_stats_v2') + if (cachedOrderStats) mutateOrderStats(cachedOrderStats) + // 然后发起网络请求(stale-while-revalidate 后台刷新) runCardStats() runOrderStats() // 查询门店关联 @@ -58,7 +72,16 @@ const UserPage: React.FC = () => { // 先从 storage 快速同步,立即更新 UI(无网络延迟) const hasUser = syncFromStorage() if (hasUser) { - // 使用缓存:30s 内重复返回页面直接展示缓存数据,无需重新请求 + // 冷启动优化:先从 localStorage 读取上次缓存的数据即时展示,消除骨架屏 + const cachedCardStats = Taro.getStorageSync('user_card_stats_v2') + if (cachedCardStats) { + mutateCardStats(cachedCardStats) + } + const cachedOrderStats = Taro.getStorageSync('user_order_stats_v2') + if (cachedOrderStats) { + mutateOrderStats(cachedOrderStats) + } + // 然后发起网络请求(stale-while-revalidate 自动在后台刷新) runCardStats() runOrderStats() // 刷新门店关联