Hooks 四问4 questions on Hooks
hooks 是什么与为什么、useMemo vs useCallback、React.memo vs useMemo、自定义 hook。What hooks are and why they exist, useMemo vs useCallback, React.memo vs useMemo, custom hooks.
这一页有什么On this page5
- 说出 hooks 解决的三个类组件痛点Name three problems with class components that hooks solve
- 分清 useMemo、useCallback、React.memo 各自缓存什么Say what each of useMemo, useCallback and React.memo actually caches
- 说出 hooks 的两条规则以及「为什么」不能写在条件里State the two rules of hooks, and explain why you cannot put one inside a condition
- 写出一个自定义 hook 并说明命名约定Write a custom hook and explain the naming rule
「useMemo 和 useCallback 有什么区别」是出现频率最高的 React 题之一,而且大部分人答不全 —— 能补上「什么时候不该用」和「三个必须配套」才是好答案。hooks 规则那道会追问底层原因(链表 + 调用顺序),答得出来就上一个档。"What is the difference between useMemo and useCallback" is one of the most common React questions, and most people give only half an answer. A good answer also covers when not to use them, and the fact that the three of them only help when used together. For the rules of hooks the interviewer will ask why, and the reason is that React stores them in a list by call order. Getting that right moves you up a level.
什么是 hooks,为什么要用What are hooks, and why use them?
#324 What are hooks in React and Why do we use them
一句话:hooks 是一组use 开头的函数, 让函数组件也能有状态和副作用。
解决三个真实痛点(这三条是标准答案):
- 逻辑复用难。以前只有 HOC 和 render props, 两者都会造成 wrapper 嵌套(见 #335)。 自定义 hook 是平的。
- 逻辑被生命周期切碎。一个「订阅 + 取消订阅」的完整逻辑 被迫拆到两个生命周期方法里;
useEffect让它们写在一起。 this太容易出错。函数组件没有this。
常用的:useState、useEffect、useContext、useRef、useMemo、useCallback、useReducer; React 18 加了useId、useTransition、useDeferredValue、useSyncExternalStore。
两条规则(必答):
- 只在最顶层调用—— 不能放在
if、循环、 嵌套函数里。 - 只在函数组件或自定义 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;
useEffectkeeps them side by side. thiswas too easy to get wrong. Function components have nothis.
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.
useMemo vs useCallback
#339 useMemo vs useCallback
一句话:useMemo 缓存 「函数的返回值」,useCallback 缓存 「函数本身」。两者都靠依赖数组决定要不要重算。
实际上 useCallback(fn, deps)完全等价于useMemo(() => fn, deps)——后者是前者的语法糖。 这句能答出来会加分。
| 缓存什么 | 什么时候用 | |
|---|---|---|
useMemo | 计算结果(值 / 对象 / 数组) | ① 计算真的贵(大列表排序过滤) ② 结果要当 props 传给 memo 组件 ③ 结果要当别的 hook 的依赖 |
useCallback | 函数引用 | ① 函数要传给 memo 组件 ② 函数是 useEffect 的依赖 ③ 自定义 hook 对外暴露的函数 |
什么时候不该用(这半边很多人答不出):两者本身都有成本—— 要存旧值、要比较依赖。 给一个 a + b 包useMemo 是纯亏。「先测量,再优化」; 默认不加,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 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.
React.memo vs useMemo
#346 React.memo vs useMemo
一句话:React.memo 是 「组件」级的 —— 决定要不要重新渲染整个组件;useMemo 是 「值」级的 —— 决定要不要重新计算一个值。
React.memo | useMemo | |
|---|---|---|
| 是什么 | 高阶组件 | 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.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.
自定义 hook 是干什么的,命名有什么约定What is a custom hook for, and what is the naming rule?
#340 What are custom hooks for and what is the naming convention for them
一句话:把「带状态的逻辑」抽出来复用。 命名必须以 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.
换一道题也能用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.
- hooks 解决三件事:逻辑复用难、逻辑被生命周期切碎、this 易错。hooks solve three things: reusing logic was hard, logic was cut apart across lifecycle methods, and this was easy to get wrong.
- hooks 规则的底层原因是「按调用顺序存链表」,不是按名字。The reason for the rules of hooks is that React stores them in a list by call order, not by name.
- useMemo 缓存值、useCallback 缓存函数;useCallback 就是 useMemo(() => fn, deps)。useMemo caches a value, useCallback caches a function; useCallback is exactly useMemo(() => fn, deps).
- React.memo 省一次渲染、useMemo 省一次计算;三个必须配套用才有意义。React.memo saves a render, useMemo saves a computation; the three of them only help when used together.
- memo 拦不住 context 变化 —— 所以 context value 必须 useMemo。memo cannot stop a Context change, which is why a Context value must go through useMemo.
- 自定义 hook 必须 use 开头(ESLint 靠它识别);复用逻辑不复用状态。A custom hook has to start with use, because that is how ESLint recognises it; you reuse the logic, not the state.