默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。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.
0 / 105道自评过self-assessed
0会Got it0模糊Shaky0不会No idea105还没做Not seen
正在读这台浏览器里的标记…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 — 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
In one line: hooks are functions that start with use and give function components state and side effects.
They fix three real pain points — these three are the standard answer:
Reusing logic was hard. All you had were HOCs and render props, and both leave you with wrapper nesting (see #335). A custom hook is flat.
Logic got chopped up by the lifecycle. One coherent “subscribe and unsubscribe” had to be split across two lifecycle methods; useEffect keeps them side by side.
this was too easy to get wrong. Function components have no this.
The common ones:useState, useEffect, useContext, useRef, useMemo, useCallback, useReducer; React 18 added useId, useTransition, useDeferredValue and useSyncExternalStore.
Two rules — always say them:
Call them at the top level only — never inside an if, a loop, or a nested function.
Call them only from a function component or a custom hook.
Why the first rule exists — that is the follow-up. React does not know what your hook is called. It keeps each hook’s state in a linked list, indexed by call order. Put a hook inside an if, skip it on one render, and every index after it shifts — useState hands you somebody else’s value. Say “by call order, not by name” and they know you really understand it.
JSX为什么不能写在条件里示意Illustrative
1// ✗ 顺序会变,后面所有 hook 错位
2functionBad({show}){
3if(show){
4const[a]=useState(1);// 有时调有时不调
5}
6const[b]=useState(2);// b 可能拿到 a 的槽位
7}
8
9// ✓ hook 在顶层,条件放里面
10functionGood({show}){
11const[a]=useState(1);
12const[b]=useState(2);
13useEffect(()=>{
14if(!show)return;// 条件判断放 effect 内部
15// ...
16},[show]);
17}
1// ✗ the order changes, and every hook after it shifts
2functionBad({show}){
3if(show){
4const[a]=useState(1);// called sometimes, skipped other times
5}
6const[b]=useState(2);// b may end up in a's slot
7}
8
9// ✓ hooks at the top level, the condition inside
10functionGood({show}){
11const[a]=useState(1);
12const[b]=useState(2);
13useEffect(()=>{
14if(!show)return;// put the condition inside the effect
In one line:useMemo caches “what a function returns”, useCallback caches “the function itself”. Both use the dependency array to decide whether to recompute.
In fact useCallback(fn, deps) is exactly the same thing as useMemo(() => fn, deps) — the former is sugar for the latter. Saying this scores points.
What it caches
When to use it
useMemo
A computed result (value / object / array)
① the work is genuinely expensive (sorting or filtering a big list) ② the result goes to a memo component as a prop ③ the result is a dependency of another hook
useCallback
A function reference
① the function goes to a memo component ② the function is a dependency of useEffect③ the function is part of a custom hook’s public API
When not to use them — most people miss this half: both cost something — the old value is kept and the dependencies get compared. Wrapping a + b in useMemo is a pure loss. “Measure first, then optimise”: leave them out by default and add them when the profiler says so.
The most common misuse: wrapping something in useCallback but putting a value that changes every render in the dependency array — the cache never hits, and you paid for the comparison on top.
Follow-up: “What changes with the React 19 compiler?” — React Compiler can insert memoisation for you, so most hand-written useMemo and useCallback will stop being necessary. Knowing the direction shows you are keeping up.
In one line:React.memo works at the “component” level — it decides whether to re-render a whole component; useMemo works at the “value” level — it decides whether to recompute one value.
React.memo
useMemo
What it is
A higher-order component
A hook
Where it goes
Wrapped around the component
Inside the component
What it compares
props (shallow)
the dependency array
What it saves
one component render
one computation
The key point: all three go together. Dropping React.memo on a child usually does nothing at all — the parent hands down fresh object and function literals on every render, so the shallow compare is bound to say “changed”. You must also stabilise objects with useMemo and functions with useCallback.
Follow-up: “Can memo stop a context change?” — no it cannot. memo only compares props; context travels a separate channel. That is why a context value has to be wrapped in useMemo — exactly the point of our theme switching variant, where deleting the useMemo left every behaviour test green and only the reference-stability test red. “Does children break memo?” — yes. children is a prop too, and JSX produces a new element object every time.
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.