title: [ISSUE-0017] Popup 2s timeout 与 uid 到达的竞态 description: "Popup 2s timeout 与 uid 到达的竞态(观察项)" tags: [issue, auth] created: 2026-05-26 updated: 2026-05-26 type: issue status: fixed severity: minor
[ISSUE-0017] Popup 2s timeout 与 uid 到达的竞态¶
相关源码:
src/sections/popup/index.tsx发现途径:v0.10.19 代码审查
用户感知的现象¶
理论场景:
- 网络状况"刚好"在 1.95s 拉到 user
- 2s timeout 即将 fire
- React 18 batch 顺序下,可能短暂闪现 stale 然后变 logged-in
实际: - 用户基本看不到(< 50ms) - 即使闪现,最终状态对,不影响功能
根因分析¶
useEffect(() => {
storage.getItem('local:uid').then((storedUid) => {
if (!storedUid) { setAuthState('logged-out'); return; }
timeoutId = setTimeout(() => {
setAuthState((s) => (s === 'loading' ? 'stale' : s)); // ← 函数式守住了
}, 2000);
});
}, []);
useEffect(() => {
if (uid) setAuthState('logged-in');
}, [uid]);
第二个 effect 没有 clear 第一个 effect 的 timeout。理论上: 1. t=1.95s: uid 到达 → setAuthState('logged-in') 2. t=2.00s: timeout fire → setAuthState((s) => s === 'loading' ? 'stale' : s) 3. 因为 s 已经是 'logged-in',函数式守卫返回 s(不变)
正确结果。但 React 18 在严格模式下 / 某些 batch 顺序下,timeout 回调和 effect 之间的时序可能让 s 短暂仍是 'loading',然后被 set 为 'stale',下次 render 又被 effect 2 改回 'logged-in'。理论闪现 < 16ms(一帧),人眼不可见。
不修的理由¶
- 闪现 < 16ms,用户体感 0
- 最终状态正确,没有功能影响
- 修法(用 ref / clear timeout)增加复杂度,收益微小
如未来发现真的可见闪现 / 报错,再修:
useEffect(() => {
if (uid && timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (uid) setAuthState('logged-in');
}, [uid]);
如何避免再犯¶
- 状态机 effect 之间互相依赖时,timeout/interval 必须有 ref 持有以便清除
- 「函数式 setState 守卫」是兜底但不是优雅 fix:能 clear 就 clear
相关问题¶
- ISSUE-0002 — 同状态机