做 #4682(给 client-react 建测试基座)时,用刚建好的基座实测发现,unassigned。 ## 现象 `useQuery(object, options)` 在 **options 里带任何对象/数组/函数字段** 时会进入无界重取循环——而这正是它自己 TSDoc 示例的写法。 实测(jsdom + @testing-library/react,fake client 记 `data.find` 调用次数,渲染后静置 300ms): | 用法 | 300ms 内 `find` 调用次数 | |:---|---:| | `useQuery('todo_task', { where: { status: 'open' } })` —— **内联 options** | **8830** | | `const OPTS = {...}` 提升到组件外,`useQuery('todo_task', OPTS)` | **1** | 不是「多取几次」,是以事件循环的速度**永远打下去**。 ## 成因 `packages/client-react/src/data-hooks.tsx`: ```ts // L116 const fetchData = useCallback(async (isRefetch = false) => { … }, [client, object, query, resolvedFields, resolvedWhere, resolvedSort, resolvedLimit, resolvedOffset, enabled, onSuccess, onError]); // L156 useEffect(() => { fetchData(); }, [fetchData]); ``` `resolvedWhere` / `resolvedFields` / `resolvedSort` 直接来自 `options` 的解构,`onSuccess` / `onError` 同理。调用方内联传入 → 每次渲染都是新身份 → `fetchData` 每渲染换身份 → L156 的 effect 每渲染重跑 → `setData` → 重渲染 → 闭环,无出口。 `useQuery('todo_task', {})`(全部字段 `undefined`,都是原始值)不受影响——所以它一直没被发现。触发条件是传 `where` / `fields` / `orderBy` / `onSuccess` / `onError` 中的任意一个,也就是绝大多数真实用法。 同一形状的 `useCallback` 依赖链在 `data-hooks.tsx:592` 和 `metadata-hooks.tsx:136` 也存在,需要一并核查。 ## 影响 - 客户端把服务端当压测目标打; - 组件永远处于 loading/refetching 抖动; - 是**运行时**缺陷,`tsc` 完全看不见 —— 这正是 #4682 存在的理由。 ## 处置方向 不要用「让调用方自己 `useMemo`」当修复——文档示例就是内联写法,把正确性押在调用方的记忆力上等于没修。 按值而非按身份做依赖。可选:对 `where`/`fields`/`orderBy` 做稳定序列化(如 `JSON.stringify` 或结构化哈希)作为依赖;`onSuccess`/`onError` 存进 ref(它们是副作用回调,本就不该参与重取决策)。 修复必须**带回归测试**,基座已经就位(#4682 / PR #4692),断言直接用上表那两个数字的形状(内联 options 稳定后 `find` 只该被调用 1 次)。 ## 复现 基座合入后,`packages/client-react/` 下: ```tsx const find = vi.fn(async () => ({ data: [], total: 0 })); const client = { data: { find, query: find }, events: {}, setLocale: vi.fn() } as unknown as ObjectStackClient; function C() { useQuery('todo_task', { where: { status: 'open' } }); return null; } render(<ObjectStackProvider client={client}><C /></ObjectStackProvider>); await new Promise(r => setTimeout(r, 300)); expect(find.mock.calls.length).toBe(1); // 实测 8830 ```
Activity
os-zhuang commentedon Aug 2, 2026
认领开工。维护者批准与 #4694 一并修(同源:依赖数组按身份而非按值)。
session_01LYnZrTwXbrctB8E8HpJAPTclaude/issue-4693-hook-dep-identity(同时覆盖 useAutoRefresh 在 refetch 身份不稳定时每渲染一次就重订阅一次(两条流都churn) #4694)依赖 #4682 的测试基座(PR #4692)才能带回归测试,所以本分支基于该分支起,待其落地后并入 main。
Generated by Claude Code
fix(client-react): stop five hooks from looping on dependency identity (