React Hooks 是 React 16.8 引入的一项革命性特性,它允许你在函数组件中使用状态(state)和其他 React 特性,而无需编写 class 组件。下面将详细解读 React Hooks 的核心概念、常用 Hooks 及其工作原理。
一、Hooks 的核心概念
1. 什么是 Hooks
Hooks 是特殊的函数,以"use"开头(如 useState
, useEffect
),让你能够"钩入" React 的状态和生命周期特性。
2. Hooks 的基本规则
只在最顶层调用 Hooks:不要在循环、条件或嵌套函数中调用 Hook
只在 React 函数组件或自定义 Hook 中调用 Hooks
二、常用内置 Hooks 详解
1. useState
const [state, setState] = useState(initialState);
用于在函数组件中添加局部状态
返回一个状态值和一个更新该状态的函数
参数可以是初始值或返回初始值的函数(惰性初始化)
示例:
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
2. useEffect
useEffect(() => {
// 副作用逻辑
return () => {
// 清理函数(可选)
};
}, [dependencies]);
用于处理副作用(数据获取、订阅、手动修改 DOM 等)
相当于 class 组件中的
componentDidMount
,componentDidUpdate
和componentWillUnmount
的组合第二个参数是依赖数组,控制 effect 的执行时机
示例:
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
return () => {
// 清理工作
};
}, [count]); // 仅在 count 更改时更新
}
3. useContext
const value = useContext(MyContext);
用于订阅 React 的 Context 对象
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
示例:
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>I am styled by theme context!</button>;
}
4. useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState 的替代方案,适合复杂 state 逻辑
接收一个形如
(state, action) => newState
的 reducer 函数返回当前 state 和配套的 dispatch 方法
示例:
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, {count: 0});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
5. useCallback
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
返回一个 memoized 回调函数
仅在依赖项改变时才会更新
用于优化子组件渲染,避免不必要的重新渲染
6. useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值
仅在依赖项改变时才会重新计算
用于性能优化,避免每次渲染都进行高开销计算
7. useRef
const refContainer = useRef(initialValue);
返回一个可变的 ref 对象,其
.current
属性被初始化为传入的参数常用于访问 DOM 节点或存储可变值而不引起重新渲染
示例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
三、Hooks 的工作原理
1. Hooks 的调用顺序
React 依赖于 Hooks 的调用顺序来正确关联状态和对应的 Hook。这就是为什么不能在条件或循环中调用 Hook。
2. Hooks 的实现机制
每个组件有一个"记忆单元格"列表(可以看作是一个数组)
每次调用 Hook 时,它都会读取当前的单元格(或初始化它),然后将指针移动到下一个
这就是为什么 Hook 的调用顺序必须一致
3. 自定义 Hook
可以创建自己的 Hook 来复用状态逻辑。自定义 Hook 是一个名称以"use"开头的 JavaScript 函数,它可以调用其他 Hook。
示例:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
}, [friendID]);
return isOnline;
}
四、Hooks 的优势
简化组件逻辑:解决了 class 组件中生命周期函数经常包含不相关的逻辑的问题
复用状态逻辑:通过自定义 Hook 可以轻松复用状态逻辑,无需高阶组件或 render props
更直观的代码:Hooks 让你根据代码的用途而非生命周期方法来组织代码
更小的打包体积:函数组件通常比 class 组件更轻量
五、Hooks 的最佳实践
按功能而非生命周期组织代码:将相关的逻辑放在同一个 useEffect 中
合理使用依赖数组:确保 useEffect 和 useCallback/useMemo 的依赖项完整且准确
避免过度优化:不要过早使用 useMemo 和 useCallback,先测量再优化
自定义 Hook 命名以"use"开头:这是 React 识别 Hook 的方式
考虑使用 eslint-plugin-react-hooks:帮助检测 Hook 规则的违反