DrillLab
八股题库Question bank

105 道问答题,一道一卡105 questions, one card each

默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。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
0Got 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.

题目Questions

筛出 36 道(共 105 道) · 第 2 / 3 页。36 of 105 questions · page 2 / 3.
React 与生态React & ecosystem#329

受控组件 vs 非受控组件

Controlled component vs uncontrolled component

看答案Show answer

一句话:受控 = 值由 React state 说了算value + onChange);非受控 = 值由 DOM 自己保管, 需要时用 ref 去读。

受控非受控
值放哪React stateDOM 节点
怎么读直接读 stateref.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.

ControlledUncontrolled
Where the value livesReact stateThe DOM node
How you read itStraight from stateref.current.value
Re-renders on every keystrokeYesNo
Live validation / linked fieldsEasyHard
Amount of codeMoreLess

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.

JSX两种写法示意Illustrative
1// 受控:闭环 value -> onChange -> setState -> value
2const [text, setText] = useState(""); // 注意初始值是 "",不是 undefined
3<input value={text} onChange={(e) => setText(e.target.value)} />
4<button disabled={text.trim() === ""}>提交</button> {/* 校验只有受控才顺手 */}
5
6// 非受控:值在 DOM 里
7const ref = useRef(null);
8<input ref={ref} defaultValue="初始" />
9<button onClick={() => console.log(ref.current.value)}></button>
1// Controlled: a closed loop of value -> onChange -> setState -> value
2const [text, setText] = useState(""); // note the initial value is "", not undefined
3<input value={text} onChange={(e) => setText(e.target.value)} />
4<button disabled={text.trim() === ""}>Submit</button> {/* validation is easy only when controlled */}
5
6// Uncontrolled: the value lives in the DOM
7const ref = useRef(null);
8<input ref={ref} defaultValue="initial" />
9<button onClick={() => console.log(ref.current.value)}>Read</button>
React 与生态React & ecosystem#345

什么是状态提升

What is Lifting State Up in React

看答案Show answer

一句话:两个兄弟组件要共享同一份数据时,把 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.

React 与生态React & ecosystem#331

什么是 props drilling

What is props drilling

看答案Show answer

一句话:为了把数据送到深处的组件,中间那些根本不用它的组件被迫一层层往下传

为什么是问题:

  • 中间组件被污染—— 它的 props 签名里出现了跟它无关的字段, 可复用性下降。
  • 改一处动一串—— 加一个字段要改路径上每一个组件。
  • 额外重渲染—— 值变了整条路径都重渲染。

四种解法,按代价从小到大:

  1. 组合 / children——最被低估的一招。 把元素直接当 props 传下去, 中间层就不用知道它需要什么数据。
  2. Context—— 适合主题、用户、语言这类 「整棵树都读、不常变」的值。我们那道主题切换变式题就是这个。
  3. 状态库—— 复杂全局状态。
  4. 把组件树重新拆一下—— 有时 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:

  1. Composition / children the most underrated one. Pass elements down as props and the middle layers never need to know what data they want.
  2. Context — good for theme, user, language: values the whole tree reads and that rarely change. Our theme-switching variant question is this one.
  3. A state library — for complex global state.
  4. 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.

React 与生态React & ecosystem#336

什么是 Pure Component

What are Pure Component

看答案Show answer

一句话: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.

JSXmemo 生效的前提示意Illustrative
1const Row = React.memo(function Row({ item, onPick }) {});
2
3// ✗ memo 白加:两个 prop 每次都是新引用
4<Row item={{ ...raw }} onPick={() => pick(raw.id)} />
5
6// ✓ 稳住引用,memo 才有意义
7const item = useMemo(() => ({ ...raw }), [raw]);
8const onPick = useCallback((id) => pick(id), [pick]);
9<Row item={item} onPick={onPick} />
1const Row = React.memo(function Row({ item, onPick }) {});
2
3// ✗ memo achieves nothing: both props are a new reference every render
4<Row item={{ ...raw }} onPick={() => pick(raw.id)} />
5
6// ✓ keep the references stable, and memo starts to mean something
7const item = useMemo(() => ({ ...raw }), [raw]);
8const onPick = useCallback((id) => pick(id), [pick]);
9<Row item={item} onPick={onPick} />
React 与生态React & ecosystem#338

什么是 Fragment

React Fragment

看答案Show answer

一句话:一个不产生真实 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 div inside a <tr> is invalid HTML; the browser hoists it out and the layout breaks on the spot.
  • Flex / Grid layouts — an extra div breaks 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.Fragment key={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
8{rows.map((r) => (
9 <React.Fragment key={r.id}>
10 <dt>{r.term}</dt>
11 <dd>{r.desc}</dd>
12 </React.Fragment>
13))}
React 与生态React & ecosystem#335

什么是 HOC

What is HOC

看答案Show answer

一句话:高阶组件 ——接收一个组件、返回一个增强后的新组件的函数。它是「复用组件逻辑」的老方案。

withRouterconnect(Redux)、withStyles 都是 HOC。 本质就是 #292 的高阶函数用在组件上。

三个必须注意的点(考点在这):

  • 要透传 props——<Comp {...props} />, 不然把原来的 props 吞了。
  • 要拷贝静态方法—— 包一层之后原组件的静态属性丢了 (hoist-non-react-statics 干这事)。
  • ref 传不进去—— 要用 forwardRef

为什么现在少用了(这才是重点):

  • wrapper 地狱—— 叠三四层之后 DevTools 里全是嵌套, 难调试。
  • props 来源不明—— 组件里出现一个 user prop, 你不知道是哪个 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-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
2function withAuth(Comp) {
3 return function Wrapped(props) {
4 const user = useUser();
5 if (!user) return <Login />;
6 return <Comp {...props} user={user} />; // 记得透传 props
7 };
8}
9
10// 同一件事用 hook:平的,而且来源一眼看得出
11function Page() {
12 const user = useUser(); // ← 明确知道 user 从哪来
13 if (!user) return <Login />;
14 return <Content user={user} />;
15}
1// HOC
2function withAuth(Comp) {
3 return function Wrapped(props) {
4 const user = useUser();
5 if (!user) return <Login />;
6 return <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
11function Page() {
12 const user = useUser(); // ← it is obvious where user comes from
13 if (!user) return <Login />;
14 return <Content user={user} />;
15}
React 与生态React & ecosystem#324

什么是 hooks,为什么要用

What are hooks in React and Why do we use them

看答案Show answer

一句话:hooks 是一组use 开头的函数, 让函数组件也能有状态和副作用

解决三个真实痛点(这三条是标准答案):

  • 逻辑复用难。以前只有 HOC 和 render props, 两者都会造成 wrapper 嵌套(见 #335)。 自定义 hook 是平的。
  • 逻辑被生命周期切碎。一个「订阅 + 取消订阅」的完整逻辑 被迫拆到两个生命周期方法里;useEffect 让它们写在一起。
  • this 太容易出错。函数组件没有 this

常用的:useStateuseEffectuseContextuseRefuseMemouseCallbackuseReducer; React 18 加了useIduseTransitionuseDeferredValueuseSyncExternalStore

两条规则(必答):

  1. 只在最顶层调用—— 不能放在 if、循环、 嵌套函数里。
  2. 只在函数组件或自定义 hook 里调用。

为什么有第一条 —— 这是追问点。React 不知道你的 hook 叫什么名字, 它是按调用顺序把每个 hook 的状态 存在一条链表上的。 如果 hook 写在 if 里, 某次渲染少调了一个, 后面所有 hook 的下标就全错位了——useState 会拿到别人的值。
能答出「靠调用顺序而不是名字」就说明真理解了。

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:

  1. Call them at the top level only — never inside an if, a loop, or a nested function.
  2. 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 错位
2function Bad({ show }) {
3 if (show) {
4 const [a] = useState(1); // 有时调有时不调
5 }
6 const [b] = useState(2); // b 可能拿到 a 的槽位
7}
8
9// ✓ hook 在顶层,条件放里面
10function Good({ show }) {
11 const [a] = useState(1);
12 const [b] = useState(2);
13 useEffect(() => {
14 if (!show) return; // 条件判断放 effect 内部
15 // ...
16 }, [show]);
17}
1// ✗ the order changes, and every hook after it shifts
2function Bad({ show }) {
3 if (show) {
4 const [a] = useState(1); // called sometimes, skipped other times
5 }
6 const [b] = useState(2); // b may end up in a's slot
7}
8
9// ✓ hooks at the top level, the condition inside
10function Good({ show }) {
11 const [a] = useState(1);
12 const [b] = useState(2);
13 useEffect(() => {
14 if (!show) return; // put the condition inside the effect
15 // ...
16 }, [show]);
17}
React 与生态React & ecosystem#339

useMemo vs useCallback

useMemo vs useCallback

看答案Show answer

一句话:useMemo 缓存 「函数的返回值」,useCallback 缓存 「函数本身」。两者都靠依赖数组决定要不要重算。

实际上 useCallback(fn, deps)完全等价于useMemo(() => fn, deps)——后者是前者的语法糖。 这句能答出来会加分。

缓存什么什么时候用
useMemo计算结果(值 / 对象 / 数组)① 计算真的贵(大列表排序过滤) ② 结果要当 props 传给 memo 组件 ③ 结果要当别的 hook 的依赖
useCallback函数引用① 函数要传给 memo 组件 ② 函数是 useEffect 的依赖 ③ 自定义 hook 对外暴露的函数

什么时候不该用(这半边很多人答不出):两者本身都有成本—— 要存旧值、要比较依赖。 给一个 a + buseMemo 是纯亏。「先测量,再优化」; 默认不加,profiler 显示有问题再加。

最常见的误用:包了 useCallback但依赖数组里放了每次都变的东西 —— 等于没包,还多付了比较成本。

会追问:「React 19 的编译器会怎样?」—— React Compiler 能自动插入记忆化,大部分手写的useMemo /useCallback 将不再必要。 知道这个趋势会显得你在跟进。

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 cachesWhen to use it
useMemoA 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
useCallbackA 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.

JSX两者的区别与等价关系示意Illustrative
1// useMemo:缓存值
2const sorted = useMemo(() => items.sort(cmp), [items]);
3
4// useCallback:缓存函数
5const onPick = useCallback((id) => setPicked(id), []);
6
7// 两者的关系
8useCallback(fn, deps) === useMemo(() => fn, deps)
9
10// ✗ 常见误用:依赖每次都变,等于没缓存
11const onSave = useCallback(() => save(config), [{ ...config }]);
12// ↑ 每次都是新对象
1// useMemo caches a value
2const sorted = useMemo(() => items.sort(cmp), [items]);
3
4// useCallback caches a function
5const onPick = useCallback((id) => setPicked(id), []);
6
7// How the two relate
8useCallback(fn, deps) === useMemo(() => fn, deps)
9
10// ✗ a common mistake: the dependency changes every time, so nothing is cached
11const onSave = useCallback(() => save(config), [{ ...config }]);
12// ↑ a new object every render
React 与生态React & ecosystem#346

React.memo vs useMemo

React.memo vs useMemo

看答案Show answer

一句话:React.memo 是 「组件」级的 —— 决定要不要重新渲染整个组件useMemo 是 「值」级的 —— 决定要不要重新计算一个值

React.memouseMemo
是什么高阶组件hook
用在哪包在组件外面写在组件里面
比较什么props(浅比较)依赖数组
省掉什么一次组件渲染一次计算

关键:三个必须配套用。只在子组件上加 React.memo通常一点效果都没有—— 因为父组件每次渲染都会给出新的对象和函数字面量, 浅比较必然判定「变了」。必须同时用useMemo 稳住对象、useCallback 稳住函数。

会追问:memo 能拦住 context 变化吗?」——拦不住memo 只比 props, context 走另一条通道。所以 context value 必须useMemo—— 这正是我们那道主题切换变式题的核心考点, 删掉 useMemo 后实测「功能测试全绿、 只有引用稳定性那条红」。
children 会破坏 memo 吗?」—— 会,children 也是 prop, 而 JSX 每次都产生新元素对象。

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.memouseMemo
What it isA higher-order componentA hook
Where it goesWrapped around the componentInside the component
What it comparesprops (shallow)the dependency array
What it savesone component renderone 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.

React 与生态React & ecosystem#340

自定义 hook 是干什么的,命名有什么约定

What are custom hooks for and what is the naming convention for them

看答案Show answer

一句话:「带状态的逻辑」抽出来复用。 命名必须以 use 开头

为什么必须 use 开头—— 这是考点,不是风格问题:

  • ESLint 靠这个前缀识别它是 hook, 才能检查 hooks 规则 (react-hooks/rules-of-hooks)。 不加前缀,你在里面违规调用 hook 也不会有人警告你。
  • 它同时也是给读代码的人的信号:这个函数里可能有状态, 所以它有调用位置的限制

关键概念:复用的是逻辑,不是状态。两个组件各自调 useCounter(), 得到的是两份完全独立的状态。 想共享状态得用 Context 或状态库。这一条是高频追问,很多人答错。

什么时候该抽:同一组useState +useEffect 的组合在两处以上出现; 或者一个组件里的 effect 逻辑长到 让主体读不懂了。

会追问:「自定义 hook 能返回什么?」—— 随意。约定是「像 useState 一样返数组」 (调用方好重命名)、 「三个以上返对象」(不用记顺序)。
「里面能调别的 hook 吗?」—— 能,这正是它的意义;但同样要遵守两条规则。

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.

JSX自定义 hook 的形状示意Illustrative
1// 一个真实好用的:把「值 + 存 localStorage」打包
2function useLocalStorage(key, initial) {
3 const [value, setValue] = useState(() => {
4 try {
5 const raw = localStorage.getItem(key);
6 return raw ? JSON.parse(raw) : initial;
7 } catch {
8 return initial; // 隐私模式读不了就用默认值
9 }
10 });
11
12 useEffect(() => {
13 try { localStorage.setItem(key, JSON.stringify(value)); } catch {}
14 }, [key, value]);
15
16 return [value, setValue]; // 像 useState 一样返数组
17}
18
19// 用起来
20const [theme, setTheme] = useLocalStorage("theme", "light");
21
22// 注意:两个组件各调一次,得到的是两份独立状态,不是共享的
1// One that is genuinely useful: a value together with storing it in localStorage
2function useLocalStorage(key, initial) {
3 const [value, setValue] = useState(() => {
4 try {
5 const raw = localStorage.getItem(key);
6 return raw ? JSON.parse(raw) : initial;
7 } catch {
8 return initial; // private mode cannot read, so use the default
9 }
10 });
11
12 useEffect(() => {
13 try { localStorage.setItem(key, JSON.stringify(value)); } catch {}
14 }, [key, value]);
15
16 return [value, setValue]; // returns an array, the same shape as useState
17}
18
19// Using it
20const [theme, setTheme] = useLocalStorage("theme", "light");
21
22// Note: two components each calling it get two separate states, not a shared one
React 与生态React & ecosystem#343

怎么优化 React 性能

How could you improve performance in React

看答案Show answer

先说这一句,再列手段:「先用 React DevTools Profiler 找出到底哪个组件渲染慢、渲染了多少次, 再决定动哪。」上来就背 useMemo会显得像背题。

手段分三类:

① 少渲染

  • React.memo +useMemo +useCallback三件套配套用(#346)
  • state 下移—— 把频繁变的 state 放到真正需要它的那个小组件里, 别提到顶层带着整棵树重渲染。这招常常比加 memo 有效得多。
  • children 组合—— 父组件重渲染时, 作为 prop 传进来的 children不会重建
  • 拆分 Context —— 一个 context 里放太多东西, 改任何一项所有消费者都重渲染。

② 少下载

  • 代码分割——React.lazy +Suspense,按路由切(#347)
  • 按需引入第三方库,别import _ from "lodash"
  • 用 bundle analyzer 看谁占体积

③ 少算 / 少画

  • 长列表虚拟化—— 只渲染视口内的几十行。一万行的列表,这一条比其他所有优化加起来都有用。
  • 列表 key 用稳定 id(#330)
  • 输入防抖、搜索节流
  • React 18 的 useTransition /useDeferredValue—— 让重活不挡住输入

会追问:「怎么知道有没有多余渲染?」—— Profiler 的「Highlight updates」 或者 <Profiler onRender>; 以及注意 StrictMode 下开发模式会渲染两次, 别把它当成 bug。

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 + useCallback used 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 splittingReact.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.

React 与生态React & ecosystem#342

React 里怎么写样式

How to use styles in React

看答案Show answer

一句话:五种, 各有明确的取舍。

方式好处代价
普通 CSS / SCSS 文件零成本、能用全部 CSS 特性类名全局,会冲突
CSS Modules类名自动加哈希,天然隔离动态样式要配 CSS 变量
行内 style动态值最直接没有伪类、媒体查询、动画; 每次渲染新对象
CSS-in-JS(styled-components)能用 props 决定样式,作用域天然隔离运行时开销,SSR 要额外配置
原子化(Tailwind)不用起类名,产物体积可控JSX 里类名很长,团队要统一约定

「动态样式」的推荐做法—— 这是加分点:用 CSS 变量而不是行内 style。 把变量写在行内, 真正的样式规则还在 CSS 文件里 —— 这样既能动态,又保留伪类和媒体查询。本站的深色模式就是这么做的(切 data-theme 属性, CSS 变量整套换)。

会追问:「行内 style 为什么影响性能?」—— 每次渲染都创建新对象, 会破坏子组件的 memo; 而且它不能被浏览器按规则缓存。要用就 useMemo 稳住。

In one line: five ways, each with a clear trade-off.

ApproachUpsideCost
Plain CSS / SCSS filesFree, and every CSS feature is availableClass names are global, so they collide
CSS ModulesHashed class names, isolated by defaultDynamic styles need CSS variables
Inline styleThe most direct way to use a dynamic valueNo 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 workRuntime cost, and SSR needs extra setup
Atomic (Tailwind)No naming, and the output size stays under controlVery 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.

JSX动态样式的正确做法示意Illustrative
1// 推荐:行内只放变量,规则留在 CSS 里
2<div className="bar" style={{ "--pct": `${percent}%` }} />
3
4/* CSS 里 */
5.bar::after { width: var(--pct); } /* 伪类照样能用 */
6@media (max-width: 480px) { .bar { height: 4px; } }
7
8// ✗ 行内写全套:没法写伪类和媒体查询,还每次新对象
9<div style={{ width: `${percent}%`, background: "#2b6" }} />
1// Recommended: only the variable goes inline, the rules stay in CSS
2<div className="bar" style={{ "--pct": `${percent}%` }} />
3
4/* In the CSS */
5.bar::after { width: var(--pct); } /* pseudo-classes still work */
6@media (max-width: 480px) { .bar { height: 4px; } }
7
8// ✗ everything inline: no pseudo-classes, no media queries, and a new object each time
9<div style={{ width: `${percent}%`, background: "#2b6" }} />

这些题从哪来Where these come 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.