UseEffect如何让使用者在函数组件中执行副作用操作
大家需要明确的是,生命周期函数与 useEffect 是不同的。
一、概念
useEffect 可以让使用者在函数组件中执行副作用操作。那什么是副作用操作呢?
在 React 中,由 state 的变化导致 UI 发生变化的过程是正常操作,其他操作行为:例如数据请求、直接手动修改 DOM 节点、直接操作页面「修改页面标题等」、记录日志等都是副作用操作。
副作用操作是相对于操作 state 而言的。每一次因为 state 的改变,都有一次对应副作用函数的执行时机。如果 state 多次改变,那么就有多次对应副作用的执行时机。
例如:我希望记录点击的次数。该次数不仅要在页面上显示,也要在页面标题中显示。我们就可以给出如下代码来实现需求。
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在该例子中,修改页面标题的行为是副作用行为,因此我们可以直接使用 useEffect 来定义它。useEffect 的第一个参数为一个回调函数,该回调函数就是我们上面说的副作用函数「effect」,我们想要执行的副作用逻辑都写在该函数中。
二、语法
// 中括号表示参数可选
useEffect(effct[, deps])
useEffect 是 React 提供的 Hook,它能够帮助我们定义 effect 函数。
第一个参数就是副作用函数 effect。
第二个参数表示依赖项,是一个可选参数。当不传入该参数时,每次 UI 渲染 effect 函数都会执行。
但是大多数时候我们并不想任何 state 的变化都一定要执行 effect 函数,这个时候我们可以传入依赖项数组。使用时请确保依赖项数组中为 state/props 的值,表示 effect 只会响应依赖项中状态的变化。如果你在 useEffect 中传入与 state 无关的数据,effect 不会响应它们。
只有当依赖项中是 state 发生变化时,effect 才会与之对应的执行。不同的 state 数据变化通常对应不同的副作用操作。因此我们可以在函数组件中,定义多个 effect。
function Demo() {
const [count, setCount] = useState(0)
const [show, setShow] = useState(false)
useEffect(() => {
// do something
}, [count])
useEffect(() => {
// do other something
}, [show])
...
}
除此之外,我们还可以传入空数组作为依赖项,用于表示依赖项不会发生变化。因此,空数组对应的 effect,就只会在初始化时执行一次,以后就再也不会执行了。
我们通常利用这个特性完成一些初始化工作,例如请求页面数据。
const [list, setList] = useState(0);
// DOM渲染完成之后 effect 函数执行
useEffect(() => {
recordListApi().then(res => {
setList(res.data);
})
}, []);
三、清除副作用
有的时候,副作用函数 effect 执行会留下一些痕迹,因此 useEffect 提供了一种清除副作用的方式。
effect 与 clear effect 是一一对应的紧密关系。因此,我们可以定义一个回调函数由 effect 执行时返回,该函数就是 clear effect 函数。
useEffect(() => {
// dosomething
// 定义 clear effect 函数
return () => {
// clear something
}
}, [])
这里一定要注意该函数与 class 组件中的 componentWillUnmount 的区别,官方文档中的案例存在一定的误导性。
如果 deps 传入空数据,则两者是类似的,否则他们完全不一样,effect 与 clear effect 都有可能执行多次。
clear effect 在下次 effect 执行之前执行,也会在组件销毁之前执行一次。
我们可以借助该特性实现一个防抖的案例。例如我们要实现一个搜索框的功能。文字输入过程中会自动发起搜索请求。为了防止请求发送过于频繁,在高频输入时,不发送接口请求,如果超过了 500ms 下一次输入事件还没有发生,那么就自动请求一次。实现代码如下:
import { useEffect, useState } from 'react'
export default function EffectDemo() {
const [text, setText] = useState('')
useEffect(() => {
let timer = setTimeout(() => {
console.log('发送搜索请求')
}, 500)
return () => {
console.log('清除定时器')
clearTimeout(timer)
}
}, [text])
return (
<div>
<input type="text" placeholder='请输入内容...' onChange={(e) => setText(e.target.value)} />
</div>
)
}
我们在 effect 中定义了定时器,作为延迟操作:500ms 后执行请求逻辑。如果下一次 text 快速发生变化,clear effect 执行会清除掉上一次定义的定时器任务,那么请求逻辑就不会执行。
只有下一次 text 的改变超过了 500ms 时,定时器任务才会如期执行。
执行顺序为:
四、useEffect的使用场景
副作用函数的作用在于执行那些不能直接放在组件渲染过程中的操作。
1、数据请求
使用useEffect从API获取数据,并更新组件状态。
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
};
fetchData();
}, []);
2、订阅外部事件
使用useEffect来订阅和取消订阅外部事件。
useEffect(() => {
const handleScroll = () => {
// 处理滚动事件
};
window.addEventListener('scroll', handleScroll);
return () => {
// 在组件卸载时取消订阅
window.removeEventListener('scroll', handleScroll);
};
}, []);
3、手动操作DOM
使用useEffect来进行手动的DOM操作。
useEffect(() => {
const element = document.getElementById('myElement');
// 执行DOM操作
return () => {
// 在组件卸载时清理DOM
element.remove();
};
}, []);
4、定时器和周期性任务
如果你需要执行定时任务或周期性的操作,useEffect也是一个不错的选择。确保在组件卸载时清理定时器。
5、第三方库集成和初始化
有时候,你可能需要在组件挂载时初始化某个第三方库,或者在组件卸载时清理这些初始化。这也是useEffect的一个应用场景。
五、实际 useEffect 的更多细节
1、Effect 执行时机: React 会在浏览器绘制完成后,再执行 effects。这确保了在一次渲染中,所有的 DOM 操作都已完成。
2、多次调用和清理: useEffect 可能会被多次调用,例如在组件更新时。清理函数将在下一次 effect 执行前执行。
3、调度和协调: React 使用 Fiber 架构进行调度和协调更新,以实现更高效的渲染和更好的用户体验。
六、useEffect 可以模拟哪些生命周期
1、useEffect模拟componentDidMount
当useEffect的第二个参数传入的是一个空列表时,相当于模拟生命周期函数componentDisMount。这个函数的副作用是仅在组件第一次挂载UI的时候调用一次。
通常用它来模拟组件初次挂载时,访问api,获取数据。
2、useEffect模拟componentDidUpdate
如果在使用useEffect时不带上第二个参数,就相当于模拟了生命周期函数componentDidUpdate。每次渲染结束的时候都会被调用。
如果上面的例子去掉第二参数,像下面这样:
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setList(data));
});
(1)在每次渲染后被执行,调用API更新list;
(2)由于list的更新,ui会再次渲染;
(3)程序就陷入了死循环。
因此,要谨慎使用第二个参数为空的情形。
3、useEffect模拟componentWillUnmount
在useEffect中返回一个函数(即清楚副作用)用于模拟component WillUnMount
useEffect(() => {
let timerId = window.setInterval(() => {
console.log(Date.now())
}, 1000)
// 返回一个函数用于模拟 WillUnMount
return () => {
window.clearInterval(timerId)
}
}, [])
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律