1、Hooks 初体验:为什么要有 Hooks?从 Class 组件到函数组件的思维转变,我的第一个自定义 Hook
1.1 一个让我头疼的问题
我记得刚接触 React 那会儿,写 Class 组件写得挺顺手的。
但时间一长,问题就来了。
比如一个简单的计数器,在 Class 组件里你得写 constructor、绑定 this、调用 setState。代码量不大,但看着就是别扭。更别提那些复杂的生命周期了——componentDidMount、componentDidUpdate、componentWillUnmount,三个方法可能都在做同一件事:管理某个副作用。
我有个项目,一个组件里塞了七八个生命周期方法。每次改需求,我都得小心翼翼地在三个地方同步修改。说实话,那段时间我特别想骂人。
1.2 为什么要有 Hooks?
你想想看,React 团队推出 Hooks,不是为了炫技。他们想解决三个实际问题:
- 状态逻辑复用难——以前用高阶组件、render props,代码嵌套得像洋葱
- 复杂组件难维护——生命周期里塞满不相关的逻辑
- this 指向问题——忘了 bind 就报错,新手老手都踩过坑
说白了,Hooks 让 React 更「函数式」了。函数组件 + Hooks,你不需要关心组件挂载了没、更新了没,你只需要关心「数据是什么」和「副作用怎么处理」。
1.3 从 Class 组件到函数组件的思维转变
这个转变,我花了大概两周才适应。核心就三点:
| Class 组件思维 | 函数组件思维 |
|---|---|
| 关注生命周期(挂载、更新、卸载) | 关注状态和副作用 |
| this.state / this.setState | useState 返回的数组 |
| 方法需要 bind 或箭头函数 | 闭包捕获当前值,无需 bind |
举个例子。一个简单的计数器,Class 组件长这样:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>计数:{this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
换成函数组件 + Hooks:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
看到了吗?少了 constructor、少了 this、少了 bind。代码量直接砍半。而且逻辑更直观——「我有一个状态叫 count,初始值是 0,点击按钮就加 1」。
1.4 我的第一个自定义 Hook
学 Hooks 最爽的一点,就是可以自己造 Hook。
我记得第一个自定义 Hook 是为了处理「窗口大小变化」这个需求。项目里好几个组件都要监听 window.resize,每次都得写 addEventListener 和 removeEventListener,烦死了。
于是我写了个 useWindowSize:
import { useState, useEffect } from 'react';
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 MyComponent() {
const { width, height } = useWindowSize();
return (
<p>当前窗口:{width} x {height}</p>
);
}
你看,一个自定义 Hook 就把「监听窗口变化」的逻辑封装起来了。任何组件想用,一行代码就搞定。这就是 Hooks 的威力——逻辑复用变得前所未有的简单。
1.5 避坑指南
我曾经在自定义 Hook 里犯过一个低级错误——在条件语句里调用 Hook。
// ❌ 错误写法
function useMyHook(flag) {
if (flag) {
const [data, setData] = useState(null); // 报错!
}
}
// ✅ 正确写法
function useMyHook(flag) {
const [data, setData] = useState(null);
// 用条件逻辑处理数据,而不是条件调用 Hook
}
Hooks 的调用顺序必须在每次渲染时保持一致。不能在循环、条件或嵌套函数里调用。这个规则我一开始觉得烦,后来发现它保证了 Hooks 的行为可预测——React 靠调用顺序来关联状态和 Hook。
1.6 小结
嗯,这一章我们聊了:
- Class 组件的痛点——逻辑分散、复用困难、this 烦人
- Hooks 的诞生就是为了解决这些问题
- 从 Class 到函数的思维转变:关注状态而非生命周期
- 第一个自定义 Hook——useWindowSize,把逻辑封装成可复用的函数
下一章,我们会深入 useState 和 useEffect 的细节。这两个是最常用的 Hook,但坑也不少。我会把我在项目中踩过的坑一个一个讲给你听。
公众号:蓝海资料掘金营,微信deep3321