React常用Hooks全面解析:16个核心钩子的概念与使用场景
本文整理了React开发中最常用的16个Hook,分为6大类,覆盖日常开发的大部分场景。内容基于实际使用经验手打整理,未包含React 19新增特性。
一、六大分类概览
| 分类 | Hook名称 | 用途 |
|---|---|---|
| State Management(状态管理) | useState | 定义组件状态 |
| State Management(状态管理) | useReducer | 复杂状态管理 |
| State Management(状态管理) | useSyncExternalStore | 订阅外部数据源 |
| Effect Hooks(副作用) | useEffect | 处理副作用 |
| Effect Hooks(副作用) | useLayoutEffect | 同步执行副作用 |
| Effect Hooks(副作用) | useInsertionEffect | 注入样式专用 |
| Ref Hooks(引用) | useRef | 创建可变引用 |
| Ref Hooks(引用) | useImperativeHandle | 暴露子组件方法 |
| Performance Hooks(性能优化) | useMemo | 缓存计算结果 |
| Performance Hooks(性能优化) | useCallback | 缓存函数引用 |
| Context Hooks(上下文) | useContext | 读取上下文值 |
| Transition Hooks(过渡) | useTransition | 标记非紧急更新 |
| Transition Hooks(过渡) | useDeferredValue | 延迟更新状态值 |
| Random Hooks(杂项) | useDebugValue | 自定义Hook调试 |
| Random Hooks(杂项) | useId | 生成唯一ID |
下面按分类逐一说明每个Hook的用法和适用场景。
二、State Management(状态管理)
状态管理是React最核心的功能——当状态发生变化时,自动重新渲染组件。这个分类包含三个Hook。
useState - 定义组件状态
使用频率最高的Hook,用于定义组件的局部状态。
const [value, setValue] = useState(''); // 设置状态初始值
const handleChange = (e) => {
setValue(e.target.value); // 事件触发时更新状态
};
<input
type="text"
value={value}
onChange={handleChange}
/>第一个变量value存储当前状态值,第二个变量setValue是更新状态的函数。上面的例子中,输入框内容会实时同步到value状态中。
useReducer - 复杂状态管理
比useState更强大,适合管理复杂的状态逻辑。虽然使用频率较低,但在特定场景下优势明显。
const reducer = (state, action) => {
switch (action) {
case 'increment':
return state + 1;
default:
return state;
}
};
const [count, dispatch] = useReducer(reducer, 0);
<button onClick={() => dispatch('increment')}>Increment</button>dispatch函数接收一个action参数,可以根据不同的action做出灵活的状态变更。在游戏界面这类交互复杂、状态繁多的场景中,useReducer比useState更适用。
useSyncExternalStore - 订阅外部数据源
这个Hook比较冷门,用于将非React的状态(如第三方库、浏览器API)接入到React组件中,实现外部数据源的订阅。
三、Effect Hooks(副作用)
副作用指的是组件渲染过程中与渲染结果无直接关联、但会影响外部环境的一些操作。
useEffect - 处理副作用
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `你点击了 ${count} 次`;
}, [count]);
<button onClick={() => setCount(count + 1)}>
Click me
</button>第二个参数传入[count],表示只有当count变化时,才会执行回调函数。需要注意,useEffect不应该用于“点击按钮时执行”或“获取数据时执行”这类场景。
实际上useEffect并不需要频繁使用。它最合适的场景是让React与浏览器API同步,比如控制视频(或音频)的播放与暂停:
const ref = useRef(null); // 创建引用,绑定视频DOM元素
useEffect(() => {
if (isPlaying) {
ref.current.play(); // 播放
} else {
ref.current.pause(); // 暂停
}
}, [isPlaying]);
<video ref={ref} src={src} loop playsInline />当isPlaying设置为true时,视频开始播放。这种与外部系统同步的场景,才是useEffect真正发挥作用的地方。
useLayoutEffect - 同步执行副作用
useLayoutEffect在浏览器完成渲染之前同步执行,可以用来避免页面闪烁或布局跳动。
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect(); // 获取提示框高度
setTooltipHeight(height); // 设置高度
}, []);浏览器还没开始渲染这个提示框,我们就已经获取到它的高度并保存为状态。这样在后续渲染中可以提前布局,避免视觉上的跳跃。
useInsertionEffect - 注入样式专用
这是一个更加小众的Hook,执行时机比useLayoutEffect还早,专门为CSS-in-JS库开发者设计,用于确保所有CSS样式都能正确应用到对应的组件元素上。
四、Ref Hooks(引用)
useRef - 创建可变引用
useRef类似于useState,也能存储值,但不会触发组件重新渲染。引用是可变的,可以直接通过等号运算符修改,例如ref.current = 'something'。
const [timer, setTimer] = useState(0);
const intervalRef = useRef(); // useRef用来保存setInterval的ID
const startTimer = () => {
intervalRef.current = setInterval(() => { // 存储定时器ID
setTimer((prevTimer) => prevTimer + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current); // 停止时清除定时器
};
<p>Timer: {timer} seconds</p>
<button onClick={startTimer}>开始计时</button>
<button onClick={stopTimer}>停止计时</button>你可能会问,为什么不用普通变量?因为React组件每次重新渲染,普通变量的值会丢失。使用useRef保存的值在组件整个生命周期中都存在。
useImperativeHandle - 暴露子组件方法
这个Hook配合forwardRef使用,用于向父组件暴露子组件的方法。
function ParentComponent() {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current.focus();
};
return (
<>
<CustomInput ref={inputRef} />
<button onClick={handleFocus}>激活input</button>
</>
);
}
// 使用forwardRef + useImperativeHandle暴露子组件方法
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});因为直接给自定义组件(如CustomInput)传递ref,默认情况下是拿不到内部DOM元素的。useImperativeHandle解决了这个问题,让父组件可以调用子组件暴露的方法。
五、Performance Hooks(性能优化)
useMemo - 缓存计算结果
useMemo通过记忆化技术缓存计算结果,避免不必要的重复计算。只有在依赖项发生变化时才会重新计算。
function SumComponent({ numbers }) {
const sum = useMemo(() => { // 缓存求和结果
return numbers.reduce((total, n) => total + n, 0);
}, [numbers]);
return <h1>合计: {sum}</h1>;
}求和结果被缓存起来,组件重新渲染时不会重复执行reduce计算。只有当numbers数组发生变化时,缓存才会更新。
useCallback - 缓存函数引用
useCallback与useMemo类似,但专门用于缓存函数。
function Counter() {
const [count, setCount] = useState(0);
// 缓存increment函数
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []);
return (
<>
<div>{count}</div>
<Button onClick={increment} />
</>
);
}
// Button组件
function Button({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}每次组件重新渲染时,内部定义的函数都会被重新创建。使用useCallback后,只要依赖项不变,函数引用就不会改变,避免子组件不必要的重新渲染。
六、Context Hooks(上下文)
useContext - 读取上下文值
useContext用于读取上下文的值,可以理解为React版的全局变量。
const ThemeContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
const theme = useContext(ThemeContext);
// ...
}createContext创建一个上下文,通过Provider提供给子组件,其他组件中通过useContext即可读取到该值。这个模式常用于主题切换、语言偏好等全局配置。
七、Transition Hooks(过渡)
useTransition - 标记非紧急更新
useTransition可以将某些状态更新标记为非紧急,避免阻塞用户交互。
const [filter, setFilter] = useState('');
const [inputValue, setInputValue] = useState('');
const [isPending, startTransition] = useTransition();
const filteredItems = items.filter(item => item.includes(filter));
<input
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
startTransition(() => {
setFilter(event.target.value);
});
}}
placeholder="请填充内容...."
/>
{isPending ? (
<p>加载中...</p>
) : (
filteredItems.map(item => <div key={item}>{item}</div>)
)}在输入框中输入时,如果每次输入都重新渲染整个列表,会导致UI卡顿。用startTransition包裹过滤逻辑后,这段更新被标记为低优先级。isPending是一个布尔值,表示当前是否有状态更新正在等待中,可以在更新完成前显示加载提示。
useDeferredValue - 延迟更新状态值
useDeferredValue与useTransition类似,但它会在合适的时机自动延迟更新状态值。
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);一个完整的过滤列表示例:
function FilteredList({ items }) {
const [inputValue, setInputValue] = useState('');
const deferredFilter = useDeferredValue(inputValue);
const filteredItems = items.filter(item =>
item.toLowerCase().includes(deferredFilter.toLowerCase())
);
return (
<>
<input
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
placeholder="Type to filter..."
/>
{filteredItems.map(item => <div key={item}>{item}</div>)}
</>
);
}useDeferredValue很适合过滤列表这类场景,无需手动设置过渡,也不需要任何异步状态管理。
八、Random Hooks(杂项)
useDebugValue - 自定义Hook调试
在React DevTools中为自定义Hook添加标签,方便调试时查看内部状态。
useId - 生成唯一ID
调用useId会生成一个唯一的ID,通常用于表单输入框之间的ID共享。
function EmailInput({ name }) {
const id = useId();
return (
<>
<label htmlFor={id}>{name}</label>
<input id={id} type="email" />
</>
);
}
function Form() {
return (
<form>
<EmailInput name="Email" />
<EmailInput name="Confirm Email" />
</form>
);
}在Form中两次使用EmailInput组件,通过useId生成的唯一ID可以避免输入框冲突,无需手动管理ID。
总结
以上16个Hook覆盖了React日常开发的绝大多数场景。回顾一下核心要点:
状态管理三件套:useState处理简单状态,useReducer应对复杂逻辑,useSyncExternalStore处理外部数据源。
副作用处理有执行时机之分:useEffect在渲染后执行,useLayoutEffect在渲染前执行,useInsertionEffect专门处理样式注入。
引用不同于状态:useRef的值可变且不触发重渲染,适合存储DOM引用或不需要响应式更新的值。
性能优化靠缓存:useMemo缓存计算结果,useCallback缓存函数引用,减少不必要的重新计算和渲染。
过渡机制提升体验:useTransition和useDeferredValue将非紧急更新延后处理,保证交互流畅。
理解这些Hook的执行时机和适用场景,才能在合适的场景选择合适的工具。不是所有场景都需要用Hook,也不是所有优化都值得加。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!