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

已筛到你正在学的《React 考试》。想看全部就点上面的「全部」。Filtered to React exam — the course you are on. Use “All” above to see everything.

练习Exercises

筛出 5 个练习(共 148 个)。Showing 5 of 148.
来自From useEffect:把 props 的变化同步进 stateuseEffect: copying a change in props into state · React 考试React exam
L3Debug LabDebug LabDebug Lab · 输入框敲不进字Debug Lab · the inputs will not take any typing

在 Title 输入框里打字,字符出现一瞬间又消失;点某一行的 Edit,表单能回填,但同样改不动。控制台没有任何报错。请判断类型并定位。

Type into the Title input and each character appears for an instant, then vanishes; press Edit on a row and the form fills in, but you cannot change it either. The console shows no error at all. Classify the error, then locate it.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。 # 现象:在 Title 输入框里打字,字符出现一瞬间又消失;Add 按钮一直是灰的。 # npx vitest run:3 failed / 1 passed(adds a note / deletes a note / edits a note in place 失败)# No error at all. # Symptom: type into the Title input and each character appears for an instant, then vanishes; the Add button stays grey. # npx vitest run: 3 failed / 1 passed (adds a note / deletes a note / edits a note in place fail)
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 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 · the list does not change after you click Update

点 Edit,输入框正常回填,按钮变成 Update。改完内容点 Update —— 表单被清空、按钮回到 Add,看起来像提交成功了,可列表一点变化都没有。控制台干净。

Click Edit and the inputs prefill correctly, and the button becomes Update. Change the content and click Update — the form clears and the button goes back to Add, so it looks as if the update worked, but the list does not change at all. 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 # 实际:表单清空了,按钮也回到了 Add,但列表还是 Old # 测试结果: # ✓ adds a note # ✓ submit button disabled when inputs empty # ✓ deletes a note # ✕ edits a note in place # expect(element).toHaveTextContent() # Expected element to have text content: New # Received: Oldc1EditDelete# 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 form is cleared and the button is back to Add, but the row is still Old # Test results: # ✓ adds a note # ✓ submit button disabled when inputs empty # ✓ deletes a note # ✕ edits a note in place # expect(element).toHaveTextContent() # Expected element to have text content: New # Received: Oldc1EditDelete
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 变式三 · fetch 取数:loading、error 与竞态Variation 3 · fetching data: loading, error, and the race between two requests · React 考试React exam
L3Debug LabDebug LabDebug Lab · URL 上是用户 2,界面显示用户 1Debug Lab · the URL says user 2 and the screen shows user 1DrillLab 自出Written by DrillLab

快速点两个用户,界面最后显示的是先点的那个。慢一点点就没问题。控制台干净。

Click two users quickly and the screen ends up showing the one you clicked first. Click a little slower and it is fine. The console is clean.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。 $ npx vitest run src/UserCard.test.tsx ✕ a slow stale response must not overwrite the newer one(竞态) Expected element not to have text content: 用户1 ✕ aborts the in-flight request on unmount TypeError: Cannot read properties of undefined (reading 'aborted') Tests 2 failed | 4 passed (6) # 手动复现: # 1. 点用户 1(这个接口慢,200ms) # 2. 立刻点用户 2(这个快,10ms) # 3. 先看到用户 2 —— 对的 # 4. 200ms 后界面自己变成了用户 1 ← 错的,URL 上还是 2# No error at all. $ npx vitest run src/UserCard.test.tsx ✕ a slow stale response must not overwrite the newer one (race condition) Expected element not to have text content: 用户1 ✕ aborts the in-flight request on unmount TypeError: Cannot read properties of undefined (reading 'aborted') Tests 2 failed | 4 passed (6) # Manual repro: # 1. Click user 1 (that request is slow, 200ms) # 2. Click user 2 right away (that one is fast, 10ms) # 3. User 2 shows up first — correct # 4. 200ms later the view switches itself to user 1 ← wrong, the URL still says 2
TSXsrc/components/UserCard/index.tsx示意Illustrative
1useEffect(() => {
2 setLoading(true);
3 setError(null);
4
5 (async () => {
6 try {
7 const res = await fetch(`/api/users/${userId}`);
8 if (!res.ok) throw new Error(`HTTP ${res.status}`);
9 setUser(await res.json());
10 } catch (e) {
11 setError((e as Error).message);
12 } finally {
13 setLoading(false);
14 }
15 })();
16}, [userId]);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 变式四 · 递归读取评论的评论Variation 4 · reading replies to replies with recursion · React 考试React exam
L3Debug LabDebug LabDebug Lab · 回复加进去了,界面不动Debug Lab · the reply went in and the screen never movedDrillLab 自出Written by DrillLab

给深层评论加回复,console.log 打出来的树里新回复确实在,但界面没变化。控制台干净。

You add a reply to a deep comment. The tree printed by console.log really does contain the new reply, but the screen does not change. The console is clean.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。 $ npx vitest run src/CommentTree.test.tsx ✕ addReply 挂到深层节点,且不改原树 TypeError: Cannot add property 0, object is not extensible (测试把原树深冻结了,实现试图直接修改它) ✕ 给三层的评论再回复,落在正确的位置 Unable to find an element with the text: 第四层 # 手动复现:点某条评论的 Reply、输入、发送 # console.log(comments) -> 新回复确实在树里 # 屏幕 -> 一点变化都没有# No error at all. $ npx vitest run src/CommentTree.test.tsx ✕ addReply attaches to a deep node and leaves the original tree untouched TypeError: Cannot add property 0, object is not extensible (The test deep-froze the original tree; the implementation edits it in place.) ✕ a reply to a third-level comment lands in the right place Unable to find an element with the text: 第四层 # Manual repro: click Reply on a comment, type something, send it # console.log(comments) -> the new reply really is in the tree # the screen -> nothing changes at all
TSXsrc/components/CommentTree/index.tsx示意Illustrative
1function addReply(nodes: Comment[], parentId: number, reply: Comment) {
2 for (const node of nodes) {
3 if (node.id === parentId) {
4 node.replies.push(reply); // 找到就塞进去
5 return nodes;
6 }
7 addReply(node.replies, parentId, reply);
8 }
9 return nodes;
10}
11
12const handleReply = (parentId: number, text: string) => {
13 const reply = { id: Date.now(), author: "我", body: text, replies: [] };
14 setComments(addReply(comments, parentId, reply));
15};
1function addReply(nodes: Comment[], parentId: number, reply: Comment) {
2 for (const node of nodes) {
3 if (node.id === parentId) {
4 node.replies.push(reply); // found it, so push it in
5 return nodes;
6 }
7 addReply(node.replies, parentId, reply);
8 }
9 return nodes;
10}
11
12const handleReply = (parentId: number, text: string) => {
13 const reply = { id: Date.now(), author: "我", body: text, replies: [] };
14 setComments(addReply(comments, parentId, reply));
15};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L3Debug LabDebug Lab故障 4 · 编辑后列表毫无变化(综合题)Fault 4 · the list does not change after an edit (mixed question)

这一题不告诉你是哪一类。控制台干净,console.log 显示数据是对的。自己分诊。

This one does not tell you which category it is. The console is clean, and console.log shows the data is correct. Sort it yourself.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
# 没有任何报错。 # 复现:添加 "A"、"B" 两条 → 点 B 的 Edit → 改成 "B2" → 点 Update # 期望:列表变成 A、B2 # 实际:列表还是 A、B,按钮还停在 Update # 在 handleSubmitNote 里插了日志: console.log("submitted:", submittedNote); // → submitted: { id: 1785737900978, title: 'B2', content: '...' } ← 数据是对的 console.log("after:", notes); // → after: [ {title:'A'...}, {title:'B2'...} ] ← 数组里也是对的! # 但屏幕上还是 B。 # 测试结果: # ✕ edits a note in place# No error at all. # Repro: add "A" and "B" → click Edit on B → change it to "B2" → click Update # Expected: the list becomes A, B2 # Actual: the list is still A, B, and the button is still on Update # Logs added inside handleSubmitNote: console.log("submitted:", submittedNote); // → submitted: { id: 1785737900978, title: 'B2', content: '...' } ← the data is right console.log("after:", notes); // → after: [ {title:'A'...}, {title:'B2'...} ] ← the array is right too! # But the screen still shows B. # Test result: # ✕ edits a note in place
TSX有问题的 handleSubmitNoteThe handleSubmitNote with the problem示意Illustrative
1const handleSubmitNote = (submittedNote: Note) => {
2 if (noteToEdit) {
3 const i = notes.findIndex((n) => n.id === submittedNote.id);
4 notes[i] = submittedNote;
5 setNotes(notes);
6 } else {
7 setNotes((prev) => [...prev, submittedNote]);
8 }
9};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this