DrillLab
练习Practice

动手做Get your hands on it

练习跟着课文走 —— 每节课尾都有本课的练习。这一页是全部练习的总库,想集中刷题的时候来。 每个练习都写清了它来自哪一节,卡住了就回去看那一节。Practice follows the lessons — every lesson ends with the exercises for that lesson. This page is the whole library, for when you want to drill in one sitting. Each exercise names the lesson it came from, so you can go back when you stall.

0 / 148个做对过you got right

练习Exercises

筛出 40 个练习(共 148 个) · 第 1 / 4 页。Showing 40 of 148 · page 1 / 4.
来自From useState:让界面跟着数据变useState: making the screen follow the data · React 考试React exam
L3写整块Write a block自己写出 NoteManager 的两个 state 和删除逻辑Write the two states of NoteManager and the delete logic yourself

只给你组件外壳。按要求补出两个 state 和 handleDelete。 不要看下面的答案,先自己写。

You get only the shell of the component. Add the two states and handleDelete as described. Write it yourself before you look at the answer below.

要求Requirements
  • 用 useState 声明 notes,类型是 Note[],初始值为空数组Declare notes with useState, typed Note[], starting as an empty array
  • 用 useState 声明 noteToEdit,类型是 Note | null,初始值为 nullDeclare noteToEdit with useState, typed Note | null, starting as null
  • handleDelete 按 id 移除对应笔记,必须用函数式更新,不许改动原数组handleDelete removes the matching note by id, using a functional update, without changing the original array
TSXsrc/components/NoteManager/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From useEffect:把 props 的变化同步进 stateuseEffect: copying a change in props into state · React 考试React exam
L3Debug LabDebug LabDebug Lab · 点 Edit 之后页面卡死Debug Lab · the page freezes after you press Edit

点某一行的 Edit 按钮,浏览器标签页转圈,控制台刷出大量警告。 请判断类型并定位。

Press the Edit button on any row. The browser tab spins and the console fills up with warnings. Classify the error, then locate it.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render. at NoteForm (src/components/NoteForm/index.tsx:13:3) at NoteManager (src/components/NoteManager/index.tsx:6:3) Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
TSX有问题的依赖数组The broken dependency array示意Illustrative
1useEffect(() => {
2 if (noteToEdit) {
3 setTitle(noteToEdit.title);
4 setContent(noteToEdit.content);
5 } else {
6 setTitle("");
7 setContent("");
8 }
9}, [noteToEdit, title, content]);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 派生数据与状态提升:什么不该做成 stateValues you can compute, and lifting state up: what should not be state · React 考试React exam
L3写整块Write a block写出派生数据与按钮文字Write the computed value and the button text

补出 isFormInvalid 和按钮的两处动态部分。 注意:只输入空格也应该算无效。

Fill in isFormInvalid and the two changing parts of the button. Note: spaces only should also count as invalid.

要求Requirements
  • isFormInvalid 是一个普通 const,不许用 useStateisFormInvalid is a plain const; useState is not allowed
  • 只输入空格也要判定为无效(用 trim)Spaces only must also count as invalid (use trim)
  • 按钮在表单无效时 disabledThe button is disabled while the form is invalid
  • 按钮文字:noteToEdit 存在时是 Update,否则是 Add(大小写必须一致)Button text: Update when noteToEdit exists, otherwise Add (the capitals must match)
  • 不许改动 data-testidDo not change any data-testid
TSXsrc/components/NoteForm/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From Task 1 · Add:提交表单,新笔记进入表格Task 1 · Add: submit the form and the new note appears in the table · React 考试React exam
L3写整块Write a block不看答案,自己写出 Task 1Write Task 1 yourself, without looking at the answer

只给你函数签名和要求。自己写完整实现。 写完点「检查我的代码」,它会用文本规则检查你有没有踩坑。

You get the function signature and the requirements. Write the whole implementation yourself. When you are done, use the check button: it applies text rules to see whether you fell into a known trap.

要求Requirements
  • 把 submittedNote 追加到 notes 的末尾Append submittedNote to the end of notes
  • 原有的笔记全部保留Keep every note that was already there
  • 必须用函数式更新 setNotes(prev => ...)Use a functional update: setNotes(prev => ...)
  • 不许用 push / splice / unshift 修改原数组Do not change the original array with push / splice / unshift
  • 留出 if (noteToEdit) 分支的位置(Task 3 会填)Leave room for the if (noteToEdit) branch (Task 3 fills it in)
TSXsrc/components/NoteManager/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From Task 2 · Delete:点 Delete,该行按 id 被移除Task 2 · Delete: click Delete and that one row is removed by id · React 考试React exam
L3写整块Write a block不看答案,自己写出 Task 2Write Task 2 yourself, without looking at the answer

一行代码的题。但要一次写对,不许用 push / splice。

One line of code. But get it right the first time, and without push or splice.

要求Requirements
  • 按 id 移除对应的那一条Remove the matching note by id
  • 其余笔记全部保留,顺序不变Keep every other note, in the same order
  • 必须用函数式更新Use a functional update
  • 不许修改原数组(不许用 splice)Do not change the original array (no splice)
  • 不许按 title 或下标比较Do not compare by title or by index
TSXsrc/components/NoteManager/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From Task 3 · Edit:回填、改文字、就地更新、退出编辑Task 3 · Edit: refill the form, change the button text, update the row where it is, leave edit mode · React 考试React exam
L3写整块Write a block不看答案,自己写出完整的 Task 3Write all of Task 3 yourself, without looking at the answer

handleEdithandleSubmitNote两个函数完整写出来(含 Task 1 的分支)。 这是 Q1 的完整答案,写对了这道题就通了。

Write both handleEdit and handleSubmitNote in full, including the Task 1 branch. This is the complete answer to Q1: get it right and the question is done.

要求Requirements
  • handleEdit:把这条笔记设为「正在编辑」,不要改动 noteshandleEdit: mark this note as the one being edited, and leave notes alone
  • handleSubmitNote 编辑分支:按 id 就地替换,位置和顺序不变handleSubmitNote, edit branch: replace by id in place, keeping the position and the order
  • handleSubmitNote 编辑分支:替换完要退出编辑模式handleSubmitNote, edit branch: leave edit mode once the replace is done
  • handleSubmitNote 新增分支:追加到末尾handleSubmitNote, add branch: append to the end
  • 全部使用函数式更新,不许修改原数组Use functional updates everywhere, and never change the original array
TSXsrc/components/NoteManager/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From Task 3 · Edit:回填、改文字、就地更新、退出编辑Task 3 · Edit: refill the form, change the button text, update the row where it is, leave edit mode · React 考试React exam
L3Debug LabDebug LabDebug Lab · 点 Update 之后毫无反应Debug Lab · nothing happens after you click Update

点 Edit,输入框正常回填,按钮变成 Update。 改完内容点 Update —— 列表一点变化都没有, 表单也没清空。控制台干净。

Click Edit and the inputs prefill correctly, and the button becomes Update. Change the content and click Update — the list does not change at all, and the form does not clear either. The console is clean.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。 # 复现: # 1. 添加 "Old / c1" # 2. 点 Edit → 输入框显示 Old / c1,按钮变 Update ✓ 这两步正常 # 3. 把标题改成 "New",点 Update # 期望:列表里那条变成 New,表单清空,按钮回到 Add # 实际:列表还是 Old,表单还留着 New,按钮还是 Update # 测试结果: # ✓ adds a note # ✓ submit button disabled when inputs empty # ✓ deletes a note # ✕ edits a note in place # Unable to find text content "New" in element [data-testid="notes-list"]# No error at all. # Repro: # 1. Add "Old / c1" # 2. Click Edit → the inputs show Old / c1, the button reads Update ✓ both fine # 3. Change the title to "New" and click Update # Expected: that row becomes New, the form clears, the button goes back to Add # Actual: the row is still Old, the form still holds New, the button still reads Update # Test results: # ✓ adds a note # ✓ submit button disabled when inputs empty # ✓ deletes a note # ✕ edits a note in place # Unable to find text content "New" in element [data-testid="notes-list"]
TSX有问题的 handleSubmitNoteThe handleSubmitNote with the bug示意Illustrative
1const handleSubmitNote = (submittedNote: Note) => {
2 const note = { ...submittedNote, id: Date.now() };
3
4 if (noteToEdit) {
5 setNotes((prev) =>
6 prev.map((n) => (n.id === note.id ? note : n)),
7 );
8 setNoteToEdit(null);
9 } else {
10 setNotes((prev) => [...prev, note]);
11 }
12};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 四个测试逐条读,以及它们的盲区The four tests read line by line, and what they fail to catch · React 考试React exam
L3写整块Write a block自己补一个测试,覆盖「按 id 删除」这个盲区Write a test of your own to cover the delete-by-id blind spotDrillLab 自出Written by DrillLab

现有测试测不出「按 id 删除」。写一个新测试: 添加两条同名笔记,删掉其中一条, 断言另一条还在。
提示:两条数据时页面上有两个 Delete 按钮,getByRole 会因为「找到多个」而抛错 —— 得用 getAllByRole

The existing tests cannot check the delete by id. Write a new test: add two notes with the same title, delete one of them, and assert that the other is still there.
A note: with two notes there are two Delete buttons on the page, and getByRole throws because it found more than one. Use getAllByRole.

要求Requirements
  • 添加两条 title 完全相同、content 不同的笔记Add two notes with exactly the same title and different content
  • 用 getAllByRole 拿到 Delete 按钮数组,点第一个Use getAllByRole to get the array of Delete buttons, then click the first one
  • 断言 notes-list 不再含「内容A」Assert that notes-list no longer holds 内容A
  • 断言 notes-list 仍然含「内容B」Assert that notes-list still holds 内容B
  • 所有 userEvent 调用都要 awaitPut an await on every userEvent call
TSXsrc/NoteManager.test.tsx(自己加的测试)
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 实现:worker pool(工人池)Building it: a worker pool, meaning a fixed number of workers sharing one queue · React 考试React exam
L3写整块Write a block从签名开始,自己写出整个 runTasksStart from the signature and write all of runTasks yourself

只给签名。这是 Q2 的完整答案,写对了这道题就通了。 写完可以在本机 npm run q2 验证。

You get only the signature. This is the complete answer to Q2: get it right and the question is done. When you finish you can check it here with npm run q2.

要求Requirements
  • 同一时刻最多 limit 个任务在运行At most limit tasks are running at the same time
  • 某个任务结束后,立刻启动下一个(不是等一批都结束)As soon as one task finishes, start the next one (do not wait for a whole batch)
  • 任何任务失败都不能让整体抛错No failing task may make the whole call throw
  • 返回数组的顺序必须与 tasks 一致The order of the returned array must match tasks
  • 成功写 { status: "fulfilled", value },失败写 { status: "rejected", reason }On success write { status: "fulfilled", value }; on failure write { status: "rejected", reason }
TypeScriptq2/taskRunner.ts
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 变式一 · Todo ListVariation 1 · Todo List · React 考试React exam
L3写整块Write a block自己写出筛选与「清除已完成」Write the filtering and the clear-completed action yourselfDrillLab 自出Written by DrillLab

已有 todosfilter 两个 state。 写出可见列表和「清除已完成」,注意筛选态下的写操作该作用于谁。

You already have the two states todos and filter. Write the visible list and the clear-completed action, and think about which list a write should act on while a filter is on.

要求Requirements
  • visible 是派生数据,不许用 useState 或 useEffectvisible is derived data: no useState and no useEffect
  • filter 为 "all" 时显示全部,"active" 显示未完成,"done" 显示已完成When filter is "all" show everything, "active" shows the unfinished ones, "done" shows the finished ones
  • clearDone 移除所有已完成项,保留未完成项clearDone removes every finished item and keeps the unfinished ones
  • 写操作必须作用于 todos,不能作用于 visibleA write has to act on todos, never on visible
  • 不许修改原数组Do not change the original array
TSXsrc/components/TodoList/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 变式二 · 计时器:useEffect 的清理函数Variation 2 · a timer: the useEffect cleanup function · React 考试React exam
L3写整块Write a block自己写出整个计时器Write the whole timer yourselfDrillLab 自出Written by DrillLab

两个 state、一个 effect、一个 reset、一个 mm:ss 格式化。 检查器会专门查清理函数和函数式更新。

Two states, one effect, one reset, and one mm:ss formatter. The checker looks specifically for the cleanup function and the updater form.

要求Requirements
  • format(65) 要返回 "01:05",个位数补零format(65) has to return "01:05", padding single digits with a zero
  • 点 Start 开始每秒加一,点 Pause 停下并保留当前值Start begins adding one per second; Pause stops and keeps the current value
  • Reset 停下来并清零(按钮文字回到 Start)Reset stops and goes back to zero (the button text returns to Start)
  • effect 必须返回清理函数清掉定时器The effect has to return a cleanup function that clears the interval
  • 必须用函数式更新,避免过期闭包Use the updater form, so there is no stale closure
  • 按钮文字:跑着显示 Pause,停着显示 StartButton text: Pause while running, Start while stopped
TSXsrc/components/Timer/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 变式三 · fetch 取数:loading、error 与竞态Variation 3 · fetching data: loading, error, and the race between two requests · React 考试React exam
L3写整块Write a block自己写出带竞态防护的取数 effectWrite the fetching effect with race protection yourselfDrillLab 自出Written by DrillLab

三个 state 已给好。写出 effect 和三个提前返回。 检查器会查 res.ok、清理函数、竞态防护和 AbortError 过滤。

The three states are given. Write the effect and the three early returns. The checker looks for res.ok, the cleanup function, the race protection and the AbortError filter.

要求Requirements
  • 从 /api/users/{userId} 取数Fetch from /api/users/{userId}
  • 非 2xx 响应要当成错误处理,错误信息形如 HTTP 404Treat any non-2xx response as an error, with a message like HTTP 404
  • userId 变化时重新取数,并把上一次的结果作废(竞态防护)Refetch when userId changes, and void the previous result (race protection)
  • 用 AbortController 掐掉在途请求,但 AbortError 不展示给用户Use AbortController to cut off the request in flight, but never show AbortError to the user
  • 渲染顺序:loading → error → 空数据 → 正常数据Render order: loading, then error, then no data, then the data
  • effect 本身不能是 async 函数The effect itself must not be an async function
TSXsrc/components/UserCard/index.tsx
This check is textual: it looks for the right constructs, it does not run your code
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.