useEffect:把 props 的变化同步进 stateuseEffect: copying a change in props into state
Task 3 的「点 Edit 后内容回填到表单」,靠的就是这 9 行。In Task 3, clicking Edit puts the note back into the form. These 9 lines are what does it.
这一页有什么On this page6
- 说清 useEffect 什么时候跑Explain when useEffect runs
- 看懂依赖数组的三种写法各代表什么Read the three ways of writing the dependency array and what each one means
- 解释 NoteForm 里那个 useEffect 为什么必须存在Explain why the useEffect in NoteForm has to be there
- 知道 useEffect 无限循环是怎么造成的Know how a useEffect infinite loop happens
Task 3 要求「点 Edit → 内容回填进表单」。表单的 title/content 是 NoteForm 自己的 state,而触发源 noteToEdit 是外面传进来的 prop —— 把外部变化同步进内部 state,这正是 useEffect 的活。Task 3 asks that clicking Edit fills the form with the note. The title and content of the form are NoteForm's own state, while the trigger noteToEdit is a prop from outside. Copying an outside change into inside state is exactly the job of useEffect.
react-notes-app/src/components/NoteForm/index.tsx第 17–25 行那个 useEffectThe useEffect on lines 17 to 25
react-notes-app/src/components/NoteForm/index.tsxuseEffect 在「渲染完成之后」跑useEffect runs after the render is finished
它不是渲染的一部分,是渲染的后续动作。It is not part of the render. It is what happens after the render.
useEffect(fn, deps) 的意思是:「这次渲染结束、DOM 更新完之后, 如果 deps 里有东西变了,就执行 fn」。
依赖数组有三种写法,行为完全不同:
| 写法 | 什么时候执行 fn | 典型用途 |
|---|---|---|
useEffect(fn, []) | 只在第一次渲染后执行一次 | 初始化、拉一次数据 |
useEffect(fn, [a, b]) | 第一次 + 之后每次 a 或 b 变化 | 同步:外部变了,内部跟上 |
useEffect(fn) | 每一次渲染后都执行 | 几乎总是写错了 |
第三种是无限循环的常见来源:fn 里改了 state → 触发重渲染 → fn 又执行 → 又改 state → …… React 最后会抛Maximum update depth exceeded。
useEffect(fn, deps) means:“once this render is finished and the DOM is updated, if anything in deps changed, run fn”.
There are three ways to write the dependency array, and they behave completely differently:
| Form | When fn runs | Typical use |
|---|---|---|
useEffect(fn, []) | Once, after the first render | Setup, one-off fetch |
useEffect(fn, [a, b]) | First render, then whenever a or b changes | Syncing: something outside moved, catch up |
useEffect(fn) | After every render | Almost always a mistake |
The third one is the usual source of infinite loops: fn changes state → re-render → fn runs again → changes state again → ... and React eventually throws Maximum update depth exceeded.
读懂项目里这个 useEffectRead the useEffect in this project
9 行代码,把 Task 3 的一半工作做完了。Nine lines of code do half of Task 3.
问题是这样的:NoteForm 的输入框内容存在它自己的 state(title / content)里。而「用户点了哪条笔记的 Edit」 这件事发生在外面, 通过 noteToEdit 这个 prop 传进来。
所以需要一条规则:「每当 noteToEdit 变了, 就把内部两个 state 改成它的值」。 这正是 useEffect(fn, [noteToEdit])。
else 分支也重要:noteToEdit变回 null(编辑完成、退出编辑模式)时, 要清空表单。没有这个 else, 提交完之后表单里还留着刚才编辑的内容。
为什么不能直接写在渲染里?因为在组件函数体里直接调 setTitle会立刻触发新一轮渲染,而那一轮又会再调一次 —— 死循环。useEffect + 依赖数组保证了 「只在 noteToEdit 真的变了的时候才动手」。
Here is the problem. The text in NoteForm’s inputs lives in its own state (title / content). But “which note’s Edit did the user press” happens outside, and arrives through the noteToEdit prop.
So you need one rule: “whenever noteToEdit changes, set the two inner pieces of state to its values”. That is precisely useEffect(fn, [noteToEdit]).
The else branch matters too: when noteToEdit goes back to null (the edit is done, edit mode is over), the form has to be cleared. Without that else, the text you were just editing is still sitting there after you submit.
Why not put it straight in the render?Because calling setTitle in the component body schedules another render immediately, and that render calls it again — a dead loop. useEffect plus a dependency array guarantees you only act when noteToEdit really changed.
react-notes-app/src/components/NoteForm/index.tsx依赖数组写错的三种后果Three things that go wrong when the dependency array is wrong
拿这段代码做实验,三种写法对应三种病:
[]—— 只在首次渲染跑一次。点 Edit 时noteToEdit变了,但 effect 不再执行。症状:点 Edit 按钮文字变成了 Update, 但输入框是空的。(因为按钮文字是渲染时直接读 prop 算的,不依赖 effect。)- 什么都不写 —— 每次渲染后都跑。 effect 里调了 setTitle → 重渲染 → 又跑 → ……症状:Maximum update depth exceeded,页面卡死。
[noteToEdit, title, content]—— 把自己改的 state 也放进依赖。 setTitle 改了 title → title 变了 → effect 又跑 → setTitle 又执行 → ……症状:同样死循环。
这条特别值得记:effect 里改的 state, 不要放进它自己的依赖数组。
Take that code and try three versions of the array. Each one gets you a different illness:
[]— runs once after the first render.noteToEditchanges when you press Edit, but the effect never runs again. Symptom: pressing Edit turns the button into Update, but the inputs stay empty. (The button text is computed from the prop during render, so it does not need the effect.)- Nothing at all — runs after every render. The effect calls setTitle → re-render → runs again → ... Symptom: Maximum update depth exceeded, and the page freezes.
[noteToEdit, title, content]— the state the effect itself writes is in its own dependencies. setTitle changes title → title changed → the effect runs again → setTitle again → ... Symptom: the same dead loop.
This one is worth committing to memory: state that an effect writes does not belong in that effect’s dependency array.
动手做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.
这是 NoteForm 里那个决定 Task 3 成败的 effect。 两个空:一个分支条件,一个依赖数组。
This is the effect in NoteForm that decides whether Task 3 passes. Two blanks: one branch condition, one dependency array.
点某一行的 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.
初学者常见的几种写法错误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.
useEffect。Calling a setter during render immediately schedules another render, and that render calls the setter again.Rendering has to be pure is a basic React rule: the component function only turns the current props and state into JSX, and does nothing else. Anything else belongs in useEffect.[] = 只在首次渲染后跑一次。首次渲染时noteToEdit 是 null, 所以什么都没发生;后面点 Edit,effect 不再执行。这个 bug 特别迷惑人:按钮文字会正确变成 "Update"(那是渲染时直接读 prop 算的), 让人以为「Edit 生效了」,但输入框是空的。
[] means the effect runs once, after the first render. On that first render noteToEdit is null, so nothing happens, and when you click Edit later the effect never runs again.This bug is confusing: the button text does change to "Update", because that is computed straight from the prop during render. It looks like Edit worked, but the inputs are empty.
换一道题也能用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.
- useEffect 在渲染完成后跑,依赖数组决定它跑不跑。useEffect runs after the render is finished, and the dependency array decides whether it runs at all.
- [] 只跑一次;[a] 在 a 变化时跑;不写则每次渲染都跑(通常是错的)。[] runs once. [a] runs when a changes. No array at all runs after every render, which is usually wrong.
- NoteForm 那个 effect 的职责是「把外部的 noteToEdit 同步进内部两个 state」。The job of that effect in NoteForm is to copy the outside noteToEdit into its two inside state values.
- else 分支负责在退出编辑时清空表单,不能省。The else branch clears the form when editing ends. Do not leave it out.
- effect 里改的 state 不能放进它自己的依赖数组,否则死循环。State that the effect itself changes must not be in that effect's dependency array, or it loops forever.