当前位置: 首页 > 科技观察

重点来了,UseEffect如何让使用者在函数组件中执行副作用操作

时间:2023-03-20 22:56:05 科技观察

重点来了,UseEffect是如何让用户在函数组件Update中进行副作用操作的,让大家久等了。花了相当长的时间优化文章的UI细节,进一步提升阅读体验。大家应该觉得.useEffect是一个比较难掌握的知识点。很多人对它了解不多,就觉得功能组件不受控制。除了难以理解之外,React还提供了一个类似的hook:useLayoutEffect来增加学习难度。对于新手来说,这会要了你的命。很多朋友尝试用类语法中的生命周期来类比理解useEffect,因为官方文档是这样引导的,那么大部分都会陷入一些误区,所以在学习之前,大家需要明确的是生命周期循环函数不同于useEffect。要完全理解和使用该方法,需要对闭包、同步、异步、事件循环等基本概念有清晰的认识。1.useEffect概念允许用户在函数组件中进行副作用操作。那么什么是副作用手术呢?在React中,状态变化引起的UI变化过程是一个正常的操作。其他操作行为:比如请求数据,直接手动修改DOM节点,直接操作页面“修改页面标题等”,记录Logging等,都是副作用操作。副作用操作是相对于操作状态而言的。每次状态发生变化,对应的副作用函数都有一次执行机会。如果状态发生多次变化,对应的副作用就有多次执行机会。例如:我想记录点击次数。这个计数不仅应该显示在页面上,还应该显示在页面标题中。我们可以给出如下代码来实现需求。从'反应'导入{useState,useEffect};functionExample(){const[count,setCount]=useState(0);}useEffect(()=>{//使用浏览器API更新文档标题document.title=`您点击了${count}次`;});return(

你点击了{count}次

setCount(count+1)}>点击我
);}在这个例如,修改页面标题的行为是一个副作用行为,所以我们可以直接使用useEffect来定义它。useEffect的第一个参数是一个回调函数,也就是我们上面提到的副作用函数“effect”,我们要执行的副作用逻辑就写在这个函数里面。2.语法//中括号表示可选参数useEffect(effct[,deps])useEffect是React提供的一个Hook,可以帮助我们定义效果函数。第一个参数是副作用函数effect。第二个参数表示依赖关系,是一个可选参数。不传入该参数时,每次渲染UI时都会执行效果函数。但是大多数时候我们不希望任何状态改变来执行效果函数。这个时候我们可以传入依赖数组。使用时请确保state/props的值在dependency数组中,表示该effect只会响应dependencies中的state变化。如果你在useEffect中传入非状态相关的数据,effect将不会响应它们。只有当dependencies中的state发生变化时,effect才会相应地执行。不同的状态数据变化通常对应不同的副作用。所以我们可以在函数组件中定义多种效果。functionDemo(){const[count,setCount]=useState(0)const[show,setShow]=useState(false)useEffect(()=>{//做某事},[count])useEffect(()=>{//doothersomething},[show])...}另外,我们也可以传入一个空数组作为依赖,表示依赖不会改变。因此空数组对应的effect只会在初始化时执行一次,永远不会再执行一次。我们通常使用这个特性来完成一些初始化工作,比如请求页面数据。const[list,setList]=useState(0);//DOM渲染完成后,效果函数执行useEffect(()=>{recordListApi().then(res=>{setList(res.data);})},[]);三、去除副作用有时候,副作用函数effect的执行会留下一些痕迹,所以useEffect提供了去除副作用的方法。Effect和cleareffect一一对应。因此,我们可以定义一个回调函数,在effect执行时返回,这就是cleareffectfunction。useEffect(()=>{//dosomething//定义清空效果函数return()=>{//clearsomething}},[])这里要注意类组件中这个函数和componentWillUnmount的区别,在官方文档中case有点误导。如果deps传入空数据,则两者类似,否则完全不同,effect和cleareffect都可能执行多次。cleareffect在下一个effect执行之前执行,在组件销毁之前也会执行一次。我们可以利用这个特性来实现一个防抖案例。比如我们要实现一个搜索框的功能。在文本输入期间自动发起搜索请求。为了防止发送请求过于频繁,高频输入时不发送接口请求。如果在500毫秒后没有发生下一个输入事件,则将进行自动请求。实现代码如下:import{useEffect,useState}from'react'exportdefaultfunctionEffectDemo(){const[text,setText]=useState('')useEffect(()=>{lettimer=setTimeout(()=>{console.log('发送搜索请求')},500)return()=>{console.log('清除计时器')clearTimeout(timer)}},[text])return(
setText(e.target.value)}/>
)}我们在效果中定义了一个定时器作为延迟操作:500毫秒后执行请求逻辑。如果下次文本变化很快,执行cleareffect会清除上次定义的定时器任务,请求逻辑不会执行。只有当下一次文本变化超过500ms时,定时器任务才会按计划执行。执行顺序为:4.Case在学习和理解effect的含义时,我们知道状态的改变会触发UI重新渲染,UI渲染完成后会执行effect。然而在实际操作中,我们往往知道自己要执行的副作用逻辑是什么。难点在于我们需要自己设计一个合理的状态。不合理的设计会使程序变得复杂。现在我们要实现如下动画效果:点击红色画布,白色方块执行第一个动画,并显示执行日志。执行完毕后立即执行第二个动画返回点,并显示执行日志。白色方块动画过程中点击事件无效:点击不影响动画执行,结束后再次生效。这种效果的实现对于几个状态的设计最为重要。首先,我们需要用一个状态来表示第一个动画是否执行了anime01。其次,我们需要用一个状态来表示第二个动画是否被执行。最后,我们还可以使用一个额外的状态来判断整个流程是否已经执行完毕。重点思考这种状态的特征和存在的必要性。在实现这个逻辑时,我们只需要知道每个动作的结束时间点,并修改相应的状态即可。例如:第一个动画执行结束,修改anime02为true。完整代码如下:import{useState,useRef,useEffect}from'react';//@ts-ignoreimportanimefrom'animejs';import'./style.scss';exportdefaultfunctionAnimateDemo(){const[anime01,setAnime01]=使用状态(假);const[anime02,setAnime02]=useState(false);constelement=useRef(null);//是不是已经停止了conststoped=useRef(true)useEffect(()=>{anime01&&animate01();anime02&&animate02();},[anime01,anime02]);functionanimate01(){anime({targets:element.current,translateX:400,backgroundColor:'#FF8F42',borderRadius:['0%','50%'],complete:()=>{setAnime01(false)setAnime02(true)}})}functionanimate02(){anime({targets:element.current,translateX:0,backgroundColor:'#FFF',borderRadius:['50%','0%'],easing:'easeInOutQuad',完成:()=>{setAnime02(false);停止ed.current=true}})}functionclickHandler(){if(stopped.current){stopped.current=falsesetAnime01(true);}}return({anime01&&
第一个动画正在执行
}{anime02&&
第二个动画正在执行
}
)}这种情况值得我们进一步思考。一方面,为什么数据需要使用state或者ref?另一方面是不是换个角度去思考效果