fix(user): 优化用户页面余额积分等数据加载体验
- useRequest 改为 stale-while-revalidate 机制,缓存命中时立即展示数据并后台刷新 - 增加本地存储持久化,冷启动时先展示存储数据,避免骨架屏 - user 页面使用 mutate 结合本地缓存即时更新界面 - 网络请求失败时保留缓存数据,仅清除 loading 状态 - 优化加载逻辑,保障 30 秒缓存内数据可快速响应且自动刷新
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -217,16 +217,21 @@ export function useRequest<T = any, P extends any[] = any[]>(
|
||||
abortControllerRef.current?.abort()
|
||||
abortControllerRef.current = new SimpleAbortController()
|
||||
|
||||
// 检查缓存
|
||||
// 检查缓存 - stale-while-revalidate: 立即展示缓存数据,同时后台刷新
|
||||
let servedFromCache = false
|
||||
if (cacheKey) {
|
||||
const cached = getCache<T>(cacheKey)
|
||||
if (cached !== null) {
|
||||
setState({ data: cached, loading: false, error: null, initialized: true })
|
||||
return cached
|
||||
servedFromCache = true
|
||||
// 不 return,继续发网络请求静默刷新
|
||||
}
|
||||
}
|
||||
|
||||
// 无缓存时才显示 loading skeleton
|
||||
if (!servedFromCache) {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }))
|
||||
}
|
||||
|
||||
try {
|
||||
let result!: T
|
||||
@@ -268,7 +273,12 @@ export function useRequest<T = any, P extends any[] = any[]>(
|
||||
} catch (err: unknown) {
|
||||
if (!isMountedRef.current) return
|
||||
const error = err instanceof Error ? err : new Error(String(err))
|
||||
// 有缓存时网络失败不清除数据,仅清除 loading 状态
|
||||
if (!servedFromCache) {
|
||||
setState({ data: null, loading: false, error, initialized: true })
|
||||
} else {
|
||||
setState(prev => ({ ...prev, loading: false }))
|
||||
}
|
||||
onErrorRef.current?.(error, p)
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -22,23 +22,37 @@ const UserPage: React.FC = () => {
|
||||
const [userRoles, setUserRoles] = useState<UserRole[]>([])
|
||||
|
||||
// 获取用户卡片统计(余额/积分/优惠券/礼品卡)
|
||||
// 使用 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()
|
||||
// 刷新门店关联
|
||||
|
||||
Reference in New Issue
Block a user