EXAM-20260730-01:Finding 反馈交互链路
本次选题依据
- 薄弱程度(主要依据):能力画像中“事件冒泡与复合交互边界”为 40%、“JSX 信息完整性与异步分支”为 60%,均低于 70%。
- 重复错误:最近一次答卷中行内按钮冒泡导致“归档并导航”同时发生;mutation payload 也再次出现把对象写成字符串的问题。
- 复习紧迫度:上述薄弱项下一次复习日为 2026-08-02,已进入最近复习窗口;本次用 Finding 反馈场景做变式复测,不复刻原题。
- 项目相关性:所选三个文件存在真实
Hook → 反馈按钮 → Rule 详情页调用关系,覆盖当前项目的 React Query、tRPC 客户端、Refine mutation 与可点击行交互。 - 新旧知识占比:事件边界、早返回、异步分支与 payload 边界属于历史复测;React Query 精确缓存失效是本次扩展,新知识不超过主题一半。
- 难度与提示强度:中等;重复错误项减少一层直接提示,但仍明确变量、API、数据流和自检行为。
文件顺序与数据流
use-finding-feedback.ts:封装单条反馈查询、反馈提交和缓存失效,向组件暴露feedback/isPending/error/submit。finding-feedback-buttons.tsx:消费 Hook,提供三种反馈按钮,并隔离父级行点击事件。RuleDetailPageContent.tsx:加载规则关联的 Findings,把每条记录组合成可展开行并嵌入反馈按钮。
数据流:RuleDetailPageContent Finding → FeedbackButtons Props → useFindingFeedback query/mutation → 缓存失效 → UI 高亮/列表刷新
推荐按第 1 → 8 题顺序作答。整卷只包含前端 Hook、组件和页面内容组件,不含 tRPC router、Service、DAO、数据库或服务端认证题目。
源文件与基准
- Git 基准提交:
4e03802a81989b0ae6da6ca775d9fa789a93e773 G:\Save\Grogramming\CodexWorkTree\ab1e\daedalus\apps\app\src\hooks\use-finding-feedback.tsG:\Save\Grogramming\CodexWorkTree\ab1e\daedalus\apps\app\src\components\finding-feedback-buttons.tsxG:\Save\Grogramming\CodexWorkTree\ab1e\daedalus\apps\app\src\pages\RuleDetailPage\RuleDetailPageContent.tsx
第1题(文件 1/3):读取单条 Finding 的反馈状态
- 上下文:
FeedbackButtons会消费本 Hook 返回的feedback,用它高亮当前选择。 - 目标:按
findingId查询反馈;没有记录或请求失败时,页面仍能以“未选择”状态继续渲染。 - 已知:
findingId来自父组件 Props;trpcClient已提供getFeedbackByFinding.query。 - 产出:声明
const { data },供文件末尾的feedback派生值使用。 - 实现路线:用
findingId组成稳定 queryKey;在 queryFn 调用接口并把失败收敛为null;空 id 时禁止查询。 - 边界:空
findingId不应请求;查不到或查询失败不能阻断按钮渲染。 - 可用 API:
useQuery、trpcClient.findings.getFeedbackByFinding.query、enabled。 - 自检:同一 id 复用缓存;切换 id 读取另一条记录;空 id 不请求。
第2题(文件 1/3):提交反馈并精确失效缓存
- 上下文:文件 2 的三个按钮会调用
submit;成功后按钮自身和上层 Findings 列表都要刷新。 - 目标:提交合法 outcome payload,暴露 pending/error,并在成功后刷新两级缓存与通知父级。
- 已知:
findingId、runId、queryClient、options;outcome 只允许accepted/rejected/ignored。 - 产出:声明
const { mutate, isPending, error },供 Hook 返回对象使用。 - 实现路线:mutationFn 组合三个 payload 字段;成功后依次失效单条反馈和 Findings 列表缓存;最后安全调用可选回调。
- 边界:不能只传 outcome 字符串;可选回调不存在时不能报错。
- 可用 API:
useMutation、queryClient.invalidateQueries、options?.onSuccess?.()。 - 自检:提交期间 pending;成功后高亮刷新;上层列表可感知变化。
第3题(文件 2/3):隔离行内按钮事件并防止重复提交
- 上下文:本组件放在文件 3 的可点击 Finding 行中;按钮点击不能继续冒泡触发行展开。
- 目标:三个回调分别提交对应 outcome,并在请求进行中忽略重复点击。
- 已知:
submit来自第2题;isPending表示请求进行中;事件为React.MouseEvent。 - 产出:完成
handleAccept、handleReject、handleIgnore三个稳定回调。 - 实现路线:先阻止冒泡;非 pending 时提交对应值;依赖数组覆盖外部读取值。
- 边界:pending 时仍需阻止冒泡;三个 outcome 不得错位。
- 可用 API:
event.stopPropagation()、submit、useCallback。 - 自检:按钮不展开/收起父行;快速连点不重复提交;三按钮值不同。
第4题(文件 2/3):把反馈按钮接到三个事件回调
- 上下文:第3题已建立事件边界;JSX 负责把三枚按钮接到正确回调。
- 目标:每个按钮提交与图标/文案一致的反馈,同时保留现有高亮与可访问名称。
- 已知:三个 handle 回调;现有
aria-label、className、type与子节点全部保留。 - 产出:为三枚
button分别补上正确点击属性。 - 实现路线:按语义逐一映射;不改 active class;传函数引用。
- 边界:不要写成立即调用表达式;不要交换 reject 与 ignore。
- 可用 API:
button的onClick。 - 自检:三个按钮各触发正确回调;当前 outcome 高亮仍正常;按钮名称可读。
第5题(文件 3/3):按规则身份加载 Findings
- 上下文:路由
id决定当前 Rule;结果进入页面底部异步列表并传给文件 2。 - 目标:规则变化时加载对应 Findings,请求结束后关闭 loading。
- 已知:
id、两个 setter、trpcClient.findings.listByRuleId.query。 - 产出:一个依赖规则 id 的
useEffect。 - 实现路线:请求前打开 loading;以
{ ruleId }查询并写入数组;无论成功失败都关闭 loading。 - 边界:依赖必须包含 id;payload 字段是
ruleId,不能传裸字符串。 - 可用 API:
useEffect、Promise.then、Promise.finally。 - 自检:首次进入显示加载;切换规则重新请求;失败后不会永久 loading。
第6题(文件 3/3):守住规则开关的 mutation 边界
- 上下文:页面右上角 Switch 调用回调;rule 尚未加载时不能构造更新请求。
- 目标:有 rule 时反转 enabled 并提交 Refine update;没有 rule 时立即结束。
- 已知:
rule、id、updateRule、ResourceName.rules。 - 产出:完成
handleToggleRule函数体,保持依赖完整。 - 实现路线:先做失败路径早返回;组装 resource/id/values;enabled 取反。
- 边界:早返回后不能继续 mutation;values 必须为对象;不原地修改 rule。
- 可用 API:
updateRule({ resource, id, values })、useCallback。 - 自检:加载前不请求;开关双向切换;其他字段不变。
第7题(文件 3/3):补齐 Findings 的异步三态骨架
- 上下文:第5题维护 loading 和数组;数据列表的内部 JSX 已完整保留。
- 目标:按 loading → empty → data 的互斥顺序渲染,避免加载时闪出空态。
- 已知:
findingsLoading、findings、Loader2、EmptyState与现成列表。 - 产出:把列表包进条件渲染,补加载节点和空状态。
- 实现路线:先判断 loading;再判断长度为 0;否则显示列表。
- 边界:加载中的空数组不能显示 EmptyState;分支互斥。
- 可用 API:三元表达式、
Loader2、EmptyState。 - 自检:请求中只见 spinner;完成且为空只见空态;有数据才见列表。
第8题(文件 3/3):组合可展开行与反馈子组件
- 上下文:整行点击负责展开详情;文件 2 的按钮负责提交反馈并阻止冒泡。
- 目标:恢复行点击展开,并在反馈区域传入当前 finding/run 身份。
- 已知:
f、handleToggleExpand、FeedbackButtons;findingId 需字符串,runId 可回退空串。 - 产出:行容器的
onClick,以及FeedbackButtons的两个身份 Props。 - 实现路线:行点击传
f.id;反馈组件收到两个 id;依赖第3题隔离事件。 - 边界:不要在渲染时立即调用回调;缺少 runId 时传安全字符串。
- 可用 API:
onClick、String(f.id)、空值合并运算符。 - 自检:点行展开;点反馈按钮不折叠;每行提交自己的 id。
出题验证结果
- Git 状态:出题前工作树干净;三个目标文件均已被 Git 跟踪。
- 依赖关系:已确认
RuleDetailPageContent导入FeedbackButtons,后者导入useFindingFeedback。 - 安全边界:未选择 tRPC router、Service、DAO、数据库、服务端路由或认证文件作为答题文件。
- 差异检查:imports、顶层类型/常量、export 名称与函数签名均保留;全局题号 1–8 连续且无重复。
- JSX 规则:保留列表与按钮的大部分既有结构,只移除三态外壳、事件绑定和子组件接线。
- 格式检查:
git diff --check无空白错误,仅有工作树既有的 LF/CRLF 提示。 - 质量检查:未运行全量 typecheck/lint/test,因为挖空变量与回调会按预期阻断静态检查。