Task 2 · Delete:点 Delete,该行按 id 被移除Task 2 · Delete: click Delete and that one row is removed by id
一行 filter。但「按 id」这三个字是有分量的。One line of filter. But the words by id carry weight.
这一页有什么On this page7
- 独立写出 handleDeleteWrite handleDelete on your own
- 解释为什么必须按 id 比较而不是 title 或下标Explain why the comparison must use the id and not the title or the index
- 说清 filter 的条件为什么是 !== 而不是 ===Say why the filter condition is !== and not ===
- 知道这个测试为什么测不出「按 id」这个要求Know why this test cannot catch the by id requirement
第 3 个测试查它。但那个测试只有一条数据,用 title 比较也能过 —— 这是本项目「测试过了不等于做对了」的第一个实例。The third test checks it. But that test has only one note, so comparing by title passes as well. This is the first case in this project where passing tests do not mean the code is correct.
react-notes-app/src/components/NoteManager/index.tsxhandleDelete
提醒:源项目在磁盘上是做完的版本 —— 下面就是答案。想自己先写一遍的话,现在关上。Heads up: on disk this project is the finished version — what follows is the answer. Close this if you want to write it yourself first.
react-notes-app/src/components/NoteManager/index.tsxreact-notes-app/src/components/NoteItem/index.tsxDelete 按钮在这里上报 idThe Delete button reports the id from here
react-notes-app/src/components/NoteItem/index.tsx这一问在要求什么What this task asks for
原文:「点 Delete → 该行按 id 被移除」。
「按 id」是出题人加的限定。它排除了两种写法:
- 按下标删(
splice(index, 1))—— 下标会随列表变化,而且splice改的是原数组。 - 按内容删(
n.title !== title)—— 两条同名笔记会被一起删掉。
NoteItem 那边也配合了这个设计 —— 它上报的就是 id:
The original: “click Delete → that row is removed by id”.
“By id” is a qualifier the author added. It rules out two approaches:
- Delete by index (
splice(index, 1)) — indexes shift as the list changes, andsplicemutates the original array. - Delete by content (
n.title !== title) — two notes with the same name go out together.
NoteItem is built for this design too — what it reports up is the id itself:
react-notes-app/src/components/NoteItem/index.tsx 与 NoteTable/index.tsx先想再写Think it through before you write
最后一问是这道题唯一会绕人的地方。filter 的语义是「留下」,不是「删掉」。 所以要删 id 相等的那条,条件必须写成「保留 id 不相等的」。
That last question is the only place this task twists you around. filter means keep, not remove. So to delete the note whose id matches, the condition has to read “keep the ones whose id does not match”.
实现The implementation
一行就够:
逐段读:
prev.filter(...)——filter永远返回新数组, 原数组一动不动。所以不可变更新自动满足, 不需要额外套展开语法。(note) => note.id !== id—— 对每一条问一句「你的 id 是不是要删的那个? 不是 → 留下」。- 没有
if判断「找不到怎么办」。 不需要 —— 如果没有匹配的 id,filter就原样返回一份全留的新数组, 界面无变化。这是合理行为。
One line is enough:
Read it piece by piece:
prev.filter(...)—filteralways returns a new array and leaves the original alone. So the update does not change the original array, and no extra spread is needed.(note) => note.id !== id— ask every note: is your id the one being deleted? No → you stay.- There is no
iffor “what if nothing matches”. None is needed — with no matching id,filterreturns a new array that keeps everything, and the screen does not change. That is sensible behaviour.
react-notes-app/src/components/NoteManager/index.tsx测试的盲区:为什么它测不出「按 id」The blind spot in the test: why it cannot catch by id
这是这个项目最值得记住的一课。This is the one lesson from this project most worth remembering.
看第 3 个测试:
整个测试只有一条数据。所以下面这些写法全都能通过:
prev.filter(n => n.id !== id)✓ 正确prev.filter(n => n.title !== "ToDelete")—— 硬编码都能过[]—— 直接清空整个列表,也能过prev.slice(1)—— 删第一条,也能过
所以:不要用「测试过了」当作「做对了」的证据。这道题的正确性判据是 README 里那句「按 id」, 以及你自己在 npm run dev 里加三条同名笔记 手动点一遍的结果。
这个道理在 Federation 那门考试里会以更夸张的形式重现 —— 那边有六个端点全部返回 null, 测试照样过了三个。
Look at the third test:
The whole test has exactly one note in it. Which means every one of these passes:
prev.filter(n => n.id !== id)✓ correctprev.filter(n => n.title !== "ToDelete")— even hard-coding gets through[]— wiping the whole list gets through tooprev.slice(1)— dropping the first item, also through
So: never take “the tests pass” as evidence that you got it right. Correctness here is judged by that phrase “by id” in the README, plus what you see when you add three same-named notes under npm run dev and click through by hand.
The same point comes back in a far more extreme form in the Federation exam — over there six endpoints all return null and three tests still pass.
react-notes-app/src/NoteManager.test.tsx怎么自己验证「按 id」真的做对了How to check for yourself that by id really works
测试帮不上忙,就自己造一个测试不到的场景:
npm run dev,打开浏览器。- 加三条标题完全相同的笔记, 内容分别写 1、2、3。
- 点中间那条的 Delete。
- 正确:只有内容为 2 的那条消失, 1 和 3 还在,顺序不变。
如果按 title 删:三条全没了。
如果按下标删:可能删错行。
这种「手动造一个测试覆盖不到的场景」的能力, 比会写 filter 值钱得多。
The tests cannot help here, so build the scenario they miss yourself:
npm run dev, open the browser.- Add three notes with exactly the same title, with contents 1, 2 and 3.
- Click Delete on the middle one.
- Correct: only the one with content 2 disappears; 1 and 3 stay, in the same order.
If you deleted by title: all three vanish.
If you deleted by index: you may hit the wrong row.
This skill — building by hand a scenario the tests do not cover — is worth far more than knowing how to write filter.
动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
三个空。第三个空是这道题唯一会绕人的地方。
Three blanks. The third one is the only part that trips people up.
一行代码的题。但要一次写对,不许用 push / splice。
One line of code. But get it right the first time, and without push or splice.
- 按 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
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
测试全过,但手动测试时发现:三条标题相同的笔记, 点其中一条的 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.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- handleDelete 就一行:setNotes(prev => prev.filter(n => n.id !== id))。handleDelete is one line: setNotes(prev => prev.filter(n => n.id !== id)).
- filter 的语义是「留下」,所以删除要用不等号。filter means keep, so deleting uses the not-equal operator.
- 必须按 id 比较:title 不唯一,下标会变,splice 还会改原数组。Compare on the id: titles are not unique, indexes shift, and splice changes the original array.
- 第 3 个测试只有一条数据,硬编码甚至清空列表都能过 —— 测试不是正确性证明。The third test has only one note, so even hard-coding or emptying the list passes. A passing test is not a proof of correctness.
- 验证「按 id」的办法是手动加三条同名笔记,删中间那条。To check by id, add three notes with the same title by hand and delete the middle one.