一、核心概念与定位
这四个 Hooks 解决的是函数组件的不同核心问题,先通过表格快速区分:
| Hook | 核心作用 | 解决的问题 | 本质 / 返回值 | 关键依赖 |
|---|---|---|---|---|
useState |
管理组件的状态,使函数组件拥有状态 | 函数组件无内置状态(替代 class 的 state) | 返回 [状态, 状态更新函数] |
无依赖,更新函数可缓存 |
useEffect |
处理组件的副作用(异步、DOM 操作等) | 模拟类组件生命周期,处理非渲染逻辑 | 无返回值(可返回清理函数) | 依赖数组控制执行时机 |
useCallback |
缓存函数引用,避免每次渲染重新创建函数 | 因函数引用变化导致子组件 / 依赖重复执行 | 返回缓存的函数 | 依赖数组控制缓存失效 |
useMemo |
缓存计算结果,避免每次渲染重复计算 | 复杂计算导致的性能损耗 | 返回缓存的计算结果 | 依赖数组控制缓存失效 |
二、逐个拆解:用法 + 场景 + 示例
1. useState:状态管理的基础
核心逻辑
- 函数组件的 "状态容器",调用后返回一个数组:
[当前状态, 更新状态的函数]; - 状态更新是异步的(批量更新),更新函数有两种写法:直接传值 / 传回调(获取上一次状态)。
适用场景
- 组件内需要响应式变化的数据(如输入框值、开关状态、列表数据);
- 替代类组件的
this.state和this.setState。
示例
jsx
javascript
import { useState } from 'react';
function Counter() {
// 初始化状态:count=0,setCount是更新函数
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '张三', age: 20 });
// 方式1:直接传值更新(适合不依赖上一次状态)
const handleAdd = () => setCount(count + 1);
// 方式2:回调更新(适合依赖上一次状态,避免异步更新的坑)
const handleAddSafe = () => setCount(prevCount => prevCount + 1);
// 更新对象/数组(需创建新引用,不可直接修改原数据)
const updateUser = () => setUser(prev => ({ ...prev, age: prev.age + 1 }));
return (
<div>
<p>计数:{count}</p>
<button onClick={handleAdd}>+1</button>
<button onClick={handleAddSafe}>安全+1</button>
<p>用户年龄:{user.age}</p>
<button onClick={updateUser}>年龄+1</button>
</div>
);
}
2. useEffect:副作用处理
核心逻辑
-
"副作用" 指:数据请求、DOM 操作、事件绑定、定时器等非渲染相关的操作;
-
依赖数组是核心:
- 无依赖:每次渲染后执行;
- 空依赖
[]:仅挂载时执行(对应componentDidMount); - 有依赖
[a, b]:仅依赖项变化时执行(对应componentDidUpdate); - 返回的清理函数:卸载 / 依赖变化前执行(对应
componentWillUnmount)。
适用场景
- 组件挂载后初始化(请求数据、绑定事件);
- 依赖变化后更新(如 ID 变化重新请求数据);
- 组件卸载前清理(取消请求、解绑事件、清除定时器)。
示例
jsx
javascript
import { useState, useEffect } from 'react';
function DataList({ id }) {
const [data, setData] = useState([]);
useEffect(() => {
// 1. 发起请求(副作用操作)
const controller = new AbortController(); // 用于取消请求
fetch(`/api/data/${id}`, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => console.log('请求取消', err));
// 2. 绑定事件
const handleResize = () => console.log('窗口变化');
window.addEventListener('resize', handleResize);
// 3. 清理函数:卸载/依赖变化时执行
return () => {
controller.abort(); // 取消请求
window.removeEventListener('resize', handleResize); // 解绑事件
};
}, [id]); // 仅 id 变化时重新执行
return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>;
}
3. useCallback:缓存函数引用
核心逻辑
- 函数组件每次渲染时,内部定义的函数会重新创建新的引用;
useCallback会缓存函数引用,只有当依赖数组变化时,才会返回新的函数;- 核心价值:配合
React.memo避免子组件不必要的重渲染。
适用场景
- 传递给子组件的回调函数(尤其是子组件用
React.memo优化时); - 作为
useEffect的依赖项(避免因函数引用变化导致useEffect重复执行)。
示例
jsx
javascript
import { useState, useCallback, memo } from 'react';
// 子组件:用 React.memo 包裹,仅 props 变化时重渲染
const Child = memo(({ onClick, data }) => {
console.log('子组件渲染');
return <button onClick={onClick}>{data}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
// 未缓存的函数:每次渲染都会创建新引用 → 子组件每次都重渲染
const handleClickUncached = () => console.log(name);
// 缓存的函数:仅 name 变化时才创建新引用 → 子组件仅 name 变化时重渲染
const handleClickCached = useCallback(() => {
console.log(name);
}, [name]); // 依赖 name
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>修改count</button>
{/* 点击修改count时,子组件1会重渲染,子组件2不会 */}
<Child onClick={handleClickUncached} data="未缓存函数" />
<Child onClick={handleClickCached} data="缓存函数" />
</div>
);
}
4. useMemo:缓存计算结果
核心逻辑
- 函数组件每次渲染时,内部的复杂计算会重新执行;
useMemo会缓存计算结果,只有当依赖数组变化时,才会重新计算;- 注意:
useMemo是性能优化手段,不要滥用(简单计算无需缓存)。
适用场景
- 复杂计算(如大数据排序、过滤、深拷贝);
- 避免每次渲染都创建新的对象 / 数组(作为子组件 props 时);
- 替代
useCallback缓存函数(useMemo(() => () => {}, [])等价于useCallback(() => {}, []))。
示例
jsx
javascript
import { useState, useMemo } from 'react';
function BigList() {
const [list, setList] = useState(Array(10000).fill(0).map((_, i) => i));
const [keyword, setKeyword] = useState('');
// 复杂计算:过滤大数据列表 → 用 useMemo 缓存结果
const filteredList = useMemo(() => {
console.log('执行过滤计算');
return list.filter(item => item.toString().includes(keyword));
}, [list, keyword]); // 依赖 list 和 keyword
// 简单计算:无需 useMemo(缓存开销 > 计算开销)
const simpleCalc = () => keyword.length * 2;
return (
<div>
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} />
<div>
{filteredList.map(item => <p key={item}>{item}</p>)}
</div>
</div>
);
}
三、关键避坑点
-
useState 坑 :更新对象 / 数组时,必须创建新引用(不可直接修改
prevState); -
useEffect 坑:依赖数组必须声明所有用到的变量 / 函数(否则会捕获旧值);
-
useCallback/useMemo 坑:
- 不要缓存简单函数 / 计算(优化收益 < 缓存开销);
- 依赖数组要精准(漏写依赖会导致缓存值过期);
-
优先级 :先保证功能正确,再用
useCallback/useMemo做性能优化。
总结
- useState:管理组件状态,是函数组件拥有响应式数据的基础;
- useEffect:处理副作用,控制代码的执行时机(模拟生命周期);
- useCallback :缓存函数引用,优化子组件重渲染或
useEffect依赖; - useMemo:缓存计算结果,避免复杂计算重复执行,提升渲染性能。
核心原则:useState/useEffect 是函数组件的 "基础必备",useCallback/useMemo 是 "性能优化工具",按需使用而非无脑添加。