动手做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.
已筛到你正在学的《React 考试》。想看全部就点上面的「全部」。Filtered to React exam — the course you are on. Use “All” above to see everything.
练习Exercises
筛出 54 个练习(共 148 个) · 第 4 / 5 页。Showing 54 of 148 · page 4 / 5.三个空,全在这九行里。第 2 个空漏了会「越跳越快」, 第 3 个空写错会「卡在 1 不动」。
Three blanks, all within these nine lines. Miss the second and the clock speeds up with every start. Get the third wrong and the display freezes at 1.
两个 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.
- 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
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
点了几次 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 和第 4 个合起来解决竞态,第 2 个是 fetch 的经典坑。
Four blanks. The first and the fourth together settle the race. The second is the classic fetch trap.
三个 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.
- 从 /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
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
快速点两个用户,界面最后显示的是先点的那个。 慢一点点就没问题。控制台干净。
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.
四个空。第 2 个是递归调用本身,第 4 个是「往下一层」。
Four blanks. The second is the recursive call itself, and the fourth is one level further down.
这是这道题真正的难点。目标节点可能在任意深度, 要返回一棵新树,而且原树一个字节都不能改。
This is the hard part of the question. The target node can be at any depth, you have to return a new tree, and not one byte of the original may change.
- 找到 id === parentId 的节点,把 reply 追加到它的 replies 末尾Find the node whose id === parentId and append reply to the end of its replies
- 返回新数组、新节点对象,不修改原数据Return a new array and new node objects, without changing the original data
- 目标可能在任意深度,需要递归往下找The target can be at any depth, so recurse downwards to find it
- 不许用 JSON.parse(JSON.stringify(...)) 深拷贝Do not deep-copy with JSON.parse(JSON.stringify(...))
- 不许用 push / splice / 直接赋值Do not use push / splice / direct assignment
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
给深层评论加回复,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.
四个空。第 3 个是最容易漏的那一步,第 4 个决定「忘了套 Provider」 时报错清不清楚。
Four blanks. The third is the step people miss most. The fourth decides how clear the error is when somebody forgets to wrap things in the Provider.
类型已给好。写出 context、Provider、自定义 hook 三部分。 检查器会查记忆化、函数式更新和守卫。
The types are given. Write all three parts: the context, the Provider, and the custom hook. The checker looks for the memoization, the updater form and the guard.
- theme 初始为 'light'theme starts as 'light'
- toggleTheme 在 light / dark 之间翻转,必须用函数式更新toggleTheme flips between light and dark, using the updater form
- context value 要记忆化,theme 不变时不产生新对象The context value has to be memoized, so no new object appears while theme is unchanged
- toggleTheme 引用要稳定(theme 变了它也不变)The reference of toggleTheme has to be stable, unchanged even when theme changes
- 没套 Provider 就用 useTheme() 时抛出一句能看懂的错误Calling useTheme() with no Provider above it throws a message a person can read
- 不许把 theme 存到组件外的全局变量里Do not keep theme in a global variable outside the component
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
按钮好好的,卡片一渲染就整页白屏。这是真实报错。
The button is fine, and the moment the card renders the whole page goes blank. This is the real error.