跳转至

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

相关问题