EXAM-20260729-01:Anatomy 列表状态流
本次选题依据
这是一场基于历史能力画像的针对性复测,不是首次基线考试。
- 薄弱程度:
Refine Hook 返回值与 mutation 数据流最近 60%,JSX 信息完整性与异步分支最近 55%,异步 UI 状态与错误反馈最近 55%,可访问的条件渲染最近 50%。 - 重复错误:此前已出现 mutation 分支错误、异步状态顺序错误、状态/字段遗漏,以及跨三次考试的调试与占位内容遗留。
- 复习紧迫度:上述薄弱项的复习日期为 2026-07-31,距离本次考试两天;本场提前使用不同业务场景进行变式巩固。
- 项目相关性:Anatomy 列表真实使用
useDebounce、RefineuseList/useCreate/useCustomMutation、路由搜索参数与创建对话框,和当前项目核心前端数据流高度相关。
本场难度为中等。mutation 题因同类错误已出现过,提示不再给出可直接照抄的完整参数对象;新考点“防抖与 URL 双向同步”只占主题的一部分。不可变数组与表单 reset 已有较好基线,本场不再独立考查。
安全边界
- 本试卷只包含 Hook、页面组件、表单组件和路由适配层,不含 tRPC router、Service、DAO、Drizzle、数据库、服务端 API 或认证代码。
- i18n key、
t(...)写法和具体文案不计分;按钮可理解名称、错误反馈、加载状态和空状态仍计入行为与可访问性。 - 标准答案保存在 Git 基准提交中,试卷内没有可直接复制运行的完整实现。
文件顺序与数据流
| 顺序 | 文件 | 职责 | 题号 |
|---|---|---|---|
| 1 | use-debounce.ts | 把即时搜索文本转换为稳定搜索值 | 1 |
| 2 | AnatomiesCreateDialog.tsx | 收集、校验并向父级提交 Anatomy 草稿 | 2–3 |
| 3 | AnatomiesPageContent.tsx | 维护 URL/本地状态,查询列表,执行 mutation 并组合异步 UI | 4–8 |
| 4 | AnatomiesPage.tsx | 从路由读取参数并传给内容组件 | 9 |
数据流:Route search → AnatomiesPage → AnatomiesPageContent → useDebounce/useList → 列表 UI;创建方向为 AnatomiesCreateDialog → onCreate → useCreate → 详情路由。
源文件与 Git 基准
G:\Save\Grogramming\CodeForge\daedalus\apps\app\src\hooks\use-debounce.tsG:\Save\Grogramming\CodeForge\daedalus\apps\app\src\pages\AnatomiesPage\AnatomiesCreateDialog.tsxG:\Save\Grogramming\CodeForge\daedalus\apps\app\src\pages\AnatomiesPage\AnatomiesPageContent.tsxG:\Save\Grogramming\CodeForge\daedalus\apps\app\src\pages\AnatomiesPage\AnatomiesPage.tsx- Git 基准提交:
0e800f1d0b7ced0a105b0a8ae3df3acd76b84072
推荐答题顺序
严格按第 1 题到第 9 题完成。前一道题的产物会成为后续文件的已知条件。每题 10 分,总分 90 分;正式阅卷时换算为百分比。
第1题:实现可清理的泛型防抖 Hook
- 上下文:
AnatomiesPageContent把搜索框文本交给本 Hook,稳定后的值再同步到 URL。 - 目标:
value持续变化时,只在最后一次变化经过delay毫秒后发布新值。 - 已知:泛型
value、延迟delay、useState、useEffect。 - 产出:内部防抖状态、定时器副作用、清理函数和正确返回值。
- 路线:初始化内部状态;值或延迟变化时建立定时器;副作用失效时清理旧定时器。
- 边界:快速连续输入不能留下多个有效定时器;输入与输出泛型一致。
- 可用 API:
useState(value)、useEffect、setTimeout、clearTimeout;依赖数组包含value与delay。 - 自检:立即改值仍返回旧值;等待后返回新值;再次改值取消上一轮等待。
第2题:校验并提交 Anatomy 草稿
- 上下文:保存按钮调用本回调;合法草稿通过
onCreate交给父组件 mutation。 - 目标:拒绝空白名称;合法时修剪文本、创建草稿且只提交一次。
- 已知:
name、purpose、错误状态、createEmptyAnatomyDraft、onCreate、t。 - 产出:稳定的
handleSave回调。 - 路线:清旧错误;检查名称并在失败时早返回;创建草稿并提交。
- 边界:失败路径绝不能继续调用
onCreate;依赖数组不能遗漏读取值。 - 可用 API:
useCallback、trim()、createEmptyAnatomyDraft。 - 自检:空格名称提交 0 次;合法名称提交 1 次;重试会清旧错误。
第3题:组合可访问的创建对话框
- 上下文:父组件按 URL 或用户操作挂载本组件;第2题提供保存行为。
- 目标:渲染名称、用途、错误反馈、取消和创建操作,并完整绑定现有状态与回调。
- 已知:
Dialog、三项状态、样式常量和已有事件处理器。 - 产出:受控输入 JSX;错误存在时才渲染;按钮语义明确。
- 路线:组合 Dialog;绑定两个字段;条件渲染错误;连接两个操作按钮。
- 边界:按钮使用
type="button";错误具备 alert 语义;标签与输入可关联。 - 可用 API:
Dialog、label/input/textarea、role="alert"、htmlFor/id。 - 自检:输入能更新;无错误时无空 alert;取消关闭;创建进入第2题。
第4题:双向同步 URL、筛选状态与防抖搜索
- 上下文:第1题提供稳定搜索值;路由参数也可能因浏览器历史改变。
- 目标:外部参数回填本地状态;停止输入后把搜索值写回 URL 并重置页码。
- 已知:三个 Props、三个 setter、
debouncedSearch、searchParams、routeNavigate。 - 产出:三段外部同步副作用和一段防抖导航副作用。
- 路线:监听 Props;用 ref 跳过未变化值;修剪搜索词;必要时导航并重置页码。
- 边界:前进/后退必须回填;空白搜索从 URL 移除;同值不反复导航。
- 可用 API:
useEffect、useRef、routeNavigate。 - 自检:外部状态变化更新控件;快速输入只在稳定后导航;清空后 URL 无空字符串。
第5题:建立筛选条件与安全的 Refine 列表查询
- 上下文:第4题把 URL 作为共享状态,本题将它转成 Refine filters 并供第8题渲染。
- 目标:按搜索、状态和页码查询;无结果时向 UI 提供稳定空数组。
- 已知:
search、status、page、PAGE_SIZE、资源名和AnatomySummary。 - 产出:memo 化 filters、
result/query、anatomies、total、totalPages。 - 路线:只添加非空条件;配置资源/分页/filters;从可空结果派生安全值。
- 边界:不提交空筛选值;结果未返回时不访问
result.data;页数下界为 1。 - 可用 API:
useMemo、useList<AnatomySummary>、空数组回退、Math.max/ceil。 - 自检:无筛选时条件为空;切页改变查询页;加载早期数组安全;总数 0 时页数为 1。
第6题:完成创建与归档 mutation 生命周期
- 上下文:文件 2 交付创建草稿;列表行通过自定义 action 归档。
- 目标:创建成功后关闭并导航;归档成功后刷新当前列表。
- 已知:两个 mutate 函数、
router、query、资源名和归档 action。 - 产出:
handleCreate与handleArchive。 - 路线:创建请求传资源和值并安全读取成功 id;归档请求传 action/method/values;分别处理成功生命周期。
- 边界:请求前不假装成功;缺少 id 不导航;普通创建和自定义 mutation 参数形状不可混用。
- 可用 API:
useCreate、useCustomMutation的 mutate、onSuccess、refetch、navigate。 - 自检:创建失败弹窗仍开;成功只导航一次;归档成功刷新;归档未误用 resource 形状。
第7题:维护弹窗、详情导航与分页边界
- 上下文:第8题把回调连接到 Dialog、数据行、行内按钮与分页按钮。
- 目标:清理 create 参数;整行进详情但按钮不重复导航;分页与 URL 同步。
- 已知:本地状态、路由 API、
searchParams、totalPages。 - 产出:五个已有命名的交互/导航回调。
- 路线:关闭弹窗并按需清 URL;正确导航 Anatomy 详情;识别 button 后代;计算前后页并写 URL。
- 边界:不得复制成其他资源路由;归档按钮不能打开详情;页码不小于 1 且不越过总页数。
- 可用 API:
HTMLElement.closest、两个 navigate API、Math.max。 - 自检:关闭后 create 消失;点行进入正确 id;行内按钮只执行自身动作;分页不越界。
第8题:组合完整的 Anatomy 列表异步界面
- 上下文:第4–7题已经提供状态、查询、mutation、导航和分页,本题负责消费它们。
- 目标:渲染筛选栏、严格有序的异步状态、可操作列表、分页和创建弹窗。
- 已知:全部现有状态、派生值、回调、UI 组件、状态枚举和样式映射。
- 产出:页面 JSX;状态顺序为
loading → error → empty → data。 - 路线:连接筛选栏;顺序判断四种状态;映射带 key 的数据行;连接详情/归档;组合分页与弹窗。
- 边界:错误态不访问可空数据;首末页禁用;图标按钮有可访问名称;错误态有反馈和重试。
- 可用 API:
LoadingState、EmptyState、项目 UI 组件、query.isError、query.refetch。 - 自检:四种状态互斥;每行 key 唯一;归档不进详情;分页边界禁用;创建进入第6题。
第9题:用路由参数组合页面内容组件
- 上下文:本文件是路由与文件 3 的薄包装层,也是整套题的最终数据入口。
- 目标:读取搜索参数并用正确 Props 传给
AnatomiesPageContent。 - 已知:
Route.useSearch与内容组件接口。 - 产出:路由参数解构和内容组件 JSX。
- 路线:读取
search/status/page;把page映射到initialPage;保持本层无业务逻辑。 - 边界:不要错配 Props;不要新增查询或重复本地状态。
- 可用 API:
Route.useSearch()、JSX Props。 - 自检:URL 参数原样进入内容组件;无参数时允许 undefined;本文件无业务请求。
提交前自检
- 全局题号按 1–9 连续完成,未删除任何 import、类型、常量、export 签名或顶层
return。 - 固定检查异步 JSX 是否严格使用
loading → error → empty → data。 - 搜索并清除
console.log、废弃注释、空节点、临时占位和未使用 import。 - 检查列表 key、图标按钮可访问名称、错误 alert 和输入标签。
- 不要直接恢复整份 Git 答案;如需提示,保留疑问注释供阅卷时解答。
出题验证结果
- 出题前
git status --short为空,未覆盖用户未提交实现或未阅卷答卷。 - 4 个目标文件均由 Git 跟踪,标准答案可从基准提交获取。
- 题号标题全局连续为 1–9;每题均包含上下文、目标、已知、产出、路线、边界、API、自检和占位行。
- 当前文件的 import 与基准提交逐项一致;顶层类型、常量、公共 API 和 export 签名保留。
git diff --check通过,仅提示仓库现有的 LF/CRLF 转换策略。- 未运行全量类型检查、lint 或测试,因为挖空源码会按设计产生未使用项或未完成行为。