外观
React Hooks 要答到什么程度?
Hooks 让函数组件拥有状态和副作用。React 靠调用顺序把每一次的 Hook 对上一次渲染的状态,所以 Hook 只能写在组件或自定义 Hook 的顶层。useState 负责状态,useEffect 负责提交之后的副作用。
原理
一次渲染里的状态更新会被收集起来,React 18 起在事件、定时器、Promise 里都会批处理。在这次函数执行过程中读到的仍是旧状态。要基于旧值计算,把函数传给 setState:
jsx
setCount((count) => count + 1)useEffect(fn, deps) 在浏览器把这次更新画出来之后运行。deps 里的值变了,先跑上一次的清理函数,再跑这次的 fn。组件卸载时也会清理。
jsx
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal })
return () => controller.abort()
}, [url])依赖数组记下的是这一次渲染的闭包。漏写依赖,效果函数会一直看到旧的 props 或 state。
useLayoutEffect 在绘制前同步运行,适合读取布局并立刻改 DOM。里面做重活会卡住首帧。
useMemo 缓存计算结果,useCallback 缓存函数,两者都是「依赖不变就返回上次的引用」。传给 memo 子组件、或者依赖本身很贵时才有意义。React Compiler 能在构建期自动做一部分这种缓存,面试仍然要能解释为什么引用变化会让子组件重新渲染。
useRef 是一个可变盒子。改 ref.current 不会触发渲染。它适合存定时器、DOM 节点、以及「变了但不需要画出来」的值。
React 19 里,ref 可以作为普通 prop 传入函数组件,多数场景不必再包 forwardRef。
自定义 Hook 只是名字以 use 开头、内部调用了 Hook 的函数。它复用的是状态逻辑,不复用状态本身。每个调用点都有自己的状态。
追问
为什么不能写在条件里?
React 用链表按顺序记住 Hook。某一次少调用一个,后面的 Hook 会读到别人的状态。条件逻辑放进 Hook 里面,不要放在 Hook 的调用外面。
useEffect 空依赖和没有依赖有什么差别?
空数组:挂载后执行一次,卸载时清理。不写数组:每次提交后都执行。写了具体依赖:这些值用 Object.is 比出变化才执行。
状态没变,组件为什么还是渲染了?
父组件渲染,子组件默认跟着渲染。memo 只能挡住 props 的浅比较。context 值变了,所有读这份 context 的组件都会渲染。见 状态管理。
易错点
setState不是「异步函数」,但更新是调度出去的,当前函数里读不到新值。useEffect里用到的外部变量都应该出现在依赖里,再用清理函数处理过期请求,而不是故意漏依赖。- 每次渲染都新建对象再放进依赖,效果会每次都跑。