React中的useEffect(副作用)介绍
作者:前端小草籽
useEffect(副作用)介绍
useEffect是用来使函数组件也可以进行副作用操作的。
那么什么是副作用呢?
函数的副作用就是函数除了返回值外对外界环境造成的其它影响。
举个例子,假如我们每次执行一个函数,该函数都会操作全局的一个变量,那么对全局变量的操作就是这个函数的副作用。而在React的世界里,我们的副作用大体可以分为两类,一类是调用浏览器的API,例如使用addEventListener来添加事件监听函数等,另外一类是发起获取服务器数据的请求,例如当用户组件挂载的时候去异步获取用户的信息等。
react官方的原话:
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
- componentDidMount 组件挂载
- componentDidUpdate 组件更新
- componentWillUnmount 组件将要摧毁
语法:
import {useEffect} from "react" useEffect(() => { /** 执行逻辑 */ }, dependencies) //dependencies是一个数组,是可选的 //或者: useEffect(effect?=>clean?, dependencies?)
useEffect的第一个参数 effect 是要执行的副作用函数,它可以是任意的用户自定义函数,用户可以在这个函数里面操作一些浏览器的API或者和外部环境进行交互,网络请求等,这个函数会在每次组件渲染完成之后被调用。
useEffect可以有一个返回值,返回一个函数,系统在组件重新渲染之前调用,用于清除副作用(比如说副作用是定时器,return里面就可以写清除定时器的代码)。它第二个参数dependencies(依赖项)来限制该副作用的执行条件
useEffect(副作用)各种写法的调用时刻
1.写法一:没有依赖项时
useEffect所在组件每次渲染(包括首次)时都要调用:组件中任何变化都会执行(eg:useState解构出的state改了就会引发组件重新渲染,父组件给子组件传递属性的值改变,子组件中的useEffect就会执行)
//没有依赖项 useEffect(()=>{ console.log('组件每次渲染时都要调用(页面每次刷新)'); })
组件生命周期里面有一种生命周期父组件给子组件传新的属性就会调用的生命周期函数,useEffect写法一就可以替代。
组件自身状态变化会调用beforeupdate和updated,useEffect写法一就可以替代。
父组件给子组件传值:
2.写法二:依赖项中有监听的值时
根据依赖项中监听的变量是否变化决定是否执行副作用,变了就执行,不变就不执行。
//依赖项中有值时 //页面首次渲染和父组件给子组件传的属性值和子组件自身的值改变(依赖项改变才会打印) useEffect(() => { console.log('页面首次渲染和依赖项改变的时候才会打印'); },[num,props.title])
3.写法三:依赖项为空数组时
相当于Vue生命周期函数mounted,也就是页面首次渲染(挂载)后,后面不管组件中值咋改变都不会执行了,除非该组件销毁了再重新挂载时才会执行。
useEffect(() => { console.log('页面首次渲染'); },[])
4.写法四:清除副作用写法
(假如副作用是一个定时器,清除定时器,如果不清的话,会出现内存泄漏)
useEffect(() => { let timer = setInterval(()=>{ console.log(66666); },1000) return ()=>{ //当组件下一次渲染前,执行这个函数:清除副作用(计时器就是一种副作用) console.log('当组件下一次渲染前,执行这个函数:清除副作用'); clearInterval(timer); } })//这里没写依赖项,所以页面中的值改变就会刷新,比如useState解构出的state改变
问题:副作用函数不会有缓存,那为什么在副作用函数useEffect里面写了定时器,页面(组件)刷新之后上一个定时器没有清除?
就非要写return 返回一个函数去清除副作用(定时器)
5.写法五:依赖项是一个函数的时候
let fn = ()=>{ console.log(11111111); } useEffect(() => { console.log('此组件渲染时,就会运行(包括首次渲染)'); // 该组件渲染时(包括首次渲染),就会执行副作用 let timer = setInterval(()=>{ console.log(66666); },1000) return ()=>{ //而当该组件下一次运行(渲染)时,才会执行清除副作用函数(第一次渲染不执行(组件首次渲染时),下一次渲染才执行) console.log('当组件下一次渲染前,执行这个函数:清除副作用'); clearInterval(timer); } },[fn])
分析:当依赖项 fn 函数运行的时候,副作用函数才会运行(先执行副作用函数的return返回的函数,把上一次渲染生成的定时器清除,才会执行副作用函数,重新生成一个定时器),但是return返回的函数不一定运行,因为return返回的函数必须要是在该组件下一次渲染时,才会执行。因为fn函数虽然执行,但是并没有引起组件重新渲染,所以并不会执行return返回的函数。
缺点是:函数组件刷新时,函数fn又会重新生成一模一样的,没必要,会占用内存。有没有一种技术,在组件刷新时,这种像fn函数的,不重新生成,就用原来内存中的fn,就不用去重新开辟内存空间去生成函数fn。所以就用到了useCallback
注意点
1.
//这种没依赖项 let [data,setData] = useState(''); useEffect(()=>{ //这里可以进行请求后台数据,但是不能通过setData()将请求回来的数据把页面刷新 //会陷入死循环,页面一刷新 useEffect就会执行,就会请求后台数据,请求回来的数据又 //通过setData()导致页面数据改变然后刷新,这样就会陷入死循环 setData(); }) //如果有依赖项的话,就不会陷入死循环
2.组件重新渲染时,会将那些非hook相关的数据重新生成一份,比如说,
let [num,setNum] = useState(100); //hook相关的数据 let m = 100; //普通的数据 let arr = [100,200,900];//普通的引用数据 let fm = ()=>{} //普通的引用数据
以use开头的那些hook组件刷新时不会重新生成,那些普通的数据会重新生成。
如果依赖项是这种普通的arr,fm等引用数据,组件刷新时,就会重新生成,重新开辟内存空间生成,所以就会导致副作用函数执行,所以一般副作用函数的依赖项都是父组件给子组件传的属性或者useState解构出的state值这些。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。