react hook 介绍
一、React Hooks 是什么?
React Hooks 是 React 16.8 引入的一组以 use 开头的函数,让函数组件也能拥有状态(state)、生命周期和副作用处理能力------这些原本只有类组件才能做到。
🧑🍳 生活类比 :
以前,只有"有经验的厨师"(类组件)才能记住锅里煮了几分钟、盐放了多少;
现在,"新手厨师"(函数组件)只要学会用
useState、useEffect这些工具,也能轻松掌勺!
Hooks 的出现,不仅简化了组件逻辑,还避免了类组件中常见的 this 绑定问题,让代码更简洁、复用性更强。
二、useState:给组件一个"记忆"
useState 是最常用的 Hook,用于在函数组件中声明和更新状态。
js
const [count, setCount] = useState(0);
count:当前状态值(初始为0)setCount:更新状态的函数
📦 生活例子 :
就像给一个计数器装上"数字记忆"------它记得现在是几,并且能通过按钮加一或重置。
关键要点:
-
初始化必须是同步的
useState(initialValue)的参数必须是立即可用的值。不能写
useState(fetchData())(异步不行)必须写
useState(0)、useState({})等同步值类比:"今天几号?"你得马上回答,不能说"等我查下日历"。
-
更新状态有两种方式:
-
直接赋值 :
setCount(5)→ 把状态设为 5 -
基于前值更新 :
setCount(prev => prev + 1)→ 在旧值基础上计算新值⚠️ 当新状态依赖于旧状态时(比如多次快速点击),务必使用函数式更新,避免状态丢失。
-
三、纯函数 vs 副作用:React 组件的理想与现实
纯函数(Pure Function)
- 相同输入 → 相同输出
- 不修改外部变量或 DOM
- 无网络请求、无时间依赖
- 例子:
add(1, 2)永远返回3
理想中的 React 组件就是一个纯函数:
Js
(props) => <div>Hello, {props.name}!</div>
副作用(Side Effects)
但真实应用离不开"不纯"的操作:
- 发起 API 请求
- 订阅事件
- 手动操作 DOM
- 设置定时器
这些都属于 副作用------它们会"影响外部世界"或"依赖外部状态"。
useEffect:专门处理副作用的 Hook
js
useEffect(() => {
// 副作用逻辑写在这里
}, [dependencies]);
- 第一个参数:副作用函数
- 第二个参数(可选):依赖数组,控制何时重新执行
🛠️ 作用:把副作用从渲染逻辑中抽离,让组件更接近"纯函数",同时安全地处理异步或外部交互。
四、用 useEffect 实现异步数据请求
由于 useState 不能直接接收异步结果,我们采用"两步走"策略:
js
// 1. 先准备一个"空盒子"(同步初始化)
const [data, setData] = useState(null);
// 2. 组件挂载后,用 useEffect "去拿东西"
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setData);
}, []); // 空依赖数组 → 只在组件首次渲染时执行一次
📦 类比理解:
useState(null)= 桌上先放一个空盒子useEffect= 派人去仓库取货,回来后把东西放进盒子- 用户看到的是:先空白 → 稍后显示内容(可配合 loading 状态优化体验)
💡 最佳实践:
- 初始状态可设为
null、[]或{},便于后续判断是否加载完成 - 添加错误处理和 loading 状态,提升健壮性
五、React 与 Vue 的响应式哲学对比
| 维度 | React | Vue |
|---|---|---|
| 状态更新 | 手动调用 setState / setXxx |
自动追踪依赖,响应式系统自动更新 |
| 心智模型 | "我告诉 UI 该变了" | "数据变了,UI 自动跟着变" |
| 类比 | 手动开关灯 | 声控灯(数据一动,视图就亮) |
- React 更显式:每一步更新都由开发者主动触发,逻辑清晰、可控性强。
- Vue 更隐式:依赖收集 + 响应式代理自动完成更新,开发效率高,但调试复杂场景时可能不够透明。
两者没有绝对优劣,关键在于理解其背后的设计哲学:
React 强调"可预测性" ,Vue 追求"开发幸福感" 。
useEffect 和 useState
在现代前端开发中,React 函数式组件因其简洁、可读性强、逻辑复用便利等优势,已成为主流开发范式。而让函数组件具备状态管理 与副作用处理 能力的核心,正是 React Hooks。
本文将带你从最基础的 useState 入手,逐步理解为何需要 useEffect,以及如何正确使用它来构建健壮的交互逻辑。
一、"记忆"从何而来?------useState 如何赋予函数组件状态
在类组件时代,我们通过 this.state 管理内部状态;而在函数组件中,useState Hook 提供了同等能力。
基础用法
jsx
import { useState } from 'react';
export default function App() {
const [num, setNum] = useState(1);
return (
<div onClick={() => setNum(num + 1)}>
{num}
</div>
);
}
-
useState(1)接收一个初始值(1),返回一个包含两个元素的数组:- 第一个元素
num:当前状态值; - 第二个元素
setNum:用于更新状态的函数。
- 第一个元素
-
通过解构赋值,我们将其命名为
num和setNum,语义清晰。
🔄 数据流闭环 :
用户触发事件(如点击) → 调用
setNum更新状态 → React 重新渲染组件 → 页面显示新值。这形成了"事件 → 状态 → 渲染"的三角关系,是 React 响应式更新的核心机制。
💡 注意:这里的"事件"不仅指点击,还包括表单输入、定时器、API 回调等任何能触发状态变更的操作。
进阶用法:惰性初始化与函数式更新
jsx
import { useState } from 'react';
export default function App() {
// 惰性初始化:仅在首次渲染时执行
const [num, setNum] = useState(() => {
const num1 = 1 + 2;
const num2 = 2 + 3;
return num1 + num2; // 返回 6
});
return (
<div onClick={() => setNum(prev => {
console.log('上一次的值:', prev);
return prev + 1;
})}>
{num}
</div>
);
}
1. 惰性初始化(Lazy Initialization)
当初始状态需要复杂计算时,可传入一个纯函数作为参数。React 仅在组件首次渲染时调用它,避免重复计算。
✅ 必须是纯函数 :
纯函数要求相同输入必得相同输出 ,且无副作用 (如不发起网络请求、不修改外部变量)。
❌ 错误示例:
js
const [data] = useState(() => fetch('/api').then(res => res.json())); // 异步不可用!
2. 函数式更新(Functional Update)
当新状态依赖于前一个状态时(如多次快速点击),应使用函数形式:
js
setNum(prev => prev + 1);
这能避免因闭包捕获旧值而导致的状态"滞后"问题。
二、为什么需要 useEffect?------副作用的必然引入
useState 只负责声明状态 和触发更新 ,但它无法处理副作用(Side Effects)。
什么是副作用?
副作用 = 任何在组件渲染之外发生的、影响或依赖外部系统的行为。
例如:
- 发起 API 请求
- 订阅 WebSocket
- 操作 DOM
- 设置/清除定时器
- 修改全局变量
这些操作不能放在渲染逻辑中(会导致无限循环或性能问题),但又必须在特定时机执行。
纯函数的理想 vs 现实的妥协
理想中的 React 组件是一个纯函数:
Js
(props) => <div>Hello, {props.name}</div>
给定相同的 props 和 state,总是返回相同的 JSX。
但现实应用离不开副作用。于是,useEffect 应运而生------它是 React 专门用于安全处理副作用的 Hook。
三、"行动"何时发生?------useEffect 的三种典型场景
useEffect 接收两个参数:
- 副作用函数:包含你要执行的逻辑;
- 依赖数组(可选) :控制该副作用何时重新执行。
场景 1:组件挂载时执行(模拟 componentDidMount)
jsx
import { useState, useEffect } from 'react';
async function queryData() {
return new Promise(resolve => {
setTimeout(() => resolve(666), 2000);
});
}
export default function App() {
const [num, setNum] = useState(0);
useEffect(() => {
console.log('useEffect 执行');
queryData().then(data => setNum(data));
}, []); // 空依赖数组 → 仅在挂载时执行一次
console.log('render 执行');
return <div onClick={() => setNum(n => n + 1)}>{num}</div>;
}
🔍 执行顺序 :
控制台先输出
'render 执行',再输出'useEffect 执行'。这是因为 React 优先完成 DOM 渲染,再异步执行副作用,避免阻塞 UI。
此模式常用于初始化数据加载。
场景 2:依赖项变化时执行(类似 Vue 的 watch)
Jsx
useEffect(() => {
console.log('num 变化了:', num);
}, [num]); // 依赖 num
每当 num 更新,副作用函数就会重新执行。适用于监听状态变化并作出响应。
场景 3:清理副作用(防止内存泄漏)
考虑以下代码:
Jsx
useEffect(() => {
const timer = setInterval(() => {
console.log(num); // 注意:这里会打印旧的 num!
}, 1000);
}, [num]);
问题 :每次 num 变化,都会创建一个新的定时器,而旧的未被清除 → 多个定时器同时运行 → 内存泄漏!
解决方案:返回清理函数
Jsx
useEffect(() => {
const timer = setInterval(() => {
console.log(num);
}, 1000);
// 返回清理函数
return () => {
clearInterval(timer);
};
}, [num]);
🧹 清理函数的作用:
- 在下一次副作用执行前,清除上一次的资源;
- 在组件卸载时,自动调用以释放资源。
这对于定时器、事件监听、WebSocket 连接等场景至关重要。
验证卸载清理
Jsx
return (
<>
<div onClick={() => setNum(n => n + 1)}>{num}</div>
{num % 2 === 0 && <Demo />}
</>
);
当 num 为奇数时,<Demo /> 被卸载,其内部的 useEffect 清理函数会自动执行。
结语:Hooks 的哲学 ------ 显式优于隐式
React 通过 useState 和 useEffect,将状态与副作用显式地暴露在函数组件中 。虽然需要开发者手动管理更新与清理,但换来的是更高的可预测性与调试能力。
useState赋予函数"记忆";useEffect赋予函数"行动力";- 两者结合,让函数组件不再"无状态",而是简洁、组合、强大的现代 React 开发基石。
掌握这两个 Hook,你就已经站在了 React Hooks 世界的大门之内。下一步,可以探索 useCallback、useMemo、自定义 Hooks 等高级模式,构建更高效、可维护的应用。