fix(usercenter): 消除刷新"重绘两次" —— 引导期直接渲染外壳骨架,去掉多余首帧
上一版为防水合不一致加了 !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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Me | null>(null);
|
||||
const [view, setView] = useState<View>('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 = <div style={{ minHeight: '40vh', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg3)', fontSize: 14 }}>{t('loading')}</div>;
|
||||
|
||||
// ① SSR + 首次客户端渲染:读不到 localStorage,渲染中性首帧(与服务端 HTML 一致,防
|
||||
// 水合不一致)。② mount 后引导仍在进行(!ready):有 refresh token → 乐观渲染完整外壳
|
||||
// (只在内容区转圈),没 token → 直接登录页(均无闪现)。③ 引导完成:未登录→登录页、
|
||||
// 已登录→外壳 + 真实视图。外壳在 ②③ 始终在位 → 刷新不再整页重绘。
|
||||
if (!mounted) return spinner;
|
||||
if (!ready) return hasRefresh() ? appShell(spinner) : <Login onDone={onLoginDone} />;
|
||||
// 引导(会话续期)期间一律渲染**应用外壳骨架**(顶栏/导航 + 内容区加载态)。关键:
|
||||
// · SSR 与客户端首帧渲染同一份外壳(不在 render 期读 localStorage/hasRefresh)→ 无水合
|
||||
// 不一致,首帧(静态 HTML)即外壳。
|
||||
// · 引导完成后:已登录→外壳 + 真实视图(**只换内容区**,顶栏/导航原地不动),未登录→登录页。
|
||||
// 于是刷新时不再"裸屏 spinner → 外壳 → 内容"多段重绘,只有内容区一次替换。
|
||||
if (!ready) return appShell(spinner);
|
||||
if (!authed) return <Login onDone={onLoginDone} />;
|
||||
return appShell(main);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user