默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。Only the question shows by default. Answer it in your head first, then open the answer. Mark the ones you miss; the flashcard round puts those first.
正在读这台浏览器里的标记…Reading your marks from this browser…
标记不是打分,是给下一轮排队Marks are a queue, not a score
标「不会」的题会排到抽认卡最前面,标「会」的进低频池排最后。所以别客气 —— 觉得答得磕磕巴巴就标「模糊」。准备好了就去抽认卡。“No idea” jumps to the front of the next round; “got it” drops to the back. So be honest — if it came out shaky, mark it shaky. Then go run a flashcard round.
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 componentDidMount and componentWillUnmount; useEffect lets them sit together.
Reusing logic needs no nesting. Three stacked HOCs turn into wrapper hell; custom hooks stay flat.
The this problem 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.
会追问:「请求为什么不放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)
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.
In one line — say this precisely:useEffectis 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 存进 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.
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.
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.
会追问:「提太高怎么办?」—— 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.
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.
In one line:React.PureComponent ships with a shallow-comparisonshouldComponentUpdate — 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.
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 div inside 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 every flex property 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.
JSXFragment 的两个真实场景示意Illustrative
1// ✗ tr 里多一层 div:非法 HTML,布局会坏
2<tr><div><td>A</td><td>B</td></div></tr>
3
4// ✓
5<tr><><td>A</td><td>B</td></></tr>
6
7// 列表里要 key,就不能用简写
8{rows.map((r)=>(
9<React.Fragmentkey={r.id}>
10<dt>{r.term}</dt>
11<dd>{r.desc}</dd>
12</React.Fragment>
13))}
1// ✗ an extra div inside tr: invalid HTML, and the layout breaks
2<tr><div><td>A</td><td>B</td></div></tr>
3
4// ✓
5<tr><><td>A</td><td>B</td></></tr>
6
7// A list needs a key, so the short form cannot be used
自定义 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-statics exists 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 user prop shows up in the component and you cannot tell which HOC injected it.
Name collisions — two HOCs both injecting data fight 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.
JSXHOC 与 hook 的对比示意Illustrative
1// HOC
2functionwithAuth(Comp){
3returnfunctionWrapped(props){
4constuser=useUser();
5if(!user)return<Login/>;
6return<Comp{...props}user={user}/>;// 记得透传 props
7};
8}
9
10// 同一件事用 hook:平的,而且来源一眼看得出
11functionPage(){
12constuser=useUser();// ← 明确知道 user 从哪来
13if(!user)return<Login/>;
14return<Contentuser={user}/>;
15}
1// HOC
2functionwithAuth(Comp){
3returnfunctionWrapped(props){
4constuser=useUser();
5if(!user)return<Login/>;
6return<Comp{...props}user={user}/>;// remember to pass props through
7};
8}
9
10// The same thing with a hook: flat, and you can see where the value came from
11functionPage(){
12constuser=useUser();// ← it is obvious where user comes from
99 道来自面试题库 #269–#387;TypeScript 深度那 6 道是 DrillLab 自出的(senior 补强,卡片上有标注)。答案都是 DrillLab 写的,所以讲解里的代码块一律标「示意」。每道题都能点回它出处的那一节课。99 questions come from the interview bank (#269–#387); the 6 TypeScript deep-dive ones are DrillLab-made (marked on the card). All answers are written by DrillLab, so every code block here is labelled “demo”. Each card links back to the lesson it came from.