组件与通信 · 十一问11 questions on components and how they communicate
函数 vs 类组件、生命周期、useEffect 对应关系、props vs state、组件通信、受控 vs 非受控、props drilling、PureComponent、Fragment、状态提升、HOC。Function vs class components, lifecycle, how useEffect maps onto it, props vs state, talking between components, controlled vs uncontrolled, props drilling, PureComponent, Fragment, lifting state up, HOC.
这一页有什么On this page12
- 01 函数组件 vs 类组件Function components vs class components
- 02 React 的生命周期有哪些What are the React lifecycle methods?
- 03 useEffect 和生命周期怎么对应How does useEffect map onto the lifecycle?
- 04 props vs state
- 05 组件之间怎么通信How do components talk to each other?
- 06 受控组件 vs 非受控组件Controlled components vs uncontrolled components
- 07 什么是状态提升What is lifting state up?
- 08 什么是 props drillingWhat is props drilling?
- 09 什么是 Pure ComponentWhat is a Pure Component?
- 10 什么是 FragmentWhat is a Fragment?
- 11 什么是 HOCWhat is a HOC?
- 迁移模式Transfer
- 把类组件的生命周期一一映射到 useEffect 的写法Map each class component lifecycle method onto the matching useEffect call
- 说清 props 和 state 的三处差别,并解释为什么 props 不能改Explain three ways props and state differ, and why props cannot be changed
- 列出组件通信的五种方式并说明各自的适用场景List the five ways components talk to each other, and when each one fits
- 分清受控和非受控,并说出各自的选择理由Tell controlled and uncontrolled inputs apart, and give a reason for picking each
这一组和 Q1 那道真题重合度最高:受控输入、状态提升、props 往下事件往上,都是那道题的直接考点。生命周期与 useEffect 的对应关系是从类组件时代过来的人必被问的一题。This group overlaps the real Q1 question more than any other: controlled inputs, lifting state up, and props going down while events go up are all tested there directly. How the lifecycle maps onto useEffect is always asked of anyone who started in the class component era.
函数组件 vs 类组件Function components vs class components
#322 Functional components vs Class components
一句话:现在一律写函数组件。 类组件只在维护老代码和写错误边界时才用 (错误边界目前还只能用 class)。
| 类组件 | 函数组件 | |
|---|---|---|
| 状态 | this.state / setState | useState |
| 副作用 | 生命周期方法 | useEffect |
this | 要处理绑定 | 没有 this,不存在这问题 |
| 逻辑复用 | HOC / render props(嵌套很深) | 自定义 hook(平铺) |
| 代码量 | 多 | 少 |
为什么官方推函数组件—— 答这三条比列表格更有说服力:
- 逻辑能按关注点组织, 而不是按生命周期切碎。 类组件里「订阅」和「取消订阅」被迫分在
componentDidMount和componentWillUnmount两个方法里;useEffect让它们写在一起。 - 复用逻辑不用套娃。HOC 叠三层就变成「wrapper 地狱」, 自定义 hook 是平的。
this的问题彻底消失。
会追问:「函数组件里怎么拿 shouldComponentUpdate?」——React.memo, 但它默认是浅比较,需要自定义就传第二个参数。
In one line: write function components, always. Class components are for maintaining old code and for error boundaries, which still have to be classes.
| Class component | Function component | |
|---|---|---|
| State | this.state / setState | useState |
| Side effects | Lifecycle methods | useEffect |
this | You deal with binding | No this, so no such problem |
| Reusing logic | HOC / render props (deep nesting) | Custom hooks (flat) |
| Amount of code | More | Less |
Why the official line favours function components — these three points land better than the table:
- Logic groups by concern instead of being sliced up by lifecycle. In a class, subscribing and unsubscribing are forced apart into
componentDidMountandcomponentWillUnmount;useEffectlets them sit together. - Reusing logic needs no nesting. Three stacked HOCs turn into wrapper hell; custom hooks stay flat.
- The
thisproblem disappears completely.
Follow-up: “How do you get shouldComponentUpdate in a function component?” — React.memo, though it compares shallowly by default; pass a second argument when you need your own comparison.
React 的生命周期有哪些What are the React lifecycle methods?
#323 Explain the React component lifecycle and its methods
一句话:三个阶段 ——挂载、更新、卸载。
- 挂载:
constructor→getDerivedStateFromProps→render→componentDidMount(DOM 已经有了,发请求、订阅、操作 DOM 都在这) - 更新:
getDerivedStateFromProps→shouldComponentUpdate(返回 false 就跳过渲染)→render→getSnapshotBeforeUpdate→componentDidUpdate(这里改 state 必须加条件, 否则死循环) - 卸载:
componentWillUnmount(清定时器、解绑监听、取消请求) - 出错:
getDerivedStateFromError+componentDidCatch(错误边界,见 #333)
三个被废弃的要知道:componentWillMount、componentWillReceiveProps、componentWillUpdate。原因是 Fiber 的 render 阶段可能被中断和重跑, 这几个方法可能被调用多次, 放在里面的副作用会重复执行。能说出这个原因很加分。
会追问:「请求为什么不放componentWillMount?」—— 除了上面的原因, 它在 SSR 时也会执行, 而且并不会更早拿到数据 (请求是异步的,反正要等)。
In one line: three phases — mounting, updating, unmounting.
- Mounting:
constructor→getDerivedStateFromProps→render→componentDidMount(the DOM exists now, so fetching, subscribing and DOM work all belong here) - Updating:
getDerivedStateFromProps→shouldComponentUpdate(return false to skip the render) →render→getSnapshotBeforeUpdate→componentDidUpdate(setting state here needs a condition, or you get an infinite loop) - Unmounting:
componentWillUnmount(clear timers, detach listeners, cancel requests) - On error:
getDerivedStateFromError+componentDidCatch(error boundaries, see #333)
Know the three that were deprecated: componentWillMount, componentWillReceiveProps, componentWillUpdate. The reason is that Fiber can interrupt and re-run the render phase, so these could fire more than once and any side effect inside them would run twice. Giving that reason earns real credit.
Follow-up: “Why not fetch in componentWillMount?” — besides the reason above, it also runs during SSR, and it does not get the data any sooner: the request is async, so you wait either way.
useEffect 和生命周期怎么对应How does useEffect map onto the lifecycle?
#325 UseEffect vs Lifecycle Methods
一句话(这句要说准):useEffect不是生命周期的替代品, 它是「同步副作用」的另一种思路—— 你声明「这个副作用依赖哪些值」, 值变了它就重新跑。
| 类组件 | useEffect 写法 |
|---|---|
componentDidMount | useEffect(fn, []) |
componentDidUpdate | useEffect(fn, [dep]) |
componentWillUnmount | effect 里 return () => {} |
| 三个都要 | useEffect(fn)(不写依赖数组) |
getSnapshotBeforeUpdate | useLayoutEffect(DOM 更新后、浏览器绘制前同步执行) |
但这张表有个陷阱——useEffect(fn, [])不完全等于 componentDidMount: 前者在浏览器绘制之后异步执行,后者是同步的。所以用 useEffect测量 DOM 再改样式会闪一下, 这种情况要用useLayoutEffect。
更重要的是别用生命周期的思维写 effect。正确的问法不是「我要在挂载时干什么」, 而是「这个副作用依赖哪些值」。 依赖列全,React 自然会在该跑的时候跑。
会追问:「清理函数什么时候执行?」——依赖变化前和卸载时。我们那道计时器变式题就是这个考点: 漏了 clearInterval, start/pause 四次会得到 10 秒而不是 4 秒(实测)。
In one line — say this precisely: useEffect is not a replacement for lifecycle methods; it is a different way of thinking about synchronising side effects — you declare which values a side effect depends on, and it re-runs when they change.
| Class component | useEffect form |
|---|---|
componentDidMount | useEffect(fn, []) |
componentDidUpdate | useEffect(fn, [dep]) |
componentWillUnmount | return () => {} inside the effect |
| All three at once | useEffect(fn) (no dependency array) |
getSnapshotBeforeUpdate | useLayoutEffect (runs synchronously after the DOM updates, before the browser paints) |
But the table has a trap — useEffect(fn, []) is not quite componentDidMount: the first runs asynchronously after the browser paints, the second is synchronous. So measuring the DOM in a useEffect and then changing styles will flash; that case wants useLayoutEffect.
More important: stop writing effects with a lifecycle mindset. The right question is not “what do I do on mount” but “which values does this side effect depend on”. List the dependencies properly and React runs it when it should.
Follow-up: “When does the cleanup function run?” — before the dependencies change and on unmount. Our timer variant question tests exactly this: drop the clearInterval and four start/pause rounds give you 10 seconds instead of 4 (measured).
props vs state
#327 props vs state
一句话:props 是父组件传进来的、只读;state 是组件自己的、可变。
| props | state | |
|---|---|---|
| 谁拥有 | 父组件 | 组件自己 |
| 能否修改 | 不能(只读) | 能,通过 setState |
| 变了会重渲染吗 | 会 | 会 |
为什么 props 必须只读?因为组件的渲染函数应该是纯的(见 #293):同样的 props 渲染出同样的 UI。 改了 props 就等于改了「输入」, 父组件下次渲染又会把它覆盖回去 —— 数据源变成两个,谁也说不清现在该信谁。
怎么判断该用哪个(实用判据):
- 能从 props 或别的 state 算出来→ 都别放,直接算(派生数据)
- 只有这个组件关心、且会变 → state
- 多个组件都要用 → 提到共同父级(#345)
- 整棵树都要用且不常变 → Context
会追问:「能把 props 存进 state 吗?」——能但通常是 bug:useState(props.value)只在首次渲染取值, 之后 props 变了 state 不会跟着变。 只有「需要一个可编辑的初始值」时才这么做, 而且要想清楚 props 变化时要不要重置。Q1 那道题的编辑功能就是这个场景, 它用 useEffect 显式同步。
In one line: props come from the parent and are read-only; state belongs to the component and can change.
| props | state | |
|---|---|---|
| Who owns it | The parent | The component itself |
| Can you change it | No (read-only) | Yes, through setState |
| Does a change re-render | Yes | Yes |
Why must props be read-only? Because a render function is supposed to be pure (see #293): the same props render the same UI. Changing props means changing the input, and the parent will overwrite it on its next render — now you have two sources of truth and nobody can say which one to trust.
How to decide which one you need — a practical test:
- You can compute it from props or other state → store neither, just compute it (derived data)
- Only this component cares, and it changes → state
- Several components need it → lift it to their common parent (#345)
- The whole tree reads it and it rarely changes → Context
Follow-up: “Can you put props into state?” — you can, and it is usually a bug: useState(props.value) reads the value on the first render only, so later prop changes never reach the state. Do it only when you need an editable initial value, and think through whether a prop change should reset it. The edit feature in the real Q1 question is that scenario — it syncs explicitly with a useEffect.
组件之间怎么通信How do components talk to each other?
#328 Communication between components
一句话:五种,按「距离」从近到远选。
- 父 → 子:props。
- 子 → 父: 父把回调函数当 props 传下去, 子调用它。「事件往上报」就是这个。
- 兄弟之间:状态提升到共同父级, 再分别往下传(#345)。
- 跨很多层:Context—— 适合主题、当前用户、语言这种 「整棵树都要读、又不常变」的值。
- 全局、复杂、多处修改: 状态库(Redux / Zustand / Jotai) 或服务端状态库(TanStack Query)。
还有两个偏门但会问的:ref +useImperativeHandle(父组件主动调子组件的方法, 比如 focus()、play()); 以及 props.children(组合优于配置,也是解决 props drilling 的一招)。
会追问:「什么时候该上状态库?」—— 判据:同一份状态被很多不相关的组件读写、 或者需要时间旅行调试 / 中间件。只是「层数深」不该上 Redux, Context 或组合就够—— 这个回答比「大项目就用 Redux」好得多。
In one line: five ways, and you pick by distance — nearest first.
- Parent → child: props.
- Child → parent: the parent passes a callback down as a prop and the child calls it. That is all “events go up” means.
- Between siblings: lift the state to their common parent and pass it back down to each one (#345).
- Across many levels: Context — good for theme, current user, language: values the whole tree reads and that rarely change.
- Global, complex, written from many places: a state library (Redux / Zustand / Jotai) or a server-state library (TanStack Query).
Two less common ones they still ask about: ref plus useImperativeHandle (the parent calls a method on the child, say focus() or play()); and props.children (composition over configuration, which is also one answer to props drilling).
Follow-up: “When should you reach for a state library?” — the test: one piece of state is read and written by many unrelated components, or you need time-travel debugging or middleware. Depth alone is no reason for Redux — Context or composition is enough — that answer beats “big project, use Redux” by a mile.
受控组件 vs 非受控组件Controlled components vs uncontrolled components
#329 Controlled component vs uncontrolled component
一句话:受控 = 值由 React state 说了算(value + onChange);非受控 = 值由 DOM 自己保管, 需要时用 ref 去读。
| 受控 | 非受控 | |
|---|---|---|
| 值放哪 | React state | DOM 节点 |
| 怎么读 | 直接读 state | ref.current.value |
| 每次输入重渲染 | 会 | 不会 |
| 实时校验 / 联动 | 容易 | 难 |
| 代码量 | 多 | 少 |
默认用受控。因为一旦要做「输入为空就禁用提交按钮」 「实时显示字数」「两个字段联动」, 受控是唯一顺手的方式 ——Q1 那道真题的校验要求就是这样。
非受控的两个真实场景:<input type="file">(只能非受控,出于安全 JS 不能设它的值); 以及性能敏感的大表单 (每次按键都重渲染整个表单时)。
会追问(高频):「value 传了但没传onChange 会怎样?」—— 输入框变成只读, 打字没反应,React 还会警告。
「怎么给受控组件一个初始值又不锁死?」——defaultValue,但那就是非受控了。
「value={undefined} 呢?」—— React 会把它当成非受控,然后在你后来传了值时报「从非受控变成受控」的警告。 所以初始值该写 "" 而不是undefined。
In one line: controlled = React state owns the value (value + onChange); uncontrolled = the DOM keeps the value and you read it with a ref when you need it.
| Controlled | Uncontrolled | |
|---|---|---|
| Where the value lives | React state | The DOM node |
| How you read it | Straight from state | ref.current.value |
| Re-renders on every keystroke | Yes | No |
| Live validation / linked fields | Easy | Hard |
| Amount of code | More | Less |
Default to controlled. The moment you need “disable submit while the field is empty”, a live character count, or two fields that react to each other, controlled is the only comfortable option — the validation requirement in the real Q1 question works exactly that way.
Two real cases for uncontrolled: <input type="file"> (it can only be uncontrolled — for security reasons JS cannot set its value); and performance-sensitive large forms, where every keystroke would otherwise re-render the whole form.
Follow-ups — these come up a lot: “What happens if you pass value but no onChange?” — the input goes read-only, typing does nothing, and React warns you.
“How do you give a controlled component an initial value without locking it?” — defaultValue, but that makes it uncontrolled.
“And value={undefined}?” — React treats it as uncontrolled, then warns you about switching from uncontrolled to controlled once you do pass a value. So the initial value should be "", not undefined.
什么是状态提升What is lifting state up?
#345 What is Lifting State Up in React
一句话:两个兄弟组件要共享同一份数据时,把 state 移到它们最近的共同父级, 再通过 props 往下传、通过回调往上报。
为什么必须这样?因为 React 是单向数据流—— 数据只能往下流,兄弟之间没有通道。唯一的共享点就是共同祖先。
做法三步:① 找到最近的共同父级; ② state 搬上去; ③ 父级把「值」和「改值的函数」 分别传给两个孩子。
代价(要主动说):提得越高,中间层被迫透传的 props 越多(这就是 props drilling,#331), 而且父级重渲染会带着整棵子树重渲染。所以原则是「提到刚好够用的那一层,别更高」。
Q1 那道真题就是一个标准的状态提升:notes 放在NoteManager(父), 表单和表格都是它的孩子; 表单提交时调用父传下来的回调。
会追问:「提太高怎么办?」—— Context、组合(children)、 或者状态库。注意 Context 解决的是「传递」, 不是「共享」—— state 该放哪还是得想清楚。
In one line: when two sibling components need the same data, move the state up to their closest common parent, then pass it down through props and report back through callbacks.
Why does it have to work this way? Because React has one-way data flow — data only travels down, and siblings have no channel between them. The only shared point is a common ancestor.
Three steps: (1) find the closest common parent; (2) move the state up there; (3) have the parent hand the value and the function that changes it to each child.
The cost — bring it up yourself: the higher you lift, the more props the middle layers are forced to pass through (that is props drilling, #331), and a parent re-render drags the whole subtree with it. So the rule is: lift to the lowest layer that works, and no higher.
The real Q1 question is a textbook lift: notes lives in NoteManager (the parent), and both the form and the table are its children; on submit the form calls the callback the parent passed down.
Follow-up: “What if it is lifted too high?” — Context, composition (children), or a state library. Note that Context solves delivery, not sharing — you still have to decide where the state belongs.
什么是 props drillingWhat is props drilling?
#331 What is props drilling
一句话:为了把数据送到深处的组件,中间那些根本不用它的组件被迫一层层往下传。
为什么是问题:
- 中间组件被污染—— 它的 props 签名里出现了跟它无关的字段, 可复用性下降。
- 改一处动一串—— 加一个字段要改路径上每一个组件。
- 额外重渲染—— 值变了整条路径都重渲染。
四种解法,按代价从小到大:
- 组合 /
children——最被低估的一招。 把元素直接当 props 传下去, 中间层就不用知道它需要什么数据。 - Context—— 适合主题、用户、语言这类 「整棵树都读、不常变」的值。我们那道主题切换变式题就是这个。
- 状态库—— 复杂全局状态。
- 把组件树重新拆一下—— 有时 drilling 只是拆分方式不合理的症状。
会追问:「传两三层也要上 Context 吗?」——不要。 两三层的 props 是清晰的、可追踪的; Context 会让「这个值从哪来」变得不明显, 而且 context 一变所有消费者都重渲染。「drilling 超过三四层且中间层完全无关」才值得上。
In one line: to get data down to a deep component, the components in between that have no use for it are forced to pass it along, level by level.
Why that is a problem:
- The middle components get polluted — fields that have nothing to do with them show up in their props signature, so they get less reusable.
- One change touches a whole chain — adding a field means editing every component on the path.
- Extra re-renders — when the value changes, the entire path re-renders.
Four fixes, cheapest first:
- Composition /
children— the most underrated one. Pass elements down as props and the middle layers never need to know what data they want. - Context — good for theme, user, language: values the whole tree reads and that rarely change. Our theme-switching variant question is this one.
- A state library — for complex global state.
- Re-splitting the component tree — sometimes drilling is just a symptom of a bad split.
Follow-up: “Do two or three levels need Context?” — no. Two or three levels of props are clear and easy to trace; Context makes “where did this value come from” invisible, and every consumer re-renders when the context changes. It pays off once drilling passes three or four levels and the middle layers are completely unrelated.
什么是 Pure ComponentWhat is a Pure Component?
#336 What are Pure Component
一句话:React.PureComponent自带一个浅比较的shouldComponentUpdate—— props 和 state 浅比较都没变就跳过渲染。 函数组件的对应物是React.memo。
「浅比较」是本题全部的考点。它对每个 prop 用Object.is 比一层。 所以:
- 传对象/数组/函数字面量 → 优化完全失效, 因为每次渲染都是新引用。
- 内部深层改了对象 → 不会重渲染, 因为引用没变 —— 界面就不更新了。
所以 PureComponent /memo 和「不可变更新」是一对: 你必须每次造新对象, 浅比较才能正确地判断出「变了」。反过来,如果你就地改对象, 加了 memo 反而会制造 bug。
会追问:「怎么让 memo 真正生效?」—— 对象和数组用 useMemo、 函数用 useCallback稳住引用。三个必须配套用, 只加 memo 往往一点用没有 (见 #346)。
「是不是所有组件都该 memo?」—— 不是。浅比较本身也有成本,props 多且经常变的组件, 加了反而更慢。
In one line: React.PureComponent ships with a shallow-comparison shouldComponentUpdate — if a shallow compare of props and state finds nothing changed, it skips the render. The function-component equivalent is React.memo.
The shallow compare is the whole point of this question. It runs Object.is on each prop, one level deep. So:
- Pass an object, array or function literal and the optimisation is dead, because every render creates a new reference.
- Mutate something deep inside an object and it will not re-render, because the reference never changed — and the UI just stops updating.
So PureComponent / memo and immutable updates come as a pair: you have to build a new object every time for the shallow compare to see a change. The reverse holds too — mutate in place and adding memo manufactures bugs.
Follow-up: “How do you make memo actually work?” — stabilise the references: useMemo for objects and arrays, useCallback for functions. The three go together; memo on its own often does nothing at all (see #346).
“Should every component be memoised?” — no. The shallow compare costs something too, so a component with many frequently changing props gets slower, not faster.
什么是 FragmentWhat is a Fragment?
#338 React Fragment
一句话:一个不产生真实 DOM 节点的容器, 用来满足「JSX 必须有单一根节点」的要求, 同时不往页面里多塞一层div。
写法两种:<React.Fragment>和简写 <></>。
为什么需要它(举得出场景才算答好):
- 表格——
<tr>里套一层div是非法 HTML, 浏览器会把它挪出去,布局直接坏。 - Flex / Grid 布局—— 多一层
div会打断父容器和子项的直接关系,flex属性全失效。 - 减少 DOM 层数, 样式选择器也更好写。
会追问:「Fragment 上能加 key 吗?」——能,但必须用完整写法<React.Fragment key={id}>, 简写 <> 不支持任何属性。在列表里渲染多个兄弟元素时就得这么写, 这是简写唯一的限制。
In one line: a container that produces no real DOM node, so you can satisfy “JSX needs a single root” without pushing another div into the page.
Two forms: <React.Fragment> and the shorthand <></>.
Why you need it — you only answer this well with concrete cases:
- Tables — a
divinside a<tr>is invalid HTML; the browser hoists it out and the layout breaks on the spot. - Flex / Grid layouts — an extra
divbreaks the direct relationship between the container and its items, so everyflexproperty stops working. - Fewer DOM levels, and easier style selectors.
Follow-up: “Can a Fragment take a key?” — yes, but only in the long form <React.Fragment key={id}>; the shorthand <> accepts no attributes at all. You need this whenever a list renders several sibling elements per item, and it is the shorthand’s only limitation.
什么是 HOCWhat is a HOC?
#335 What is HOC
一句话:高阶组件 ——接收一个组件、返回一个增强后的新组件的函数。它是「复用组件逻辑」的老方案。
withRouter、connect(Redux)、withStyles 都是 HOC。 本质就是 #292 的高阶函数用在组件上。
三个必须注意的点(考点在这):
- 要透传 props——
<Comp {...props} />, 不然把原来的 props 吞了。 - 要拷贝静态方法—— 包一层之后原组件的静态属性丢了 (
hoist-non-react-statics干这事)。 - ref 传不进去—— 要用
forwardRef。
为什么现在少用了(这才是重点):
- wrapper 地狱—— 叠三四层之后 DevTools 里全是嵌套, 难调试。
- props 来源不明—— 组件里出现一个
userprop, 你不知道是哪个 HOC 注进来的。 - 命名冲突—— 两个 HOC 都注入
data就打架了。
自定义 hook 解决了全部三条: 平铺、来源显式 (const user = useUser()一眼看出来)、 名字由你决定。所以现在优先写 hook。
会追问:「那 HOC 还有用吗?」—— 有两个 hook 替代不了的场合:需要「包裹」渲染结果(比如给所有页面套一层错误边界或布局)、 以及要改写 props 后再传给一个你无法修改的组件。
In one line: a higher-order component — a function that takes a component and returns an enhanced one. It is the old answer to reusing component logic.
withRouter, connect (Redux) and withStyles are all HOCs. It is nothing more than the higher-order function from #292 applied to components.
Three things you must get right — the marks are here:
- Forward the props —
<Comp {...props} />, or you swallow the ones the component already had. - Copy the statics — wrapping loses the original component’s static properties (
hoist-non-react-staticsexists for this). - Refs do not pass through — you need
forwardRef.
Why it fell out of favour — this is the real point:
- Wrapper hell — stack three or four and DevTools is nothing but nesting; debugging hurts.
- Props of unknown origin — a
userprop shows up in the component and you cannot tell which HOC injected it. - Name collisions — two HOCs both injecting
datafight each other.
Custom hooks fix all three: they stay flat, the origin is explicit (const user = useUser() says it out loud), and you choose the name. So hooks come first now.
Follow-up: “Is there still a use for HOCs?” — two places hooks cannot cover: when you need to wrap the rendered output (putting an error boundary or a layout around every page), and when you have to rewrite props before handing them to a component you cannot modify.
换一道题也能用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.
- 函数组件胜出的真正原因:逻辑按关注点组织、复用不用套娃、没有 this 问题。The real reason function components won: logic groups by topic, reuse needs no nesting, and there is no this to get wrong.
- 三个 willXxx 生命周期被废弃,因为 Fiber 的 render 阶段可能重跑。The three willXxx lifecycle methods were dropped because Fiber may run the render phase more than once.
- useEffect 不是生命周期替代品,是「声明副作用依赖什么」;[] 版在绘制后执行,不完全等于 didMount。useEffect does not replace the lifecycle. It declares what a side effect depends on. The [] version runs after paint, so it is not exactly didMount.
- props 只读是因为渲染函数必须纯;能算出来的都别存。props are read-only because a render function has to be pure; if you can compute a value, do not store it.
- 通信五种:props、回调、状态提升、Context、状态库;层数深不等于该上 Redux。Five ways to talk between components: props, callbacks, lifting state up, Context, a state library. Many levels of nesting is not by itself a reason to add Redux.
- 默认用受控;file 输入只能非受控;初始值写 "" 别写 undefined。Use controlled inputs by default; a file input can only be uncontrolled; start the value at "", not undefined.
- PureComponent / memo 是浅比较,必须配不可变更新与稳定引用才有意义。PureComponent and memo compare shallowly, so they only help if you also update without changing the original object and keep references stable.
- Fragment 解决 tr 和 flex 里多一层 div 的真实问题;要 key 得用完整写法。Fragment solves a real problem: the extra div you cannot have inside a tr or a flex container. If you need a key, write out the full form.
- HOC 的三个毛病(wrapper 地狱、来源不明、命名冲突)都被自定义 hook 解决了。The three problems with a HOC (layers of wrappers, props of unclear origin, name collisions) are all solved by a custom hook.