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
筛一下Filter theseReact 考试React examL3L3Debug LabDebug Lab
难度LevelL1 → L4 和上面四档同一个意思:给你的东西越来越少L1 → L4: the same idea as the four tiers above — less is handed to you全部难度All levelsL1L1L2L2L3L3L4L4

已筛到你正在学的《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 · 点 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 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 变式三 · 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 to have text content: 用户2 Received: 用户1 Tests 1 failed | 5 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(竞态) Expected element to have text content: 用户2 Received: 用户1 Tests 1 failed | 5 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 挂到深层节点,且不改原树 TypeError: Cannot add property 0, object is not extensible (The test deep-froze the original tree; the implementation edits it in 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 # 在 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 # 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 setNoteToEdit(null);
7 } else {
8 setNotes((prev) => [...prev, submittedNote]);
9 }
10};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this