useEffect
-
-
-
- 用法
-
- 第二个参数的用法
- 清理副作用
-
- [组件卸载时终止未完成的 Ajax 请求](#组件卸载时终止未完成的 Ajax 请求)
- 获取鼠标在网页中移动时的位置
- 注意事项
-
-
用法
- 执行副作用操作,例如:请求数据、事件监听
函数副作用定义:除了返回值外函数对外界环境造成的其它影响,如获取数据、修改全局变量、更新 DOM。
javascript
useEffect(fn, deps?)
// 第一个参数 fn 是一个副作用函数,该函数会在每次渲染完成之后被调用
// 第二个参数是可选的依赖项数组,这个数组中的每一项内容都会被用来进行渲染前后的对比,依赖项发生变化时,才会重新执行副作用函数
第二个参数的用法
- 不指定依赖项:函数组件每次渲染完成后执行
- 依赖项为空数组:函数组件首次渲染完成后执行一次
- 依赖项为数组:函数组件每次渲染完成,对比渲染前后依赖项是否发生变化,只要变化,副作用函数就重新执行
javascript
import React, { useEffect, useState } from 'react'
export const Counter: React.FC = () => {
const [count, setCount] = useState(0)
const [flag, setFlag] = useState(false)
const add = () => {
setCount((prev) => prev + 1)
}
// 在组件每次渲染完成后,如果 count 值发生了变化,则执行 effect 中的回调
// 其它状态的变化,不会导致此回调函数的重新执行
useEffect(() => {
console.log(document.querySelector('h1')?.innerHTML)
}, [count])
return (
<>
<h1>count 值为:{count}</h1>
<p>flag 的值为:{String(flag)}</p>
<button onClick={add}>+1</button>
<button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
</>
)
}
清理副作用
实际应用:当前组件中使用了定时器或绑定了事件监听程序,可以在返回的函数中清除定时器或解绑监听程序
清理函数的2个触发时机:
- 组件被卸载的时候,会调用
- 当 effect 副作用函数被再次执行之前,会先执行清理函数
javascript
useEffect(() => {
// 1. 执行副作用操作
// 2. 返回一个清理副作用的函数
return () => { /* 在这里执行自己的清理操作 */ }
}, [依赖项])
组件卸载时终止未完成的 Ajax 请求
AbortController 可用于中止fetch请求
创建对象:const controller = new AbortController()
将中止信号与请求绑定:fetch( '请求路径' , { signal: controller.signal })
中止请求:controller.abort()
javascript
const RandomColor: React.FC = () => {
const [color, setColor] = useState('')
useEffect(() => {
const controller = new AbortController()
fetch('https://api.kaka.com/v1/color', { signal: controller.signal })
.then((res) => res.json())
.then((res) => {
console.log(res)
setColor(res.data.color)
})
.catch((err) => console.log('消息:' + err.message))
// return 清理函数
return () => controller.abort()
}, [])
return (
<>
<p>color 的颜色值是:{color}</p>
</>
)
}
获取鼠标在网页中移动时的位置
如果不在组件卸载时清理定时器,下一次再显示时会再创建一个新的定时器,就会有两个定时器都打印位置
javascript
const MouseInfo: React.FC = () => {
// 鼠标的位置
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
// 使用setTimeout定时器,给鼠标操作添加节流操作,500s才执行一次
let timeId : null | NodeJS.Timeout = null
// 1. 要绑定或解绑的 mousemove 事件处理函数,MouseEvent类型
const mouseMoveHandler = (e: MouseEvent) => {
if(timeId !== null) return // 定时器id不为null,证明正在执行,直接return
timeId = setTimeout(() => {
console.log({ x: e.clientX, y: e.clientY })
setPosition({ x: e.clientX, y: e.clientY })
timeId = null // 为开启下一次延时器做准备
},500)
}
// 2. 组件首次渲染完毕后,为 window 对象绑定 mousemove 事件
window.addEventListener('mousemove', mouseMoveHandler)
// 3. 返回一个清理的函数,在每次组件卸载时,为 window 对象解绑 mousemove 事件
return () => window.removeEventListener('mousemove', mouseMoveHandler)
}, [])
return (
<>
<p>鼠标的位置:{JSON.stringify(position)}</p>
</>
)
}
javascript
export const TestMouseInfo: React.FC = () => {
// 控制子组件的显示或隐藏
const [flag, setFlag] = useState(true)
return (
<>
<h3>父组件</h3>
{/* 点击按钮,切换 flag 的值 */}
<button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
<hr />
{flag && <MouseInfo />}
</>
)
}
注意事项
- 不建议把对象作为 useEffect 的依赖项,因为 React 使用 Object.is() 来判断依赖项是否发生变化。
- 若未设置依赖项,不要在 useEffect 中改变依赖项的值,会造成死循环,因为状态更新都要触发useEffect。
- 多个不同功能的副作用尽量分开声明,不要写到一个 useEffect 中。