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

筛出 16 个练习(共 148 个) · 第 1 / 2 页。Showing 16 of 148 · page 1 / 2.
来自From 数组与对象:不可变更新三件套Arrays and objects: three ways to update without changing the original · 地基 · 项目与语言Foundations · project and language
L2Debug LabDebug LabDebug Lab · 数据加进去了,界面没反应Debug Lab · the data went in, the screen did not move

这一类 bug 最难查,因为它不报错。 先看现象,判断类型,再找病灶 —— 别跳步。

This kind of bug is the hardest to find, because it reports no error. Read the symptom, classify it, then locate it. Do not skip a step.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。控制台干净。 # 现象:填好表单点 Add,表格里什么都不出现。 # 在 handleSubmitNote 里 console.log(notes) —— 长度确实在增加。 notes.length before: 0 notes.length after : 1 ← 数据真的进去了 (但 <NoteTable /> 渲染出来的行数始终是 0)# No error at all. The console is clean. # Symptom: fill in the form, click Add, and nothing shows up in the table. # Add console.log(notes) inside handleSubmitNote — the length really does grow. notes.length before: 0 notes.length after : 1 ← the data really did go in (But <NoteTable /> always renders 0 rows.)
TSX有问题的写法The broken version示意Illustrative
1const handleSubmitNote = (submittedNote: Note) => {
2 notes.push(submittedNote);
3 setNotes(notes);
4};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From ESM:import / export 与那些莫名其妙的报错ESM: import / export, and the errors that look strange at first · 地基 · 项目与语言Foundations · project and language
L2Debug LabDebug LabDebug Lab · ERR_MODULE_NOT_FOUNDDebug Lab · ERR_MODULE_NOT_FOUND

你在 node-subgraph/ 里跑 npm start, 服务器起不来。报错很长,但关键信息只有两行。

You run npm start inside node-subgraph/ and the server does not come up. The error is long, but only two lines of it matter.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npm start node:internal/modules/esm/resolve:274 Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/me/node-subgraph/src/dataSources/orderDataSource' imported from /Users/me/node-subgraph/src/index.js at finalizeResolution (node:internal/modules/esm/resolve:274:11) Did you mean to import "./dataSources/orderDataSource.js"?
JavaScriptsrc/index.js(第 2 行有问题)src/index.js (line 2 is the problem)示意Illustrative
1import { resolvers } from './resolvers/orderResolvers.js';
2import { OrderDataSource } from './dataSources/orderDataSource';
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From props:数据往下流,事件往上报props: data flows down, events go back up · React 考试React exam
L2Debug LabDebug LabDebug Lab · 页面一打开,所有笔记就消失了Debug Lab · every note disappears the moment the page opens

添加两条笔记后刷新页面(假设有持久化),表格瞬间变空。 有时候浏览器还会卡住。先判断类型,再找病灶。

Add two notes, then reload the page (assume the notes are saved somewhere). The table goes empty at once, and sometimes the browser stops responding. First name the kind of error, then find the line that causes 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 repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. (另一种表现:没有任何报错,但表格永远是空的)Warning: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. (Another symptom: no warning at all, but the table stays empty forever.)
TSX有问题的 NoteItemThe NoteItem with the bug示意Illustrative
1const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
2 return (
3 <tr>
4 <td>{note.title}</td>
5 <td>{note.content}</td>
6 <td>
7 <button onClick={onEdit(note)} className="outlined">Edit</button>
8 </td>
9 <td>
10 <button onClick={onDelete(note.id)} className="danger">Delete</button>
11 </td>
12 </tr>
13 );
14};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 受控输入:value + onChange 的闭环Controlled inputs: the loop between value and onChange · React 考试React exam
L2Debug LabDebug LabDebug Lab · 点 Add 之后页面闪一下,笔记没了Debug Lab · the page blinks after Add and the note is gone

填好标题和内容,点 Add。页面明显闪了一下, 地址栏出现了 ?,表格还是空的。

Fill in a title and some content, then press Add. The page clearly blinks, a ? appears in the address bar, and the table is still empty.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有 JavaScript 报错。 # 现象:点击 Add 后 # - 页面整体刷新了一次 # - 地址栏从 http://localhost:5173/ 变成 http://localhost:5173/? # - 输入框被清空,表格依然是空的 # - React DevTools 里所有 state 都回到了初始值# No JavaScript error. # Symptom: after clicking Add # - the whole page reloaded once # - the address bar changed from http://localhost:5173/ to http://localhost:5173/? # - the inputs were cleared and the table is still empty # - in React DevTools every piece of state is back to its initial value
TSX有问题的 handleSubmitThe broken handleSubmit示意Illustrative
1const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
2 if (isFormInvalid) return;
3 const newNote = { id: Date.now(), title: title.trim(), content: content.trim() };
4 onSubmit(newNote);
5 setTitle("");
6 setContent("");
7};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Task 2 · Delete:点 Delete,该行按 id 被移除Task 2 · Delete: click Delete and that one row is removed by id · React 考试React exam
L2Debug LabDebug LabDebug Lab · 删一条,同名的全没了Debug Lab · delete one note and every note with the same title goes too

测试全过,但手动测试时发现:三条标题相同的笔记, 点其中一条的 Delete,三条一起消失。

Every test passes, but a manual check shows this: with three notes that share a title, clicking Delete on one of them makes all three disappear.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 测试:4 passed (4) ← 测试全过! # 手动复现步骤: # 1. 添加 "会议记录 / 内容1" # 2. 添加 "会议记录 / 内容2" # 3. 添加 "会议记录 / 内容3" # 4. 点第 2 行的 Delete # 期望:只剩「内容1」「内容3」 # 实际:表格全空# Tests: 4 passed (4) ← every test passes! # Manual repro steps: # 1. Add "会议记录 / 内容1" # 2. Add "会议记录 / 内容2" # 3. Add "会议记录 / 内容3" # 4. Click Delete on the second row # Expected: only 内容1 and 内容3 are left # Actual: the table is empty
TSX有问题的 handleDeleteThe handleDelete with the bug示意Illustrative
1const handleDelete = (id: number) => {
2 const target = notes.find((n) => n.id === id);
3 setNotes((prev) => prev.filter((note) => note.title !== target?.title));
4};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 实现:worker pool(工人池)Building it: a worker pool, meaning a fixed number of workers sharing one queue · React 考试React exam
L2Debug LabDebug LabDebug Lab · 一行 START 都没打印Debug Lab · not one START line prints

npm run q2,没有报错,但一行task N START 都没有,直接就出结果了 —— 而且结果里的 value 长得很奇怪。

You run npm run q2. Nothing reports an error, but not one task N START line appears; the results come out right away. And the value in each result looks strange.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npm run q2 === FINAL RESULTS (must be in original order) === #1 { status: 'fulfilled', value: [Function (anonymous)] } #2 { status: 'fulfilled', value: [Function (anonymous)] } #3 { status: 'fulfilled', value: [Function (anonymous)] } #4 { status: 'fulfilled', value: [Function (anonymous)] } #5 { status: 'fulfilled', value: [Function (anonymous)] } #6 { status: 'fulfilled', value: [Function (anonymous)] } # 注意: # - 一行 "task N START" 都没有 # - 应该 reject 的 task 3 也变成了 fulfilled # - value 是函数,不是 "result of task N"$ npm run q2 === FINAL RESULTS (must be in original order) === #1 { status: 'fulfilled', value: [Function (anonymous)] } #2 { status: 'fulfilled', value: [Function (anonymous)] } #3 { status: 'fulfilled', value: [Function (anonymous)] } #4 { status: 'fulfilled', value: [Function (anonymous)] } #5 { status: 'fulfilled', value: [Function (anonymous)] } #6 { status: 'fulfilled', value: [Function (anonymous)] } # Note: # - not one "task N START" line was printed # - task 3, which should reject, came back fulfilled # - value is a function, not "result of task N"
TypeScript有问题的 workerThe worker with the problem示意Illustrative
1const worker = async () => {
2 while (nextIndex < tasks.length) {
3 const i = nextIndex;
4 nextIndex++;
5
6 try {
7 const value = await tasks[i];
8 results[i] = { status: "fulfilled", value };
9 } catch (reason) {
10 results[i] = { status: "rejected", reason };
11 }
12 }
13};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 变式二 · 计时器:useEffect 的清理函数Variation 2 · a timer: the useEffect cleanup function · React 考试React exam
L2Debug LabDebug LabDebug Lab · 计时器越跑越快Debug Lab · the timer keeps getting fasterDrillLab 自出Written by DrillLab

点了几次 Start / Pause 之后,秒数开始一次跳好几秒。 下面是真实的测试输出。

After a few clicks of Start and Pause, the seconds start jumping several at a time. Below is the real test output.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npx vitest run src/Timer.test.tsx ✕ pause stops the clock and keeps the value Expected element to have text content: 00:02 Received: 00:07 ✕ start/pause many times does not speed up Expected element to have text content: 00:04 Received: 00:10 ✕ reset stops and zeroes Expected element to have text content: 00:00 Received: 00:03 ✕ unmount clears the interval AssertionError: expected 1 to be +0 // Object.is equality Tests 4 failed | 4 passed (8) # 现象:start / pause 来回点四次,每次只走 1 秒, # 显示却是 00:10 —— 正好是 1+2+3+4。 # 而且 Reset 之后秒数还在自己往上涨。$ npx vitest run src/Timer.test.tsx ✕ pause stops the clock and keeps the value Expected element to have text content: 00:02 Received: 00:07 ✕ start/pause many times does not speed up Expected element to have text content: 00:04 Received: 00:10 ✕ reset stops and zeroes Expected element to have text content: 00:00 Received: 00:03 ✕ unmount clears the interval AssertionError: expected 1 to be +0 // Object.is equality Tests 4 failed | 4 passed (8) # Symptom: click start / pause four times, one second of running each time, # and the display reads 00:10 — exactly 1+2+3+4. # After Reset the seconds also keep climbing on their own.
TSXsrc/components/Timer/index.tsx示意Illustrative
1useEffect(() => {
2 if (!running) return;
3
4 const id = setInterval(() => {
5 setSeconds((s) => s + 1);
6 }, 1000);
7}, [running]);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 变式五 · 主题切换:Context 怎么用Variation 5 · theme switching: how to use Context · React 考试React exam
L2Debug LabDebug LabDebug Lab · Cannot destructure property 'theme'DrillLab 自出Written by DrillLab

按钮好好的,卡片一渲染就整页白屏。这是真实报错。

The button is fine, and the moment the card renders the whole page goes blank. This is the real error.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npx vitest run src/Theme.test.tsx TypeError: Cannot destructure property 'theme' of '(0 , __vite_ssr_import_1__.useTheme)(...)' as it is undefined. at ThemedCard (src/components/ThemedCard/index.tsx:6:11) at ThemeApp ✕ 默认是 light,按钮说 Switch to Dark ✕ 点一下变 dark:按钮文字和卡片底色一起变 ✕ 再点一下切回 light ✕ 没套 Provider 就用 useTheme(),必须立刻报错 ✕ toggleTheme 是稳定引用:theme 变了它也不变 Tests 5 failed | 3 passed (8) # 浏览器里的报错略有不同,意思一样: # Cannot destructure property 'theme' of 'useTheme(...)' as it is undefined.$ npx vitest run src/Theme.test.tsx TypeError: Cannot destructure property 'theme' of '(0 , __vite_ssr_import_1__.useTheme)(...)' as it is undefined. at ThemedCard (src/components/ThemedCard/index.tsx:6:11) at ThemeApp ✕ 默认是 light,按钮说 Switch to Dark ✕ 点一下变 dark:按钮文字和卡片底色一起变 ✕ 再点一下切回 light ✕ 没套 Provider 就用 useTheme(),必须立刻报错 ✕ toggleTheme 是稳定引用:theme 变了它也不变 Tests 5 failed | 3 passed (8) # The browser wording is slightly different but means the same thing: # Cannot destructure property 'theme' of 'useTheme(...)' as it is undefined.
TSXsrc/components/ThemeApp/index.tsx示意Illustrative
1// ThemeContext.tsx
2const ThemeContext = createContext<ThemeContextValue>(undefined as never);
3
4export function useTheme() {
5 return useContext(ThemeContext); // 没有守卫
6}
7
8// ThemeApp/index.tsx
9const ThemeApp: React.FC = () => (
10 <>
11 <ThemeProvider>
12 <ThemeToggleButton />
13 </ThemeProvider>
14 <ThemedCard />
15 </>
16);
1// ThemeContext.tsx
2const ThemeContext = createContext<ThemeContextValue>(undefined as never);
3
4export function useTheme() {
5 return useContext(ThemeContext); // no guard
6}
7
8// ThemeApp/index.tsx
9const ThemeApp: React.FC = () => (
10 <>
11 <ThemeProvider>
12 <ThemeToggleButton />
13 </ThemeProvider>
14 <ThemedCard />
15 </>
16);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 1 · 路径大小写Fault 1 · upper and lower case in a path

新建了组件之后启动开发服务器,Vite 直接报错,页面白屏。

You add a new component, start the dev server, and Vite reports an error right away. The page is blank.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
[plugin:vite:import-analysis] Failed to resolve import "./components/notemanager" from "src/App.tsx". Does the file exist? /Users/me/react-notes-app/src/App.tsx:1:24 1 | import NoteManager from "./components/notemanager"; | ^
TSXsrc/App.tsx示意Illustrative
1import NoteManager from "./components/notemanager";
2
3function App() {
4 return <NoteManager />;
5}
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 2 · props 名字对不上Fault 2 · the prop names do not match

重构时把父组件传的 prop 名改了,子组件忘了跟着改。 页面能显示,但点 Delete 直接崩。

During a refactor the prop name passed by the parent was changed, and the child component was not changed to match. The page still renders, but clicking Delete crashes it.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
Uncaught TypeError: onDelete is not a function at onClick (NoteItem/index.tsx:18:29) at HTMLUnknownElement.callCallback # 另外 TypeScript 那边也在报: src/components/NoteTable/index.tsx(20,7): error TS2322: Type '{ key: number; note: Note; onRemove: (id: number) => void; onEdit: ... }' is not assignable to type 'IntrinsicAttributes & NoteItemProps'. Property 'onDelete' is missing in type ... but required in type 'NoteItemProps'.Uncaught TypeError: onDelete is not a function at onClick (NoteItem/index.tsx:18:29) at HTMLUnknownElement.callCallback # TypeScript is reporting something too: src/components/NoteTable/index.tsx(20,7): error TS2322: Type '{ key: number; note: Note; onRemove: (id: number) => void; onEdit: ... }' is not assignable to type 'IntrinsicAttributes & NoteItemProps'. Property 'onDelete' is missing in type ... but required in type 'NoteItemProps'.
TSX两处不一致The two places that disagree示意Illustrative
1// NoteTable 里传下去的名字:
2<NoteItem
3 key={note.id}
4 note={note}
5 onRemove={onDelete} // ← 传的是 onRemove
6 onEdit={onEdit}
7/>
8
9// NoteItem 的 props 接口和解构:
10export interface NoteItemProps {
11 note: Note;
12 onDelete: (id: number) => void; // ← 期望的是 onDelete
13 onEdit: (note: Note) => void;
14}
15const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
1// The name NoteTable passes down:
2<NoteItem
3 key={note.id}
4 note={note}
5 onRemove={onDelete} // ← it passes onRemove
6 onEdit={onEdit}
7/>
8
9// The props interface of NoteItem, and how it destructures them:
10export interface NoteItemProps {
11 note: Note;
12 onDelete: (id: number) => void; // ← it expects onDelete
13 onEdit: (note: Note) => void;
14}
15const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 3 · 测试找不到元素Fault 3 · the test cannot find the element

代码看起来完全正确,手动点也没问题,但两个测试挂了。

The code looks entirely correct and clicking through it by hand works, but two tests fail.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
FAIL src/NoteManager.test.tsx > adds a note TestingLibraryElementError: Unable to find an element by: [data-testid="form-input"] Ignored nodes: comments, script, style <body> <div> <div class="layout-column ..." data-testid="note-manager"> <div class="card ..."> <form data-testid="note-form"> <section class="layout-row ..."> <label class="form-title-label">Title:</label> <input type="text" placeholder="Title" data-testid="title-input" ... /> ...
TSXsrc/components/NoteForm/index.tsx示意Illustrative
1<input
2 type="text"
3 placeholder="Title"
4 value={title}
5 onChange={(e) => setTitle(e.target.value)}
6 data-testid="title-input"
7 className="form-input"
8/>
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From N+1 问题与 DataLoaderThe N+1 problem and DataLoader · Federation 考试Federation exam
L2Debug LabDebug LabDebug Lab · DataLoader 报 is not a functionDebug Lab · DataLoader reports is not a function

npm test,其中一个 DataLoader 相关的测试挂了。 报错指向 loader 内部。

You run npm test and one of the DataLoader tests fails. The error points inside the loader.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
● Order Resolvers › DataLoader functionality › should batch multiple order requests TypeError: orderDataSource.getOrderById is not a function 29 | 30 | const orders = await Promise.all( > 31 | orderIds.map(id => orderDataSource.getOrderById(id)) | ^ 32 | ); 33 | 34 | return orders; at src/resolvers/orderResolvers.js:31:42 at Array.map (<anonymous>) at DataLoader._batchLoadFn (src/resolvers/orderResolvers.js:31:16)
JavaScriptsrc/resolvers/orderResolvers.js示意Illustrative
1function createOrderLoader(orderDataSource) {
2 return new DataLoader(async orderIds => {
3 const orders = await Promise.all(
4 orderIds.map(id => orderDataSource.getOrderById(id))
5 );
6 return orders;
7 });
8}
9
10// 参考:OrderDataSource 上真实存在的方法
11// class OrderDataSource {
12// async getOrder(id) { ... }
13// async getOrdersByUserId(userId) { ... }
14// async createOrder(userId, items) { ... }
15// }
1function createOrderLoader(orderDataSource) {
2 return new DataLoader(async orderIds => {
3 const orders = await Promise.all(
4 orderIds.map(id => orderDataSource.getOrderById(id))
5 );
6 return orders;
7 });
8}
9
10// For reference: the methods OrderDataSource really has
11// class OrderDataSource {
12// async getOrder(id) { ... }
13// async getOrdersByUserId(userId) { ... }
14// async createOrder(userId, items) { ... }
15// }
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this