useEffect
useEffect是用来使函数组件也可以进行副作用操作的React内置函数。
ps:所有的Hook只能在函数组件中使用,在自定义的事件函数以及类组件不能使用Hook。
什么是副作用
函数的副作用就是函数中除了操作函数内部的变量执行业务还操作了函数外部的业务。理解就是假如我们每次执行一个函数,该函数都会操作函数之外的一个全局变量,那么对该全局变量的操作就是这个函数产生的副作用。
在React的项目中,我们的副作用大体可以分为两类,一类是调用浏览器的API,例如使用addEventListener来添加事件监听函数以及取出浏览器的存储信息比如localStorage.getItem()等,另外一类是发起网络请求获取服务器数据。
虽然useEffect基本可以覆盖componentDidMount, componentDidUpdate,componentWillUnmount等生命周期函数组合起来使用的所有场景,但是useEffect和生命周期函数的设计理念还是存在本质上的区别的,如果一味用生命周期函数的思考方式去理解和使用useEffect的话,可能会引发一些奇怪的问题。
useEffect使用语法
import {useEffect} from "react"
useEffect(effect,dependencies)
useEffect的第一个参数Effect是要执行的副作用函数,它可以是任意的自定义函数,可以在这个函数里面操作一些浏览器的API或者和外部环境进行交互比如网络请求等,这个函数会在每次组件渲染完成之后被调用,组件每渲染一次该函数就自动执行一次,组件首次在网页 DOM 加载后,副作用函数也会执行。
useEffect的第二个参数dependencies用于限制该副作用的执行条件,使用一个数组指定副作用函数的依赖项,只有依赖项发生变化,才会重新渲染。如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副作用函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。
副作用是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应。**useEffect()允许返回一个函数,在组件卸载时即组件下一次复用时重新渲染之前会执行该函数,清理副作用。**如果不需要清理副作用,useEffect()就不用返回任何值。
案例1:useEffect基本用法
import { useEffect } from 'react'
export default function Des() {
useEffect(() => {
setInterval(() => {
console.log('Des组件对应的模板在页面中加载完成时调用');
}, 1000);
});
return (
<div>Des组件</div>
)
}
import {useState} from 'react'
import Des from './Des.jsx'
export default function App() {
let [flag,setFlag]=useState(true);
let change=()=>{
setFlag(false)
}
return (
<div>
<p>App组件</p>
{flag&&<Des></Des>}
{/* flag为true时,会执行后面的表达式,逻辑与表达式的值就是后面表达式的返回值显示Des组件 */}
{/* flag为false时,不会执行后面的表达式,不会显示Des组件 */}
<button onClick={change}>销毁Des组件</button>
</div>
)
}
Des组件被销毁了,但是组件中的副作用依然存在,所以需要在组件卸载时即组件下一次复用时重新渲染之前会执行该函数,清理副作用。
案例2:useEffect返回值函数用于清除副作用
import { useEffect } from 'react'
export default function Des() {
useEffect(() => {
let timer=setInterval(() => {
console.log('Des组件对应的模板在页面中加载完成时调用');
}, 1000);
return ()=>{
clearInterval(timer);
}
});
return (
<div>Des组件</div>
)
}
import {useState} from 'react'
import Des from './Des.jsx'
export default function App() {
let [flag,setFlag]=useState(true);
let change=()=>{
setFlag(false)
}
return (
<div>
<p>App组件</p>
{flag&&<Des></Des>}
{/* flag为true时,会执行后面的表达式,逻辑与表达式的值就是后面表达式的返回值显示Des组件 */}
{/* flag为false时,不会执行后面的表达式,不会显示Des组件 */}
<button onClick={change}>销毁Des组件</button>
</div>
)
}
Des组件在销毁时副作用返回函数被执行,组件中的副作用被清除了。
案例3:dependencies参数
import {useState,useEffect} from 'react'
export default function App() {
let [objarr,setObjarr]=useState([]);
useEffect(()=>{
console.log('App组件对应的模板在页面中加载完成时调用');
fetch('http://127.0.0.1:7001/test')
.then(res=>res.json())
.then(data=>{
console.log(data,111);
setObjarr(data)
});
});
return (
<div>
{
objarr.map((el,index)=>{
return(
<div key={index}>
<p>{el.title}</p>
<p>{el.author}</p>
</div>
)
})
}
<p>App组件</p>
</div>
)
}
在组件第一次加载时网络请求,没有设置依赖项为空数组,就会一直发起网络请求。
setObjarr(data)每一次都会得到一个新的引用数据然后刷新页面,就会一直发起网络请求,如果后端发送的数据每次都是不同的就会一直刷新页面就容易造成项目进入死循环。所以需要设置第二个参数是一个空数组,这样副作用函数只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行
import {useState,useEffect} from 'react'
export default function App() {
let [objarr,setObjarr]=useState([]);
useEffect(()=>{
console.log('App组件对应的模板在页面中加载完成时调用');
fetch('http://127.0.0.1:7001/test')
.then(res=>res.json())
.then(data=>{
console.log(data,111);
setObjarr(data)
});
},[]);
return (
<div>
{
objarr.map((el,index)=>{
return(
<div key={index}>
<p>{el.title}</p>
<p>{el.author}</p>
</div>
)
})
}
<p>App组件</p>
</div>
)
}
总结:
- useEffect(() => { }),每次组件渲染都执行
- useEffect(() => { return () => { } }, [ ]),组件第一次渲染执行,组件移除时触发清除副作用的函数
- useEffect(() => { return () => { } }, [count]),count改变才会执行,组件重新渲染前触发清除副作用的函数。这里的依赖项可以填任何数据类型,只要依赖项发生变化才会重新运行副作用函数。
- 一个函数组件中可以使用多个副作用函数。