Hooks中useEffect钩子函数详解

163 阅读4分钟

useEffect用途

因为组件是一个纯函数,在项目开发中难免需要进行ajax请求,DOM操作等外部处理,这些称为副作用。 副作用就是指获取外部数据对内部渲染结果产生影响。而副作用的使用也分是否符合规范

  const ref=useRef(null)
  setTimeout(()=>ref.current.focus(),5000)
  const  handelClick=()=>{
  ref.current.focus()
 }

第一个在计时器中定义的ref不合规,因为虽然加定时器代码也是从上向下执行,会影响到后面代码,而在点击事件中合规,然而不可能每次外部操作时都加点击事件。有时需要执行函数之前就对外部操作。因此引入useEffect

返回值(清理函数)

返回值必须是一个纯函数(可以没有),除了用副作用函数获取数据还可以实现别的,就算有依赖项,也会在状态改变时重复执行,比方说多次连接数据库,或者多次监听,就需要用函数来断开数据库/移除监听/结束定时器。 useEfect里面不能写异步?

更严谨的说是不能直接写async这样的修饰符,因为返回的是promise,可以把async写到箭头函数的里面然后再调用。

useEffect(()=>{
//连接数据库,添加监听,定时器
return ()=>{
//断开数据库,移除监听,结束定时器。}
},[page)

返回值的执行时机

1.组件被销毁时

2.第二次执行回调时,先执行上一次回调中的返回值函数

useEffect(()=>{
     console.log('---回调中-----')
return()=>{
      consloe.log('~~~回调中的返回值函数~~~~~,count’}
})

解释:当初次渲染时首先执行useEffect中的第一个log,然后把return返回值函数保存起来,当下一次由于状态改变再触发时,会优先执行刚才保存的返回值函数并且因为的箭头函数,就会把之前的count保存起来,再执行第一个log。也就是说每开启下一次回调先执行上一次回调的清理动作。

useEffect触发时机

import { useEffect } from 'react'
function App() {
  const [count, setCount] = useState(0)
  const handelClick = () => {
    setCount(count + 1)
  }
  useEffect(() => {
    console.log(123)
  })
  return (
    <>
      <button onClick={handelClick}>点击</button>
      {count}
    </>
  )
}
export default App

虽然useEffect在组件中但是初始渲染和更新渲染,都会触发useEffect()->因为每次渲染JSX后,都会发useEffect(),整个当前函数组件作用域的最后时机触发的。

分开处理副作用useEffect的依赖项的使用

可以通过将依赖项数组指定为调用的第二个参数来告诉React跳过不必要的重新运行Effect

  1. 当依赖项为空数组的时候,只会初始触发,更新不会触发
  2. ESLint会检测依赖项是否正确,包括:props,state,计算变量等

依赖项为函数时

但是在如下情况中即使count改变,useEffect也不会执行。因为useEffect的js底层是objec.is(function(){},function(){})判断前后两项是否相等,不相等为false。而下面虽然只是改变count,foo()函数不变,但是count改变重新执行函数后会执行foo导致前后两次foo()在内存中的位置不同。返回false。相当于计算变量。

  const foo=()=>{
    console.log(count);  
  }
  useEffect(()=>{
    foo()
  },[foo])

解决方法:

1.usecallback钩子函数

  const foo = useCallback(() => {
    console.log(count)
  }, [count])
  useEffect(() => {
    foo()
  }, [foo])

在useCallbak内用回调函数,并且依赖项为count,让useEffect内的foo函数前后相等

  1. 在useEffect内定义函数(推荐)
  useEffect(() => {
    const foo = () => {
      console.log(count)
    }
    foo()
  }, [count])

依赖项还是count。

useEffect清理

只要useEffect函数返回值是一个钩子函数,就是清理函数,在当前作用域的最后执行。清理后再开启下一次副作用。

  useEffect(() => {
    console.log('进入')
    return () => {
      console.log('退出')
    }
  })

当卸载组件或更新组件的时候,可以通过useEffect来实现一些清理工作

严格模式下,会检测useEffect是否实现了清理操作 初始化数据时,要注意清理操作,所以更简洁的方式是使用第三方,例如:ahooks中的useRequest

useLayoutEffect 同步执行状态更新

useEffect()是在渲染被绘制到屏幕之后执行的,是异步的;useLayoutEffect()是在渲染之后但在 屏幕更新之前,是同步的

大部分情况下我们采用useEffect(),性能更好。但当你的useEffect里面的操作需要处理DOM,并 且会改变页面的样式,就需要用useLayoutEffect,否则可能会出现闪屏问题