Skip to content

useAutoRefresh 在 refetch 身份不稳定时每渲染一次就重订阅一次(两条流都churn) #4694

Closed

Description

@os-zhuang

做 #4682(给 client-react 建测试基座)时,用刚建好的基座实测发现,unassigned。

现象

useAutoRefresh(object, refetch) 在 refetch 的函数身份每渲染变化时,每渲染一次就把两条订阅各拆一次、重开一次。

实测(fake client 记录每次 subscribeData / subscribeBulkData):

refetch 身份 3 次渲染后的订阅数(逐记录 / 批量)
内联箭头函数 () => {} 3 / 3
稳定引用(vi.fn() 提到外面) 1 / 1

成因

packages/client-react/src/realtime-hooks.tsx:

const handleEvent = useCallback((_e: DataEvent) => { refetch(); }, [refetch]);
const handleBulkEvent = useCallback((_e: BulkDataEvent) => { refetch(); }, [refetch]);

useDataSubscriptionCallback(object, handleEvent, options);
useBulkDataSubscriptionCallback(object, handleBulkEvent);

而 useDataSubscriptionCallback / useBulkDataSubscriptionCallback 的 effect 依赖数组里都含 callback。refetch 身份一变 → handleEvent 身份变 → effect 重跑 → 退订 + 重订。

为什么这不只是性能问题

  1. 退订与重订之间有窗口,该窗口内到达的事件直接丢失 —— 而 useAutoRefresh 的全部职责就是不漏掉数据变更;
  2. 真实 RealtimeAPI 的 subscribeData 会 startPolling(),unsubscribe 在订阅数归零时 stopPolling(),所以这是轮询定时器的反复起停,不是往 Map 里塞一项那么轻;
  3. 与 check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 叠加时更糟:useQuery 的 refetch 本身就是每渲染换身份的(它 memo 在同样不稳定的 fetchData 上),而 useAutoRefresh 的 TSDoc 示例正是 const { refetch } = useQuery(...) 配 useAutoRefresh(...)。两个缺陷是同一条调用链上的。

处置方向

把 callback 存进 ref,effect 只依赖 [client, object, options?.recordId],回调从 ref 里读。这是这类「订阅 + 回调」hook 的标准做法,也让 useDataSubscriptionCallback / useBulkDataSubscriptionCallback 对调用方是否记得 useCallback 免疫。

注意别顺手把 useDataSubscription / useBulkDataSubscription(返回 state 的那两个)的依赖也改了——它们不接收外部回调,当前依赖是正确的,#4682 已有测试钉住(改成依赖 options 对象身份会变红)。

修复须带回归测试:基座已就位(#4682 / PR #4692),断言形状即上表——内联回调渲染 3 次后订阅数仍应为 1。

优先级

比 #4690 低:本缺陷是 churn 与丢事件窗口,#4690 是无界请求循环。但两者同源(依赖数组用身份而非值),建议一并修。

Activity

  1. self-assigned this
    on Aug 2, 2026
  2. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    认领开工,与 #4693 同一分支处理(同源:依赖数组按身份而非按值,且在同一条调用链上)。

    • 会话 ID:session_01LYnZrTwXbrctB8E8HpJAPT
    • 分支:claude/issue-4693-hook-dep-identity

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions