说说对 React Hooks 的理解?解决了什么问题?
一、是什么
Hooks 是 React 16.8 引入的一组函数,允许在函数组件中使用 state、生命周期等 React 特性,而无需编写 class 组件。
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `点击了 ${count} 次`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>点击 {count} 次</button>;
}
在 Hooks 出现之前,函数组件被称为"无状态组件",只能接收 props 渲染 UI。如果需要 state 或生命周期,必须使用 class 组件。Hooks 彻底改变了这一局面。
二、解决了什么问题
1. 状态逻辑复用困难
class 组件时代,复用状态逻辑依赖 HOC(高阶组件)和 Render Props,两者都存在"嵌套地狱"问题:
// HOC 嵌套地狱
export default withRouter(
withTheme(
withAuth(
connect(mapState, mapDispatch)(MyComponent)
)
)
);
Hooks 通过自定义 Hook 优雅地解决了这个问题:
function useAuth() {
const [user, setUser] = useState(null);
useEffect(() => {
const unsub = auth.onAuthStateChanged(setUser);
return unsub;
}, []);
return user;
}
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handler = () => setSize({ width: innerWidth, height: innerHeight });
window.addEventListener('resize', handler);
handler();
return () => window.removeEventListener('resize', handler);
}, []);
return size;
}
function Dashboard() {
const user = useAuth();
const { width } = useWindowSize();
return <div>{user?.name} - {width}px</div>;
}
2. 复杂组件难以理解
class 组件中,相关逻辑被拆分到不同生命周期,不相关逻辑却被迫放在同一个方法中:
class ChatRoom extends React.Component {
componentDidMount() {
this.setupConnection(); // 逻辑 A
this.fetchMessages(); // 逻辑 B
this.startTimer(); // 逻辑 C
}
componentWillUnmount() {
this.destroyConnection(); // 逻辑 A 的清理
this.clearTimer(); // 逻辑 C 的清理
}
componentDidUpdate(prevProps) {
if (prevProps.roomId !== this.props.roomId) {
this.destroyConnection(); // 逻辑 A 的清理
this.setupConnection(); // 逻辑 A 的重建
}
}
}
Hooks 让我们按照逻辑关注点组织代码:
function ChatRoom({ roomId }) {
useEffect(() => {
const conn = createConnection(roomId);
conn.connect();
return () => conn.disconnect();
}, [roomId]);
useEffect(() => {
fetchMessages(roomId);
}, [roomId]);
useEffect(() => {
const timer = setInterval(tick, 1000);
return () => clearInterval(timer);
}, []);
}
3. class 本身带来的困惑
this 绑定问题,事件处理器需要手动 bind 或用箭头函数
- 难以被工具优化(class 不利于 minify、热重载不稳定)
- 学习曲线陡峭(理解原型链、this 指向)
三、Hooks 的使用规则
React 对 Hooks 有两条铁律,违反会导致 bug:
规则一:只在最顶层调用 Hooks
不能在条件语句、循环或嵌套函数中调用 Hooks:
// 错误示范
function Form({ showName }) {
if (showName) {
const [name, setName] = useState(''); // 条件调用,破坏链表顺序
}
const [age, setAge] = useState(0);
}
// 正确做法
function Form({ showName }) {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
// 在渲染中有条件地使用 name 即可
}
规则二:只在 React 函数中调用 Hooks
只能在函数组件或自定义 Hook 中使用,不能在普通 JavaScript 函数中使用。
可以通过 eslint-plugin-react-hooks 插件自动检查这些规则。
四、常用内置 Hooks
useState — 状态管理
const [state, setState] = useState(initialValue);
// 函数式更新(基于先前 state 计算)
setState(prev => prev + 1);
// 惰性初始化(只在首次渲染执行)
const [data, setData] = useState(() => expensiveComputation());
useEffect — 副作用
useEffect(() => {
// 副作用逻辑
const subscription = subscribe(props.id);
return () => {
// 清理函数
subscription.unsubscribe();
};
}, [props.id]); // 依赖数组
依赖数组的三种形态:
- 不传:每次渲染后执行
[]:仅挂载/卸载时执行
[dep1, dep2]:依赖变化时执行
useContext — 消费上下文
const ThemeContext = createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
useRef — 可变引用
function TextInput() {
const inputRef = useRef(null);
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1; // 修改不触发重渲染
});
return <input ref={inputRef} />;
}
useMemo / useCallback — 性能优化
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const memoizedFn = useCallback(() => handleClick(id), [id]);
useReducer — 复杂状态
function reducer(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(reducer, { count: 0 });
return (
<>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
五、自定义 Hook
自定义 Hook 是以 use 开头的函数,内部可以调用其他 Hooks,用于抽取可复用的状态逻辑:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(res => res.json())
.then(json => {
if (!cancelled) {
setData(json);
setLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <Spinner />;
if (error) return <ErrorMsg error={error} />;
return <Profile user={data} />;
}
六、Hooks 内部工作原理
Hooks 在 Fiber 节点上以链表的形式存储。每个函数组件对应一个 Fiber 节点,节点的 memoizedState 指向第一个 Hook,每个 Hook 通过 next 指针指向下一个:
Fiber.memoizedState → Hook1 → Hook2 → Hook3 → null
↓ ↓ ↓
state1 effect state2
这就是为什么 Hooks 必须在顶层调用——React 依赖调用顺序来匹配每次渲染中的 Hook 与其对应的状态。如果在条件语句中调用 Hook,链表顺序错乱会导致状态错位。
挂载时(mountWorkInProgressHook)创建新的 Hook 节点追加到链表尾部;更新时(updateWorkInProgressHook)按顺序遍历链表读取已有的 Hook 节点。
React 18+ 新增 Hooks
useId:生成稳定的唯一 ID,适用于 SSR 水合场景
useTransition:标记低优先级状态更新,保持 UI 响应
useDeferredValue:延迟更新某个值,类似防抖但由 React 调度
useSyncExternalStore:安全订阅外部数据源
useInsertionEffect:在 DOM 变更前同步执行,为 CSS-in-JS 库设计
function SearchResults({ query }) {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
function handleSearch(input) {
startTransition(() => {
setResults(filterResults(input)); // 低优先级更新
});
}
return (
<div>
<input onChange={e => handleSearch(e.target.value)} />
{isPending ? <Spinner /> : <ResultList items={results} />}
</div>
);
}
七、总结
Hooks 本质上是函数组件的"能力注入"机制。它不是 class 组件的简单替代,而是一种全新的心智模型:把组件看作接收 props 并返回 UI 的纯函数,Hooks 是让这个函数具有记忆和副作用能力的工具。React 团队已明确表示未来的新特性都将以 Hooks 为基础构建。