Communitygithub.com

jaimegago/agent-skills

在调试过程中,当React UI未能反映已知正确的数据源时使用——即使API返回了正确响应,组件仍渲染为空或陈旧内容,react-query/数据获取钩子似乎卡住,或网络面板显示成功的200但UI从未更新。用于诊断此运行时/时序行为,通过检查实时状态,区别于构建或样式化UI。不适用于构建/编译错误、纯样式问题,或API确实返回错误/缺失数据的情况。

agent-skills 是什么?

该技能专注于解决前端运行时调试中的典型陷阱:数据在传输层面正确,但UI渲染异常。它要求开发者仅在确认后端/API返回了正确响应后,且问题表现为组件空白、陈旧或未更新时调用。技能的核心在于“先检查实时运行时状态,再重读源代码”,因为此类Bug通常源于运行时/时序交互(如查询被中途废弃、缓存失效、组件重挂载竞争等),而非单个源文件错误。它支持React及react-query(@tanstack/react-query v5.90+)应用,通过遍历React fiber树从运行页面中提取组件的查询观察器(QueryObserver),读取live状态(status、fetchStatus、dataUpdateCount),并可通过强制refetch来证明查询函数(queryFn)本身完好。该技能还提供了具体的决策路径和JavaScript代码片段,用于快速定位bug是出现在“数据路径断裂”还是“查询在中途被废弃”的分叉点上,并指导进一步检查上游生命周期/缓存管理代码。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jaimegago/agent-skills/tree/HEAD/frontend-runtime-debugging

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

前端运行时调试:在重读源代码之前检查实时状态

数据在传输层面是正确的,但UI是错误的。这是一个运行时/时序Bug,而会浪费你时间的本能是重读源文件。抵制这种本能。重新调整反应顺序。

使用时机

在调试过程中,当症状出现时调用——而不是在任务开始时。当以下所有条件都成立时使用:

  • React组件渲染为空、陈旧或空白内容。
  • 组件所需的数据在源端已知是正确的(API/后端返回了正确的响应)。
  • 问题存在于“传输层上的正确响应”与“屏幕上的正确渲染”之间的某个环节。

典型表述:“API返回了正确的数据但组件是空白的”,“react-query卡住了”,“请求是200但UI从未更新”,“刷新后工作正常但首次加载不行”。

这不适用于什么

  • 构建/编译错误——那是工具链Bug,而非运行时Bug。
  • 样式/布局问题——数据渲染了,只是看起来不对。
  • API确实返回了错误或无数据——那是常规的后端/契约Bug。请先验证传输层响应;如果响应实际上错误或为空,请停止并修复后端/契约。此技能仅适用于传输层正确但UI错误的情况。

重新调整(关键要点)

  1. 在重读源代码之前先检查实时运行时状态。 本能反应是重读组件、模式、布局和提供者封装代码以寻找Bug。当数据在传输层正确但UI错误时,这种本能是一个陷阱:Bug通常不在任何单个源文件中,而是在各个单独正确的部分之间的运行时/时序交互中。源文件审查在结构上对此类Bug是盲目的。请先检查实时状态。

  2. 网络面板中的200可能具有误导性。 显示200的请求意味着响应已到达。但这并不表示数据已到达组件或已提交到缓存。响应可能到达后被丢弃、移除或从未提交。不要仅凭200就得出结论“请求正常”——请确认数据确实到达了消费组件的状态。

  3. 对于react-query,请检查受影响组件的实时查询观察器(query observer),而不仅仅是网络。 读取观察器的status、fetchStatus、dataUpdateCount以及error。

    • 查询卡在status: "pending" / fetchStatus: "fetching"且dataUpdateCount: 0意味着queryFn promise的结果从未提交给观察器。这表明查询在途中被移除或无效化——而不是请求失败。
    • 通过强制观察器重新请求(refetch)来确认queryFn本身是否健康。如果手动重新请求立即正确解决,则请求/客户端/模式都正常,真正的Bug是初始查询被搁置了(由缓存清除移除、由副作用无效化或由重新挂载拆除)。这一单独的观察就能区分“数据路径损坏”和“数据路径正常但查询被终止”——这是完全不同的Bug。
  4. 一旦定位到“查询在中途被搁置”,请向上游检查生命周期/缓存管理。 原因是在查询启动的同一时刻运行了某些操作:removeQueries / invalidateQueries / 缓存重置,或者挂载/卸载/重新挂载竞争。请检查认证/会话转换副作用、主体/租户切换清除以及提供者重新挂载——而不是请求代码。

决策路径

UI错误,数据源正确?
  → 确认传输层响应确实正确(如果不是,非此技能职责)
  → 读取受影响组件的实时查询观察器
       status pending + fetchStatus fetching + dataUpdateCount 0?
          → 对该观察器强制重新请求
               refetch立即正确解决?
                  → queryFn正常。Bug = 初始查询被搁置。
                    向上游查找:removeQueries/invalidateQueries/reset,
                    重新挂载/卸载竞争,认证/会话/主体清除副作用。
               refetch也失败?
                  → 数据路径真正损坏。检查queryFn/客户端/模式。
       有数据但UI仍然空白?
          → 组件中的渲染/选择器/条件Bug,现在可以重读源代码。

实时检查工具(参考性API,非稳定接口)

这些代码片段从正在运行的页面中,通过从渲染的DOM元素遍历React fiber树,获取组件的react-query观察器,读取观察器的当前查询状态,并强制重新请求以证明queryFn工作正常。

这些代码片段对版本敏感。 它们基于@tanstack/react-query v5.90和React 18 fiber内部实现推导得来,依赖未文档化的内部API,并且会漂移。请将它们视为需要适配的起点——在实际信任任何结果之前,请重新验证项目已安装版本中的字段名和fiber结构。它们是参考性方法,而非保证稳定的API。

从DOM节点遍历fiber树以找到钩子/观察器:

// 选择一个由受影响组件渲染的DOM元素(例如通过开发工具中的$0)。
const el = $0; // 或者 document.querySelector('[data-testid="..."]')
const key = Object.keys(el).find(k => k.startsWith('__reactFiber$'));
let fiber = el[key];

// 向上/向下遍历fiber树;memoizedState是一个钩子链表。
// react-query将其观察器存储在某个钩子的memoizedState上。
function* fibers(root) {
  const stack = [root];
  while (stack.length) {
    const f = stack.pop();
    if (!f) continue;
    yield f;
    if (f.child) stack.push(f.child);
    if (f.sibling) stack.push(f.sibling);
  }
}

// 扫描钩子状态以查找类似QueryObserver的对象。
function findObservers(fiber) {
  const found = [];
  for (const f of fibers(fiber)) {
    let hook = f.memoizedState;
    while (hook) {
      const s = hook.memoizedState;
      if (s && typeof s === 'object') {
        // QueryObserver实例暴露getCurrentResult()/getCurrentQuery()方法。
        if (typeof s.getCurrentResult === 'function') found.push(s);
        // 某些版本将其嵌套;同时检查常见的包裹器。
        if (s.observer && typeof s.observer.getCurrentResult === 'function') found.push(s.observer);
      }
      hook = hook.next;
    }
  }
  return found;
}

const observers = findObservers(fiber);

读取观察器的实时查询状态:

const obs = observers[0];
const r = obs.getCurrentResult();
console.log({
  status: r.status,            // 'pending' | 'error' | 'success'
  fetchStatus: r.fetchStatus,  // 'fetching' | 'paused' | 'idle'
  dataUpdateCount: obs.getCurrentQuery().state.dataUpdateCount,
  error: r.error,
});
// pending + fetching + dataUpdateCount 0  ==>  查询在中途被搁置,而非请求失败。

强制重新请求以证明queryFn健康:

obs.refetch().then(r => console.log('refetch:', r.status, r.data)).catch(e => console.log('refetch failed:', e));
// 立即正确解决  ==>  queryFn/客户端/模式正常;
//                       原始查询被上游终止了。

如果项目暴露了QueryClient(例如在window上用于调试,或可从提供者fiber访问),优先使用queryClient.getQueryCache().findAll()并直接检查每个查询的state和observers——这比遍历fiber树更稳健(当可用时)。

完成

当你定位到Bug后:

  1. 说明它落入了决策路径的哪一侧:“数据路径损坏”还是“查询被搁置”。
  2. 如果是被搁置,指出是哪个上游生命周期/缓存操作导致了它。
  3. 然后才重读负责的具体源文件——现在你知道是哪一个了。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能