本节摘要:Hooks 是给函数组件"开挂"的函数,把状态、副作用、性能缓存统一抽象进来。useState 让函数组件有状态,useEffect 管副作用,useMemo/useCallback 优化渲染。理解这三个再加上"规则",就掌握了 React Hooks 的主力。
本节阅读目标
阅读完本节,你应当能够:
函数组件原本"无状态"——随着组件变复杂,"有状态、有副作用"的需求却越来越多,于是 React 引入 Hooks:一种能"钩进"函数组件内部、挂上状态与副作用能力的函数。它们统一以 use 开头,把能力平铺在函数体里,替代了类组件的 constructor 与生命周期方法。
function Counter() { const [count, setCount] = useState(0); const add = () => setCount((c) => c + 1); return <button onClick={add}>当前 {count}</button>; }
解构出的两个值:count 是当前状态,setCount 是更新函数。更新的两种传法:传新值,或传一个把旧值变成新值的函数(如上,求值可靠)。
useEffect 接手"副作用":数据请求、订阅、手动改 DOM。第二个参数是依赖数组,它决定了"什么时候重跑":
useEffect(() => { // 挂载后与 title 变化时执行 document.title = `你有 ${unread} 条未读`; }, [unread]); // 只有 unread 变才重跑
useMemo 缓存计算结果,useCallback 缓存函数引用。它们的目的都是"在依赖没变时避免无谓重算/重建"。但有个共识:不是每个计算都值得缓存。计算很轻时加缓存反而是负担;真到计算重、或函数要传给被 memo 的子组件时,才用得上。判断标准看代价,而不是跟风。
const total = useMemo(() => items.reduce((s, x) => s + x.price, 0), [items]);
前面三条铁律里提到"自定义 Hook 也以 use 开头",这条值得单独展开,因为它是 Hooks 复用的主战场。当你在多个组件里反复写同一段"状态 + 副作用",就该抽成一个自定义 Hook。比稿现场拿它和 5.5 节的复用抽象并列评审,能看出它相比 HOC/混入更轻、来源更直接。
举个拖动显示元素位置的例子,几乎每个带浮层的组件都要写:
function useMousePosition() { const [pos, setPos] = useState({ x: 0, y: 0 }); useEffect(() => { const move = (e) => setPos({ x: e.clientX, y: e.clientY }); window.addEventListener('mousemove', move); return () => window.removeEventListener('mousemove', move); // 卸载前清理订阅 }, []); return pos; }
之后任何组件里一行 const { x, y } = useMousePosition(); 就拿到了位置。你不需要在十处复制监听与清理逻辑,也不用担心哪一处忘了清理订阅。这正是"按需复用、就地清理"在 Hooks 形态下的落地——得益于它能复用"状态+副作用"的组合,而类组件时代的 mixin 很难做到这种轻巧。

要不要抽,比很多写法问题更常见也更该有判断标准。先给一条够用的经验:同一个"状态+副作用"组合在你的代码里出现第三次,就值得抽。第一次是顺手写,第二次开始犹豫,第三次就该把重复收敛成一个带名字的 Hook。标准的反面是"一个逻辑才写了一次就去抽象",提前抽象会让你套一层不存在的通用性,回头又要改。
抽出来后也要防两件事。一是"把太多东西塞进一个 Hook",一个 Hook 管状态、管请求、管缓存,责任一多就难复用也难理解,跟组件该瘦身的道理一样;二是"为了抽象硬写 use 前缀却互相调用出环"——自定义 Hook 之间可以借用别的 Hook,但别让它变成一团你俩都理不清的网。判断尺度还是那条:抽出来之后,是不是让每一处使用者都更短、更直白了。如果答案是"更绕",那这次抽象就是减分项,宁可先不抽。
Hooks 让函数组件能管状态、副作用、性能。最后一块拼图是事件与表单——用户输入怎么进到 React 里,受控组件怎么做。下一节收尾本章。