React Hooks 深入理解
React Hooks 让函数组件拥有状态、上下文和副作用能力。真正掌握 Hooks 的关键,不是记忆 API,而是理解一次渲染就是一次独立的状态快照。
React Hooks 介绍
每次组件渲染时,React 都会重新调用组件函数。当前渲染中的变量和事件处理函数会捕获这一轮渲染的状态。Hooks 必须始终在组件顶层调用,React 才能通过稳定的调用顺序关联每一个状态槽位。
| Hook | 主要用途 | 常见误区 |
|---|---|---|
useState | 保存影响界面的状态 | 直接修改对象后传回原引用 |
useEffect | 与外部系统同步 | 把派生数据放进 Effect |
useMemo | 缓存昂贵计算 | 为所有简单计算添加缓存 |
| 自定义 Hook | 复用有状态逻辑 | 误以为可以共享同一份状态 |
useState
状态更新会请求一次新的渲染,而不会修改当前渲染已经捕获的值。当新状态依赖旧状态时,应使用函数式更新。
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
function incrementThreeTimes() {
setCount((current) => current + 1);
setCount((current) => current + 1);
setCount((current) => current + 1);
}
return (
<button onClick={incrementThreeTimes}>
当前计数:{count}
</button>
);
}对象状态
对象和数组状态应该通过新值替换。展开语法适合浅层更新;复杂嵌套状态更适合重新设计数据结构或使用 reducer。
setProfile((profile) => ({
...profile,
name: nextName,
}));useEffect
Effect 用来把 React 状态与浏览器 API、网络连接或第三方组件等外部系统保持同步。依赖数组描述 Effect 使用了哪些响应式值。
import { useEffect, useState } from "react";
export function OnlineStatus() {
const [online, setOnline] = useState(true);
useEffect(() => {
const update = () => setOnline(navigator.onLine);
update();
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
}, []);
return <span>{online ? "在线" : "离线"}</span>;
}如果一个值可以在渲染期间根据 props 和 state 计算出来,通常不需要 Effect。
useMemo
useMemo 是性能优化手段,不是语义保证。只有当计算本身昂贵,或者稳定引用能避免下游组件重复工作时才使用它。
const visibleProducts = useMemo(
() => products.filter((product) => product.name.includes(query)),
[products, query],
);判断是否值得缓存,可以先用 React DevTools Profiler 找到真正的性能热点,再做针对性优化。
自定义 Hooks
自定义 Hook 把状态逻辑抽取为可组合的函数。每次调用都会获得独立状态,只复用逻辑,不共享状态实例。
import { useEffect } from "react";
export function useDocumentTitle(title: string) {
useEffect(() => {
const previousTitle = document.title;
document.title = title;
return () => {
document.title = previousTitle;
};
}, [title]);
}下面是组件渲染与 Effect 执行的基本顺序:
flowchart LR
A[状态更新] --> B[组件重新渲染]
B --> C[React 提交 DOM]
C --> D[浏览器绘制]
D --> E[运行 Effect]
E -->|依赖变化| F[先执行上次清理函数]
F --> E小结
- 把每次渲染理解为一张不可变的状态快照。
- 使用函数式更新处理依赖旧状态的连续更新。
- Effect 只负责与外部系统同步。
useMemo应由真实性能问题驱动。- 自定义 Hook 用于复用状态逻辑,而不是共享状态。