字
字节笔记本
2026年3月22日
React Hooks 完全指南
这篇文章将全面介绍 React Hooks 的核心概念和常用 Hook,包括 useState、useEffect、useContext、useReducer、useRef、useMemo、useCallback 等,并通过实际场景示例讲解 Hooks 的使用技巧和注意事项。
useState
useState 是最基本的 Hook,用于在函数组件中添加状态管理。
jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</div>
);
}函数式更新
当新状态依赖前一个状态时,使用函数式更新:
jsx
setCount(prevCount => prevCount + 1);惰性初始化
初始状态需要复杂计算时,传入函数避免每次渲染都重新计算:
jsx
const [state, setState] = useState(() => {
return expensiveComputation();
});注意事项
- 状态更新是异步的,不会立即反映
- 状态更新会触发组件重新渲染
- 多次连续更新会被合并(在事件处理函数中)
useEffect
useEffect 用于处理副作用,如数据获取、订阅、手动修改 DOM 等。
jsx
import { useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(timer); // 清理函数
}, []); // 空依赖数组,仅挂载时执行
return <p>已运行 {seconds} 秒</p>;
}依赖数组
| 依赖数组 | 行为 |
|---|---|
| 不传 | 每次渲染后都执行 |
[] | 仅在组件挂载和卸载时执行 |
[a, b] | 当 a 或 b 变化时执行 |
常见使用场景
数据获取:
jsx
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/data');
const data = await res.json();
setData(data);
};
fetchData();
}, []);订阅事件:
jsx
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);useEffect 常见问题
问题 1:无限循环
jsx
// 错误:每次渲染都触发 effect,effect 中更新状态又触发渲染
useEffect(() => {
setData(fetchData());
}); // 缺少依赖数组
// 正确
useEffect(() => {
setData(fetchData());
}, []); // 仅执行一次问题 2:闭包陷阱
jsx
// 错误:setInterval 中的 count 永远是初始值
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 是闭包中的旧值
}, 1000);
return () => clearInterval(id);
}, []);
// 正确:使用函数式更新
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);问题 3:依赖数组的引用陷阱
jsx
// 错误:每次渲染 options 都是新的对象引用
useEffect(() => {
fetchData(options);
}, [options]); // 无限循环
// 正确:使用 useMemo 或将对象放在 effect 内部
const stableOptions = useMemo(() => ({ page: 1 }), []);
useEffect(() => {
fetchData(stableOptions);
}, [stableOptions]);useContext
useContext 用于跨组件传递数据,避免 props 逐层传递。
jsx
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}多 Context 组合
jsx
function App() {
return (
<ThemeContext.Provider value="dark">
<AuthContext.Provider value={{ user, login, logout }}>
<Layout />
</AuthContext.Provider>
</ThemeContext.Provider>
);
}useReducer
useReducer 用于管理复杂状态逻辑,是 useState 的替代方案。
jsx
import { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
);
}useState vs useReducer
| 场景 | 推荐 |
|---|---|
| 简单状态(字符串、数字) | useState |
| 多个相关联的状态 | useReducer |
| 下一个状态依赖前一个 | useReducer |
| 状态逻辑复杂 | useReducer |
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。
访问 DOM 元素
jsx
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}保存跨渲染的可变值
jsx
function Timer() {
const timerRef = useRef(null);
const start = () => {
timerRef.current = setInterval(() => {
console.log('tick');
}, 1000);
};
const stop = () => {
clearInterval(timerRef.current);
};
return (
<>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
</>
);
}useRef vs useState
| 特性 | useRef | useState |
|---|---|---|
| 变更时触发重渲染 | 否 | 是 |
| 值的可变性 | 可变 | 不可变(需 setter) |
| 适用场景 | DOM 引用、定时器 ID | UI 状态 |
useMemo
useMemo 缓存计算结果,避免每次渲染都重新计算。
jsx
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item => item.type === filter);
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}使用原则
- 只在计算开销较大时使用
- 不要把
useMemo作为语义保证 - 过度使用反而会降低性能
useCallback
useCallback 缓存函数引用,避免子组件不必要的重渲染。
jsx
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Child onClick={handleClick} />;
}
const Child = React.memo(function Child({ onClick }) {
return <button onClick={onClick}>子组件</button>;
});useMemo vs useCallback
useMemo缓存值:useMemo(() => compute(a, b), [a, b])useCallback缓存函数:useCallback(fn, [deps])等同于useMemo(() => fn, [deps])
useImperativeHandle
useImperativeHandle 配合 forwardRef 自定义暴露给父组件的实例值。
jsx
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
});
function Parent() {
const fancyInputRef = useRef(null);
return (
<>
<FancyInput ref={fancyInputRef} />
<button onClick={() => fancyInputRef.current.focus()}>
聚焦输入框
</button>
<button onClick={() => fancyInputRef.current.clear()}>
清空输入框
</button>
</>
);
});自定义 Hook
将组件逻辑抽取为可复用的函数:
jsx
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 使用
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return <p>窗口大小: {width} x {height}</p>;
}Hooks 使用规则
- 只在顶层调用:不要在循环、条件或嵌套函数中调用 Hook
- 只在 React 函数中调用:函数组件或自定义 Hook 中使用,普通 JS 函数不行
- 依赖数组要完整:useEffect 和 useCallback 的依赖数组应包含所有用到的外部变量
- 使用 ESLint 插件:
eslint-plugin-react-hooks可以自动检查规则违规
Hooks 速查表
| Hook | 用途 |
|---|---|
useState | 状态管理 |
useEffect | 副作用处理 |
useContext | 跨组件数据传递 |
useReducer | 复杂状态管理 |
useRef | DOM 引用 / 可变值 |
useMemo | 缓存计算结果 |
useCallback | 缓存函数引用 |
useImperativeHandle | 自定义 ref 暴露 |
useLayoutEffect | DOM 变更后同步执行 |
useDebugValue | 自定义 Hook 调试标签 |
分享: