hooks useEffect
1 初探Effect Hook
useEffect(effect,depsArr) => void | Destructor // Destructor 析构函数(c++中的概念) 析构函数和构造函数相反,主要用于对象销毁的时候执行,做一些清理工作
//(alias) useEffect(effect: React. EffectCallback, deps?: React. DependencyList | undefined): void
// type EffectCallback = () => (void | Destructor); // 析构函数(destructor)
useEffect函数接收两个形参,一个函数,一个数组,同时拥有一个函数或者void作为返回值
对于形参函数,react会保存这个函数,我们称这个函数的内容为Effect,react组件需要在渲染后执行这个函数。
对于形参数组:
- 如果没有,那么组件每次渲染都会执行Effect
- 如果是空数组,那么挂载执行一次Effect添加副作用,卸载时执行一次析构函数清除副作用
- 如果有依赖,那么挂载执行一次Effect添加副作用,卸载执行一次析构函数清除副作用。在依赖状态更新的时候,首先执行返回值--析构函数清除上次产生的副作用,然后执行一次Effect添加副作用
什么时候需要清除副作用,什么时候无需清除?
无需清除副作用的情况:
如果组件在每次渲染的时候都要执行某个操作,并且这个操作不存在内存泄露的情况,如定时器和事件监听,那么就没必要清除副作用,此时返回值可不写,比如更新DOM标题
需要清除副作用的情况:
事件的监听和解绑,点击获取鼠标位置等等
形参数组什么时候设置参数,什么时候不设置参数?
无需依赖数组:
如果组件每次渲染都一定要做些添加副作用的操作,那么就没必要设置依赖数组的形参
依赖数组为空:
如果组件只需在挂载的时候做些添加副作用的操作,在卸载的时候再清除副作用,那么就没必要给依赖数组设置元素。对应场景是鼠标点击获取位置的功能组件,只需要在组件卸载的时候,清除事件监听即可
需要依赖数组:
如果组件一定要依赖于某个状态更新的时候清除上次副作用,然后再做些添加副作用的操作,那么就需要给依赖数组设置元素。对应场景是,每次点击都要根据点击次数给文档title赋值,依赖于点击次数这个状态去添加副作用
执行顺序:
useEffect和useLayoutEffect都会拿到最新dom,区别是useLayoutEffect拿到的是最新的DOM,而useEffect拿到的是浏览器渲染组件到屏幕之后的最新DOM,useEffect里面再次调用setState会触发浏览器二次渲染,即重绘与回流。
useEffect是异步的,useLayoutEffect是同步的,异(同)步是相对于浏览器执行刷新屏幕Task来说的。因此useLayoutEffect会阻塞浏览器渲染,但是useEffect会导致浏览器的回流与重绘。
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新
添加副作用和清除上次副作用都在render之前
- 初次挂载添加副作用
- render
- 状态更新,清除上次更新添加的副作用
- 给本次状态更新设置副作用
- render
对于3,析构函数执行去清除上一次副作用的时候,可以通过打印得知拿到的state是更新前的值
1.2 无需清除的Effect
代码时间:使用useEffect使用DOM完成标题更新
因为组件加载和更新的时候执行同样的操作
就是React组件在每次渲染的时候都必须调用它,但是class组件没用提供这样的方法
因此我们必须在这两个方法中写同样的逻辑
componentDidMount(){
document.title = `You clicked ${this.state.count} times` ;
}
componentDidUpdate(){
document.title = `You clicked ${this.state.count} times` ;
}
import React, { useState, useEffect } from "react";
const LikeButton: React. FC = () => {
const [like, setLike] = useState(0)
const [on, setOn] = useState(true)
/*
(alias) useEffect(effect: React.EffectCallback, deps?: React.DependencyList | undefined): void
形参接收一个函数,react会保存这个形参函数,我们称这个函数的内容为Effect,react组件需要在渲染后执行这个函数
useEffect在第一次渲染和每次渲染之后都会执行,不用在意是渲染完成还是更新的时候触发
*/
// type EffectCallback = () => (void | Destructor);
useEffect(()=>{
document.title = `点击了${like}次`
})
return (
<>
<button onClick={() => setLike(like + 1)} >
{like}🐮
</button>
<button onClick={() => {setOn(!on)}}>
{on ? 'ON' : 'OFF'}
</button>
</>
)
}
export default LikeButton
1.3 需要清除的Effect
还要一些副作用是需要清楚的,比如dom事件的移除等等,防止内存泄露
react会在组件卸载的时候执行清除操作
react会在执行当前Effect(也就是组件渲染时)之前对上一个Effect进行清除
useEffect 添加和清除副作用的执行时机:
a. 如果没有给useEffect的依赖数组赋值
1. 组件卸载
2. 组件渲染的时候,执行当前Effect之前,清除上一个Effect(对事件的添加、删除很有用)
b. 如果useEffect的依赖数组是一个空数组
1. 组件初次渲染执行useEffect添加副作用
2. 组件卸载执行useEffect的返回值函数,清除副作用
c. 如果useEffect的依赖数组有值
1. 基于b项
2. 依赖数组的状态对应的状态发生变化时,清除上个Effect的副作用,然后执行useEffect添加副作用
案例: 使用useEffect完成一个鼠标跟踪器
使用class实现
componentDidMount() {
// 三参为true代表在捕获阶段触发
document.addEventListener('click',this.updateMouse)
}
componentWillMount() {
document.removeEventListener('click',this.updateMounse)
}
hooks实现
我们发现这种写法几乎每次组件渲染都会给document添加、移除事件,性能不好
因此探讨一下如何限制useEffect执行次数是很有必要的
import React, { useState, useEffect } from 'react'
const MouseTracker: React. FC = () => {
const [positions, setPositions] = useState({
x: 0, y: 0
})
useEffect(() => {
// 副作用不清除会导致后续点击一次输出多次inner,因为有多个事件在监听
const mouseClicker = (e: MouseEvent) => {
console.log('inner')
setPositions({
x: e.clientX,
y: e.clientY
})
}
document.addEventListener('click', mouseClicker)
// type EffectCallback = () => (void | Destructor)
// // Destructors are only allowed to return void.
// type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
// 清除副作用
return () => {
console.log('outer')
document.removeEventListener('click', mouseClicker)
}
})
return (
<p>{positions.x + " " + positions.y}</p>
)
}
export default MouseTracker
1.3 useEffect执行顺序
// 点击之前
render函数执行之前 0
执行useEffect添加副作用 0
// 点击之后
event handler run
before render 页面渲染之前 320
执行useEffect返回值析构函数DeStructor 清除副作用 0 // 先清除旧的副作用
执行useEffect添加副作用 320 // 再添加新的副作用
2 如何控制Effect调用次数
通过1.1 和 1.2 我们发现,每次组件渲染都会执行useEffect,感觉有些浪费,会导致性能问题
有什么办法能规避这个问题呢?
2.1 只在组件挂载和卸载的时候执行
案例: 修改前两个例子,控制useEffect的执行,如何只执行一次useEffect和清除副作用的返回值函数?
如果useEffect二参传入空数组,那么相当于告诉react你的useEffect并不依赖于任意props或者state
如果我们这样改写,那么副作用的添加和清除只会在组件挂载、清除的时候各自执行一次,即组件挂载只执行一次添加副作用,组件卸载只执行一次清除副作用
APP.tsx
import React, {useState} from 'react';
import MouseTracker from './components/MouseTracker';
const App: React. FC = () => {
const [show, setShow] = useState(true)
return (
<div className="App">
{show && <MouseTracker/>}
<button onClick={()=>setShow(!show)}>toggle MouseTracker</button>
</div>
);
}
export default App;
Component
import React, { useState, useEffect } from 'react'
const MouseTracker: React. FC = () => {
const [positions, setPositions] = useState({
x: 0, y: 0
})
useEffect(() => {
const mouseClicker = (e: MouseEvent) => {
setPositions({
x: e.clientX,
y: e.clientY
})
}
document.addEventListener('click', mouseClicker)
return () => {
document.removeEventListener('click', mouseClicker)
}
}, [])
return (
<p>{positions.x + " " + positions.y}</p>
)
}
export default MouseTracker
2.2 根据依赖数组中的状态执行
import React, { useState, useEffect } from "react";
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
const [on, setOn] = useState(true)
// 点击了多少次并不关系开关的状态,因此只需要依赖于like的值进行更新,只需要在like的值改变的情况下执行
useEffect(() => {
console.log('执行')
document.title = `点击了${like}次`
},[like])
return (
<>
<button onClick={() => setLike(like + 1)} >
{like}🐮
</button>
<button onClick={() => { setOn(!on) }}>
{on ? 'ON' : 'OFF'}
</button>
</>
)
}
export default LikeButton
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端