默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。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.
找一道题Find one按方向、掌握状态筛Filter by topic and markReact 与生态React & ecosystem
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.
In one line: pull “logic that carries state” out so it can be reused. The name must start with use.
Why the use prefix is mandatory — this is the point being tested, and it is not about style:
ESLint uses the prefix to recognise it as a hook so it can enforce the rules of hooks (react-hooks/rules-of-hooks). Without the prefix, you can break those rules inside it and nobody warns you.
It is also a signal to whoever reads the code: this function may hold state, so there are limits on where you may call it.
The key idea: you reuse the logic, not the state. Two components that each call useCounter() get two completely independent pieces of state. To share state you need Context or a state library. This is a frequent follow-up and a lot of people get it wrong.
When to extract one: the same combination of useState and useEffect shows up in two or more places; or the effect logic in one component has grown long enough that you can no longer read the component itself.
Follow-up: “What can a custom hook return?” — anything. The convention is “return an array like useState does” (so the caller can rename freely) and “return an object once there are three or more values” (so nobody has to remember the order). “Can it call other hooks?” — yes, that is the whole point; the same two rules still apply.
Open with this sentence, then list the techniques:“First I use the React DevTools Profiler to find which component is slow and how many times it renders, then I decide what to touch.” Reciting useMemo straight away sounds like you memorised an answer sheet.
Three families of technique:
① Render less
React.memo + useMemo + useCallbackused as a set (#346)
Push state down — put frequently changing state in the small component that actually needs it instead of lifting it to the top and re-rendering the whole tree. This often helps far more than adding memo.
Compose with children — when the parent re-renders, children passed in as a prop are not rebuilt.
Split your contexts — put too much in one context and changing any field re-renders every consumer.
② Download less
Code splitting — React.lazy + Suspense, split per route (#347)
Import third-party libraries piecemeal, not import _ from "lodash"
Run a bundle analyzer to see who is taking up the space
③ Compute less, paint less
Virtualise long lists — render only the few dozen rows in the viewport. On a ten-thousand-row list this beats every other optimisation put together.
Use stable ids as list keys (#330)
Debounce input, throttle search
React 18’s useTransition / useDeferredValue — keep the heavy work from blocking typing
Follow-up: “How do you know there are wasted renders?” — the Profiler’s “Highlight updates”, or <Profiler onRender>; and remember StrictMode renders twice in development, so do not mistake that for a bug.
In one line: five ways, each with a clear trade-off.
Approach
Upside
Cost
Plain CSS / SCSS files
Free, and every CSS feature is available
Class names are global, so they collide
CSS Modules
Hashed class names, isolated by default
Dynamic styles need CSS variables
Inline style
The most direct way to use a dynamic value
No pseudo-classes, media queries or animations; a new object every render
CSS-in-JS (styled-components)
props can drive the styles, and scoping needs no extra work
Runtime cost, and SSR needs extra setup
Atomic (Tailwind)
No naming, and the output size stays under control
Very long class strings in JSX, and the team needs conventions
The recommended way to do dynamic styles — this is the bonus point: use a CSS variable, not an inline style. Put only the variable inline and leave the actual rule in the CSS file — you get the dynamic value and keep pseudo-classes and media queries. That is how this site’s dark mode works (flip the data-theme attribute and the whole set of CSS variables swaps).
Follow-up: “Why do inline styles hurt performance?” — every render creates a new object, which breaks memo on the child, and the browser cannot cache it as a rule. If you must use one, stabilise it with useMemo.
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.