useState:让界面跟着数据变useState: making the screen follow the data
两个 state 撑起了整道 Q1:notes 和 noteToEdit。Two pieces of state carry the whole of Q1: notes and noteToEdit.
这一页有什么On this page7
- 01 普通变量为什么不行Why a plain variable does not work
- 02 useState 返回一个数组,里面两样东西useState returns an array with two things in it
- 03 为什么用 setNotes(prev => ...) 而不是 setNotes([...notes, n])Why setNotes(prev => ...) and not setNotes([...notes, n])
- 04 一次点击的完整旅程The full path of one click
- 练习 · 动手做Practice
- 常见错误Common mistakes
- 迁移模式Transfer
- 说清 useState 返回的两个东西各是什么Explain what each of the two things useState returns is
- 知道为什么必须用 setter 而不能直接赋值Know why you have to use the setter instead of assigning a new value
- 会用函数式更新 setX(prev => ...) 并说清它比 setX(newValue) 好在哪Use the updater form setX(prev => ...) and say what makes it safer than setX(newValue)
- 看懂一次点击是怎么最终变成新界面的Follow how one click turns into a new screen
Q1 的判卷标准就是「点了按钮之后界面对不对」。state 用错,四个测试全挂。这是整门考试最核心的一节。Q1 is graded on one thing: is the screen correct after the button is clicked. Use state wrong and all four tests fail. This is the most important lesson in the whole exam.
react-notes-app/src/components/NoteManager/index.tsx两个 state 与三个 handler 的全部真实代码The full real code for the two pieces of state and the three handlers
react-notes-app/src/components/NoteManager/index.tsx普通变量为什么不行Why a plain variable does not work
组件函数每次渲染都会重新执行一遍。普通变量活不过这一遍。The component function runs again on every render. A plain variable does not survive that.
这是理解 React 最关键的一件事:组件函数会被反复调用。 每次界面需要更新,React 就把你的组件函数再执行一次, 拿到新的 JSX,然后对比、更新真实 DOM。
所以如果你在组件里写 let notes = [], 那么每次重新渲染,这行都会重新执行,notes 又变回空数组。数据存不住。
useState 解决的正是这个问题: 它让 React 在组件外部替你记住这个值, 每次重新渲染时把上次的值交还给你。
This is the most important thing to understand about React: your component function gets called again and again. Every time the UI needs to change, React runs your function once more, takes the new JSX, compares it, and updates the real DOM.
So if you write let notes = [] inside the component, that line runs again on every render and notes is back to an empty array. The data cannot survive.
useState exists for exactly this problem: it asks React to remember the value outside your component and hand you last render’s value each time it runs.
useState 返回一个数组,里面两样东西useState returns an array with two things in it
const [notes, setNotes] = useState<Note[]>([])这一行做了三件事:
- 声明一块由 React 保管的状态,初始值
[]。这个初始值只在第一次渲染时用, 之后的渲染会忽略它。 notes拿到当前这次渲染看到的值。setNotes是唯一合法的修改途径。 调用它 = 告诉 React「值变了,请重新渲染」。
useState 返回的是数组, 所以用 [a, b] 这种数组解构, 名字随你起(但习惯上是 x / setX)。
真实项目里的两个 state:
const [notes, setNotes] = useState<Note[]>([]) does three things in one line:
- Declares a slot of state that React looks after, starting at
[]. That initial value is only used on the first render; every render after that ignores it. notesholds the value this particular render sees.setNotesis the only legal way to change it. Calling it means “the value changed, please render again”.
useState returns an array, which is why you take it apart with [a, b]. The names are yours to pick (though x / setX is the convention).
The two pieces of state in the real project:
react-notes-app/src/components/NoteManager/index.tsx为什么用 setNotes(prev => ...) 而不是 setNotes([...notes, n])Why setNotes(prev => ...) and not setNotes([...notes, n])
两种都能用。但前者在一种情况下明显更安全。Both forms work. But the first one is clearly safer in one situation.
notes 这个变量拿到的是当前这次渲染时的快照。 如果你在同一个事件里连续调用两次 setter, 第二次看到的 notes 还是旧的:
而函数式更新 setNotes(prev => ...)里的 prev 是 React 交给你的 「此刻最新的值」,连续调用也不会丢。
Q1 里其实不会连续调两次,所以两种写法都能过测试。 但真实项目里的代码统一用了函数式更新 ——这是更稳的默认习惯,跟着写就对了。
The notes variable holds a snapshot from this render. If you call the setter twice inside the same event, the second call still sees the old notes:
With a functional update — setNotes(prev => ...) — the prev React hands you is “the freshest value right now”, so back-to-back calls lose nothing.
Q1 never actually calls the setter twice in a row, so both styles pass the tests. But the real project uses functional updates everywhere —it is the steadier default, so just write it that way.
一次点击的完整旅程The full path of one click
把这条链走通,你就真的懂 React 了。Once you can follow this chain end to end, you really do understand React.
下面是「点 Delete 按钮」这一下,从手指到屏幕之间发生的全部事情。 一步一步点过去。
关键在第 4 步:React 并不知道「哪一行被删了」。 它只知道「state 变了」,于是把 NoteManager整个重新执行一遍,拿到新的 JSX,再和上一次的对比, 最后只把真正变化的 DOM 改掉。
所以你不需要手动去删 DOM 节点、 不需要 document.querySelector。 你只管改数据,界面自己跟上。 这就是 React 的全部承诺。
Below is everything that happens between your finger and the screen when you press Delete. Step through it one frame at a time.
Step 4 is the one that matters: React has no idea which row was deleted. All it knows is that state changed, so it runs NoteManager again from the top, takes the new JSX, compares it with the previous one, and changes only the DOM that really differs.
Which means you never remove DOM nodes by hand and never need document.querySelector. You change the data, the UI keeps up. That is the whole promise of React.
动手做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.
用户点了某一行的 Delete 按钮。把下面五件事按发生顺序排好。
The user clicked the Delete button on one row. Put the five things below in the order they happen.
只给你组件外壳。按要求补出两个 state 和 handleDelete。 不要看下面的答案,先自己写。
You get only the shell of the component. Add the two states and handleDelete as described. Write it yourself before you look at the answer below.
- 用 useState 声明 notes,类型是 Note[],初始值为空数组Declare notes with useState, typed Note[], starting as an empty array
- 用 useState 声明 noteToEdit,类型是 Note | null,初始值为 nullDeclare noteToEdit with useState, typed Note | null, starting as null
- handleDelete 按 id 移除对应笔记,必须用函数式更新,不许改动原数组handleDelete removes the matching note by id, using a functional update, without changing the original array
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
初学者常见的几种写法错误Mistakes beginners actually make
下面每一段都是「能编译、但结果不对」或者「一跑就炸」的真实写法。先自己看出问题在哪,再看解释。Every snippet below either compiles and gives the wrong answer, or blows up on the first run. Spot the problem yourself before reading the explanation.
notes 是 const 声明的, 这行连编译都过不去。就算改成 let, React 也不会知道值变了 —— 它只监听 setter 的调用。唯一的修改途径是 setNotes。notes is declared with const, so this line does not even compile. Even as a let, React would not know the value changed — it only watches for setter calls. setNotes is the only way to change it.setNotes 只是「预约一次重新渲染」, 它不会当场改变 notes 这个变量。 新值要到下一次渲染才看得到。 想在更新后做点什么,用 useEffect(下一节讲)。setNotes only asks React for one more render. It does not change the notes variable on the spot. The new value is visible on the next render. To do something after the update, use useEffect (the next lesson).换一道题也能用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.
- 组件函数会被反复执行,所以普通变量存不住数据 —— 这是 useState 存在的原因。The component function runs again and again, so a plain variable cannot hold data. That is the reason useState exists.
- useState 返回 [当前值, setter];初始值只在第一次渲染生效。useState returns [current value, setter]. The initial value is used only on the first render.
- setter 是唯一合法的修改途径,调用它等于「预约一次重新渲染」。The setter is the only legal way to change the value. Calling it asks React for one more render.
- setX(prev => ...) 比 setX(新值) 稳,项目里统一用前者。setX(prev => ...) is safer than setX(newValue), and this project uses the first form everywhere.
- 你只管改数据,DOM 由 React 对比后自动更新 —— 不要自己操作 DOM。You change the data, and React compares and updates the DOM for you. Do not touch the DOM yourself.