今天咱们来聊聊 React 世界里最“真香”的存在——Hooks。
回想一下 React 16.8 之前被类组件 (Class Component) 支配的恐惧:到处乱飞的 this 指向、为了复用逻辑写出的“俄罗斯套娃”高阶组件(HOC)、还有那个让人头秃的生命周期图谱……
自从 Hooks 横空出世,React 开发就像换了个游戏引擎。函数组件不再是“傻瓜式”的纯展示组件,它们有了记忆、有了灵魂!
别被官方文档里那些晦涩的术语吓跑,今天咱们用说人话的方式,把 Hooks 扒个底朝天!
1. useState:组件的“便利贴”与“游戏存档”
函数组件本质上就是个普通的函数。函数执行完了,里面的变量就销毁了,像个只有7秒记忆的金鱼 。
怎么让它记住东西呢?useState 就是给组件贴的“便利贴”。
基本用法
useState 就像去银行开户,你存进去初始资金,银行给你两样东西:
- 余额查询 (state):现在的钱数。
- 存取款机 (setState):专门用来改余额的机器。
JavaScript
import React, { useState } from 'react';
const Counter = () => {
// 游戏存档:初始化 count 为 0
// 解构赋值:[当前状态, 修改状态的函数]
const [count, setCount] = useState(0);
const handleClick = () => {
// 错误示范:直接改变量,React 根本听不见!
// count = count + 1;
// 正确示范:用“遥控器”去通知 React 更新
setCount(count + 1);
};
return (
<div>
<p>当前点击次数:{count} </p>
<button onClick={handleClick}>加一</button>
</div>
);
};
核心知识点(敲黑板)
状态不可变性 (Immutability) :
React 的 State 是只读的。你不能直接修改它(比如 user.name = 'Jack'),必须用 setState 传入一个新的值。React 只有看到新旧数据不一样了,才会去重新渲染页面。
2. useEffect:副作用的“触发器”
如果说渲染 UI 是组件的“本职工作”,那副作用 (Side Effects) 就是它的“课后作业”。
什么是副作用?
- 去服务器拿数据 (Fetch)
- 手动改 DOM (document.title)
- 设置定时器 (setTimeout)
useEffect 就是帮你管理这些作业的管家。
依赖数组:useEffect 的灵魂
很多新手搞不懂 useEffect 后面那个数组 [] 是干嘛的。其实它就是一份**“监听名单”**。
-
情况 1:不传数组(太危险了 )
- 含义:每次渲染都执行!
- 后果:如果你在里面更新状态,就会死循环,电脑风扇起飞。
-
情况 2:空数组 [] (最常用 )
- 含义:只在**组件出生(挂载)**时执行一次。
- 比喻:就像你出生时打的疫苗,这辈子就这一次。
-
情况 3:有依赖项 [count] (精准打击 )
- 含义:只有当 count 变了,我才执行。
- 比喻:这是个触发器。只有当你按下名为 count 的开关,灯泡才会亮。
JavaScript
import React, { useState, useEffect } from 'react';
const MovieList = () => {
const [movies, setMovies] = useState([]);
const [category, setCategory] = useState('action');
// 场景 1:组件挂载后,立马去拿数据(相当于 componentDidMount)
useEffect(() => {
console.log('刚进页面,我去拉取电影列表了');
// 伪代码:ajax('api/movies').then(data => setMovies(data));
}, []); // 空数组 = 只执行一次
// 场景 2:当类别变化时,重新获取数据
useEffect(() => {
console.log(` 类别变成了 ${category},重新加载数据...`);
}, [category]); // 监听名单:category 变了我就动
return (
<div>
<h1>电影列表</h1>
{/* ...渲染逻辑 */}
</div>
);
};
3. 其他常用 Hooks:工具箱里的瑞士军刀
useContext:全校广播 / Wi-Fi 信号
- 痛点:Props Drilling(属性钻取)。爷爷组件要把钱给孙子组件,得先给爸爸,爸爸再给儿子,儿子再给孙子...太累了!
- 解决:useContext 就像Wi-Fi 信号。只要爷爷建了个基站(Provider),孙子拿着手机(useContext)直接连,不需要中间人传话。
useReducer:useState 的进阶版
如果你的状态逻辑很复杂(比如购物车:添加、删除、计算总价、清空...),用 useState 写一堆函数会很乱。
useReducer 适合处理复杂的“状态机” 。它接收一个“指令”(Action),然后根据指令去计算新的状态。
useMemo & useCallback:防抖大法
这两个 Hook 都是为了性能优化。
- useMemo: 缓存计算结果。像学霸的错题本,做过的难题把答案记下来,下次直接看答案,不用重新算。
- useCallback: 缓存函数引用。防止父组件重新渲染时,生成的函数地址变了,导致子组件跟着无意义的重新渲染。
⚠ 避坑指南:不要滥用!过早优化是万恶之源。大多数情况下,你的 App 还没复杂到需要这两个东西。
4. 自定义 Hooks:封装逻辑的终极武器
这是 Hooks 最强大的地方!我们可以把组件里非 UI 的逻辑抽离出来,变成一个可复用的函数。
规则:函数名必须以 use 开头(比如 useWindowWidth, useUser)。
栗子:实时获取窗口宽度
如果不封装,每个需要宽度的组件都要写一遍 addEventListener。
JavaScript
// hooks/useWindowWidth.js
import { useState, useEffect } from 'react';
// 自定义 Hook:就是一个用了其他 Hook 的普通函数
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
// 监听窗口变化
window.addEventListener('resize', handleResize);
// 清理函数:组件销毁时移除监听(相当于 componentWillUnmount)
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // 只在挂载时监听一次
return width; // 返回数据
}
// 在任何组件里使用,简直太优雅了!
const MyComponent = () => {
const width = useWindowWidth(); // 一行代码搞定
return <p>当前屏幕宽度:{width}px</p>;
};
5. Hooks 的“天条”:千万别触犯
Hooks 虽然好用,但有两个绝对不能破的规矩,否则 React 会直接报错或者行为诡异。
第一条:只能在顶层调用
千万不要在 loops (循环), conditions (if 判断), 或者 nested functions (嵌套函数) 里调用 Hooks。
JavaScript
// 错误示范
if (isAdmin) {
const [adminData, setAdminData] = useState({}); // React 会晕!
}
// 正确示范
const [adminData, setAdminData] = useState({});
if (isAdmin) {
// 逻辑写在这里
}
原因:React 是根据 Hooks 调用的顺序来记录状态的。如果你在 if 里写 Hook,下次渲染 if 没进去,顺序就乱了,张三的帽子戴到了李四头上。
第二条:依赖项要写全
在 useEffect 里用到了哪个外部变量,就一定要把它加到依赖数组 [] 里。
后果:如果你欺骗 React,就会产生闭包陷阱 (Stale Closure) ,你会发现你的 count 永远停留在旧值,怎么加都不变。
结语
Hooks 的出现,让 React 变得更加函数式、更加灵活。
- useState 给了我们记忆。
- useEffect 帮我们处理脏活累活。
- 自定义 Hooks 让我们成为了逻辑复用的大师。
看完这篇,是不是觉得 Hooks 也没那么可怕?赶紧打开你的编辑器,把那些老旧的 Class 组件重构一遍吧!