useEffect(回调函数,[依赖项])
- 官方文档
- 我们可以认为,
useEffect()
是一个监视函数,它能够帮助我们再组件挂载的时候执行对应的回调函数或者再我们想要监视的变量变化时执行对应的回调函数。
useEffect
可以做什么?(模拟生命周期)
- 挂载阶段:
- 从上向下执行函数,如果碰到
useEffect
就执行并将 useEffect
传入的副作用函数
推入一个队列(链表),在组件挂载完成之后,将队列(链表)中的副作用函数
执行,并将副作用函数
的返还函数
,推入一个新的队列(链表)
- 更新阶段
- 更新阶段不同于其他阶段对应的函数是否要执行,取决于依赖参数
- 从上向下执行函数,如果碰到
useEffect
就执行并将 useEffect
传入的副作用函数推入一个队列(链表),在组件更新完成之后,找出之前的返回函数
队列,依次准备执行,在执行前会判断该 useEffect
的依赖参数,如果依赖参数改变就执行,否则跳过当前项去看下一项,然后再执行副作用队列,执行时同样判断依赖是否变化,来决定其是否执行,如果执行,就重新获取其对应的返回函数
。
- 卸载阶段:
- 组件即将卸载时,找出其对应的返回函数队列,依次执行!
常规处理副作用的几种情况:
依赖参数不同时有不同的效果:
- 为空: 组件的任何更新,该
useEffect
对应的返回函数
和副作用函数
都执行
- 为空数组: 不监听组件的更新
- 数组中有具体依赖:对应的依赖数据,有变化的时候,才会执行
useEffect()
的三种使用方法:
useEffect(() => { return ()=>{
} })
-------------------------------------------
useEffect(() => {
return ()=>{
} }, [])
-------------------------------------------
useEffect(() => {
return ()=>{
} }, [依赖项])
|
上面提到的返回函数又称清理函数
,它的执行时机为:在组件卸载时以及下一次副作用函数调用之前执行
,由此,我们就可以使用useEffect()
来模拟生命周期函数,如componentWillUnmount
useEffect(()=>{ return ()=>{ console.log("组件即将卸载时执行"); } }, [])
|
不仅如此我们还可以模拟各种各样的生命周期函数
- 组件挂载完之后做某事(
componentDidMounted
)
useEffect(()=>{ console.log("组件挂载完之后执行"); return ()=>{
} },[]);
|
- 组件挂载完成及更新完成做某事(
componentDidUpdate
)
useEffect(()=>{ console.log("组件挂载完成之后及更新完成之后执行"); })
|
- 组件更新完做某事(
componentDidUpdate
)
const isMounted = useRef(false); useEffect(()=>{ if(isMounted.current){ console.log("组件更新完成") } else { isMounted.current = true; } })
|
- 组件即将卸载做某事(
componentWillUnmount
)
useEffect(()=>{ return ()=>{ console.log("组件即将卸载时执行"); } },[]);
|
案例展示:
- 使用
useEffect
重塑上面生命周期的案例: 父组件给子组件提供数据,子组件接收后,渐变展示(定时器),父组件点击销毁子组件后子组件消失,同时关闭定时器
import React,{useEffect,useState} from 'react'
function Son(props){ const [opacity,setOpacity] = useState(1) const [isOpenTimer,setIsOpenTimer] = useState(0) const [count,setcount] = useState(0)
console.log('父组件传过来的数据',props)
var timer = isOpenTimer?setInterval(()=>{ if(opacity >= 0){ setOpacity(opacity - 0.1) }else{ setOpacity(1) } },1000):''
useEffect(()=>{ console.log('延时2s开启定时器'); setTimeout(()=>{ setIsOpenTimer(1) },2000) },[])
useEffect(()=>{ console.log('姓名修改了'); setcount(count+1) },[props.sendName])
useEffect(()=>{ return ()=>{ console.log("组件即将卸载时关闭定时器"); clearInterval(timer) } },[]);
return ( <div> <p>-------------子组件---------------</p> <p> 父组件传过来的名字: <span style={{opacity:opacity}}> {'姓名:'}{props.sendName} - {'修改次数:'+ count} </span> </p> </div> ) }
export default function App() { const [isShowSon,setisShowSon] = useState(1) const [name,setName] = useState('lam')
return ( <div> <p>---------------父组件---------------</p> <button onClick={()=>{ setName('肥林')//修改姓名发送给子组件 }}>提交给子组件</button> {/* 通过标志位判断是否展示子组件 */} {isShowSon && <Son sendName={name}></Son>} <button onClick={()=>{ setisShowSon(0) }}>销毁(关闭渐变展示)</button> </div> ) }
|
结果展示:
useEffect
和useLayoutEffect
有什么区别?
简单来说就是调用时机不同, useLayoutEffect
和原来 componentDidMount
& componentDidUpdate
一致,在react
完成DOM
更新后马上同步调用的代码,会阻塞页面渲染。而useEffect
是会在整个页面渲染完才会调用的代码。官方建议是优先使用useEffect
,
在实际使用时如果想避免页面抖动(在useEffect
里修改DOM
很有可能出现)的话,可以把需要操作DOM
的代码放在useLayoutEffect
里。在这里做点dom
操作,这些dom
修改会和 react
做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。