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 规则的违反