From 101b31e0734114aa7d7335577042cec7bdb49306 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 8 Jul 2026 15:01:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(usercenter):=20=E6=B6=88=E9=99=A4=E5=88=B7?= =?UTF-8?q?=E6=96=B0"=E9=87=8D=E7=BB=98=E4=B8=A4=E6=AC=A1"=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E5=BC=95=E5=AF=BC=E6=9C=9F=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=A4=96=E5=A3=B3=E9=AA=A8=E6=9E=B6,?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99=E9=A6=96=E5=B8=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版为防水合不一致加了 !mounted 中性首帧,反而制造了额外一段:一次 Cmd+R 走 裸 spinner(mounted=false)→ 应用外壳(乐观)→ 内容区(overview),三段重绘 = 用户看到的"刷新两次(先主显示区、后整页)"。 改:去掉 mounted gate;引导期(!ready)一律渲染 appShell(spinner)——外壳骨架 (顶栏/导航就位、内容区加载态)。因 render 期不读 localStorage/hasRefresh,SSR 与 客户端首帧一致、无水合不一致,静态 HTML 首帧即外壳。引导完成后只把内容区从 spinner 换成真实视图,顶栏/导航原地不动 → 刷新只有内容区一次替换,不再整页重绘。 代价:未登录用户刷新 /user/ 会先见外壳骨架再落登录页(极短、且随即要登录), 可接受;已登录用户(刷新的绝大多数场景)全程外壳稳定、无闪。 Co-Authored-By: Claude Opus 4.8 --- web/usercenter/components/UserCenter.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/web/usercenter/components/UserCenter.tsx b/web/usercenter/components/UserCenter.tsx index 86c7bf7..7d8ba5f 100644 --- a/web/usercenter/components/UserCenter.tsx +++ b/web/usercenter/components/UserCenter.tsx @@ -45,16 +45,11 @@ export default function UserCenter() { const [authed, setAuthed] = useState(false); const [ready, setReady] = useState(false); - const [mounted, setMounted] = useState(false); const [me, setMe] = useState(null); const [view, setView] = useState('overview'); const [dir, setDir] = useState(0); const touchRef = useRef({ x: 0, y: 0 }); - // SSR/首帧标记:服务端与客户端首次渲染都读不到 localStorage,必须渲染同一份中性 - // 首帧(避免水合不一致)。mount 后再据 hasRefresh() 乐观渲染外壳。 - useEffect(() => setMounted(true), []); - const loadMe = useCallback(() => { api.getMe().then(setMe).catch(() => {}); }, [api]); @@ -207,12 +202,12 @@ export default function UserCenter() { const spinner =
{t('loading')}
; - // ① SSR + 首次客户端渲染:读不到 localStorage,渲染中性首帧(与服务端 HTML 一致,防 - // 水合不一致)。② mount 后引导仍在进行(!ready):有 refresh token → 乐观渲染完整外壳 - // (只在内容区转圈),没 token → 直接登录页(均无闪现)。③ 引导完成:未登录→登录页、 - // 已登录→外壳 + 真实视图。外壳在 ②③ 始终在位 → 刷新不再整页重绘。 - if (!mounted) return spinner; - if (!ready) return hasRefresh() ? appShell(spinner) : ; + // 引导(会话续期)期间一律渲染**应用外壳骨架**(顶栏/导航 + 内容区加载态)。关键: + // · SSR 与客户端首帧渲染同一份外壳(不在 render 期读 localStorage/hasRefresh)→ 无水合 + // 不一致,首帧(静态 HTML)即外壳。 + // · 引导完成后:已登录→外壳 + 真实视图(**只换内容区**,顶栏/导航原地不动),未登录→登录页。 + // 于是刷新时不再"裸屏 spinner → 外壳 → 内容"多段重绘,只有内容区一次替换。 + if (!ready) return appShell(spinner); if (!authed) return ; return appShell(main); }