别再被
useState和useEffect整懵了!本文带你用最轻松的方式,搞懂 React Hooks 的灵魂所在。
🌟 你是否也经历过这些“Hook 焦虑症”?
- 每次写
useState都在纠结:到底传值还是传函数? useEffect的依赖数组像一道玄学题,加不加?怎么加?- 组件一更新,定时器满天飞,内存泄漏警告响个不停……
- 更可怕的是:明明写了
[],怎么还执行了两次?!
别慌!今天我们就来一场 React Hooks 的脱口秀式科普,让你笑着学会,笑着写出干净、健壮、优雅的函数组件!
🧠 一、useState:不只是“变量”,而是“会呼吸的状态”
❓ 初始值是简单数字?直接传!
const [count, setCount] = useState(0);
这谁都会。但如果你的初始值需要“算一下”呢?
💡 复杂计算?用初始化函数!
const [num, setNum] = useState(() => {
const a = expensiveCalculation(); // 假设很耗时
return a * 2;
});
✅ 关键点:这个函数只在组件首次渲染时执行一次,后续更新完全忽略它。
❌ 不要在这里写异步!useState要的是确定性——状态不能“等两秒才知道是多少”。
🤔 那我怎么在组件加载时请求数据?
👉 别急,useEffect马上登场!
🌀 二、useEffect:React 的“副作用管家”
🧼 什么是“副作用”?
在纯函数的世界里:
- 输入 → 输出(比如
(x, y) => x + y) - 没有网络请求、没有定时器、没有 DOM 操作
而现实世界偏偏充满“副作用”:
“我要发请求!”、“我要监听窗口大小!”、“我要启动一个每秒滴答的钟!”
React 说:把这些脏活交给 useEffect 吧!
🛠️ useEffect 的三种经典姿势
1️⃣ 挂载即执行(onMounted)
useEffect(() => {
console.log('组件出生啦!');
}, []); // 空数组 = 只跑一次
⚠️ 注意:React 18 严格模式下,开发环境会故意执行两次,帮你提前发现副作用问题!别慌,生产环境只跑一次。
2️⃣ 依赖变化就执行(watcher)
useEffect(() => {
document.title = `你点了 ${count} 次`;
}, [count]); // count 变了才跑
🔍 依赖项必须包含 effect 内用到的所有外部变量!否则你会读到“过期的闭包值”。
3️⃣ 清理副作用(onUnmounted / before update)
useEffect(() => {
const timer = setInterval(() => console.log('滴答'), 1000);
return () => clearInterval(timer); // 清理!
}, []);
💥 不清理 = 内存泄漏!多个定时器同时跑,页面卡成 PPT,用户想砸电脑……
🎭 实战案例:点击 +1,偶数时显示子组件
我们来看一段“教科书级”的代码:
// App.jsx
useEffect(() => {
const timer = setInterval(() => console.log(num), 1000);
return () => clearInterval(timer);
}, [num]); // 依赖 num
每次 num 变化,旧定时器被清除,新定时器启动。
✅ 安全!✅ 干净!✅ 不泄漏!
而子组件 Demo:
// Demo.jsx
useEffect(() => {
const timer = setInterval(() => console.log('timer'), 1000);
return () => clearInterval(timer);
}, []); // 只在挂载/卸载时处理
当 num 变奇数,<Demo /> 被卸载,它的清理函数自动执行!
👉 React 自动帮你管理生命周期,无需手动调用 onUnmounted!
📈 图解 React 核心模型:Event → State → View
React 的核心思想可以用一张图概括:
📌 事件驱动模型:
- 用户触发 Event(如点击按钮)
- 触发状态变更(
setNum())- 状态改变后,View 自动重渲染
- 新 View 再响应新的 Event,形成闭环
✅ 这就是 React 的“单向数据流”哲学:一切皆由状态驱动,视图只是状态的映射。
🤯 常见误区 & 幽默小剧场
❌ 误区1:“我在 useState 里发请求!”
const [data] = useState(async () => {
const res = await fetch('/api');
return res.json(); // ❌ 报错!useState 不接受 Promise!
});
🗣️ React 内心 OS:
“兄弟,我是同步的!你要异步?找useEffect啊!”
✅ 正确姿势:
useEffect(() => {
fetch('/api').then(res => setData(res.json()));
}, []);
❌ 误区2:“依赖数组空着最安全!”
useEffect(() => {
console.log(user.name); // user 是 props
}, []); // ❌ 如果 user 变了,这里还是旧值!
🐞 这叫 “陈旧闭包陷阱” —— 你看到的
user是组件第一次渲染时的快照!
✅ 解法:把 user 加进依赖,或用 useCallback / useRef 缓解。
🧩 三、自定义 Hook:把逻辑抽离成“乐高积木”
React Hooks 最酷的地方?你可以自己造 Hook!
比如封装一个通用的数据请求:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(url)
.then(res => res.json())
.then(setData)
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}
// 使用
function UserProfile() {
const { data, loading } = useFetch('/api/user');
if (loading) return <div>加载中...</div>;
return <div>你好,{data.name}!</div>;
}
🧱 逻辑复用从未如此简单!告别 HOC,告别 render props,拥抱函数式组合!
🎉 结语:Hooks 不是魔法,而是“思维升级”
React Hooks 的本质,不是语法糖,而是一种思维方式的转变:
- 状态 → 用
useState管理 - 副作用 → 用
useEffect隔离 - 逻辑复用 → 用自定义 Hook 抽离
它们让函数组件拥有了类组件的能力,却更轻、更灵活、更函数式。
🌈 记住:
状态要确定,副作用要清理,依赖要诚实。
做到这三点,你就是 Hooks 高手!