React中useEffect和useLayoutEffect的原理及使用

47 阅读3分钟

useEffect

  • 语法:useEffect(callback,[])有四种使用方式
  1. useEffect(callback)

    • 没有设置依赖 第一次渲染完毕后,执行callback,等价于 componentDidMount
    • 组件每一次更新 都会调用 相当于class组件里面的componentDidUpdate

    import React, { useEffect, useState } from 'react'

    const Demo = ()=>{
      const [num,setNum] = useState(0)

      useEffect(() => {
       // 第一次渲染完毕就执行 获取最新的状态值 每次组件更新后都会执行
        setNum(10)
      })
     
      return <>
          <div>{num}</div>
      </>
    }

    export default Demo
  1. useEffect(callback,[])
    • 设置了 但是没有依赖 只有第一次渲染完毕后 才会执行callback 每一次视图更新完毕后 不再执行callback
    • 类似componentDidMount
     const Demo = ()=>{
       const [num,setNum] = useState(0)
    
       useEffect(() => {
        // 第一次渲染完毕就执行 获取最新的状态值 每次组件更新后不再执行
         setNum(10)
       },[])
      
       return <>
           <div>{num}</div>
       </>
     }
    
     export default Demo
    
  2. useEffect(callback,[依赖的状态[多个]])
    • 第一次渲染完毕后会执行callback
    • 当依赖的状态值(或者多个状态值中的一个发生变化)也会触发callback执行
    • 依赖没有发生变化 组件更新变化的时候 callback不会执行
    const Demo = ()=>{
      console.log("渲染了")
      const [num,setNum] = useState(0)
      const [num2,setNum2] = useState(0)
      useEffect(() => {
       // 第一次渲染完毕就执行 获取最新的状态值
        console.log("渲染了num",num)
        //setNum(10)
      },[num])
       useEffect(() => {
       // 第一次渲染完毕就执行 获取最新的状态值
        console.log("渲染了num2",num)
        //setNum(10)
      },[num2])
      const handle = () => {
        setNum(num + 1);
      };
      const handle2 = () => {
        setNum2(num2 + 1);
      };
      return <>
          <div>{num}</div>
           <div>{num2}</div>
        <button onClick={handle}>新增</button>
        <button onClick={handle2}>新增</button>
      </>
    }
    
    export default Demo
    
  3. useEffect(()=> return ()=>{})
    • 返回的函数,会在组件释放的时候执行
    • 如果组件更新 会把上一次返回的函数执行 「可以“理解为”上一次渲染的组件释放了」

)

 const Demo = ()=>{
   const [num,setNum] = useState(0)

   useEffect(() => {
     // 获取的是上一次的状态值
      return ()=>{
       console.log("useEffect函数执行了")  //输出为上一次的值
      }
   },[])
   const handle = () => {
     setNum(num + 1);
   };
   return <>
       <div>{num}</div>
        <button onClick={handle}>新增</button>
   </>
 }

 export default Demo
 ```
 
  useEffect必须在函数的最外层上下文中调用,不能把其嵌入到条件判断、循环等操作语句中
  ```
  不能把其嵌入到条件判断、循环等操作语句中
  if (num > 5) {
     useEffect(() => {
         console.log('OK');
     });
 } 
  ```
  useEffect如果设置返回值,则返回值必须是一个函数「代表组件销毁时触发」;下面案例      中,callback经过async的修饰,返回的是一个promise实例,不符合要求!!
  ```
 useEffect(async () => {
     let data = await queryData();
     console.log('成功:', data);
 }, []); 
 ```
 
# useLayoutEffect

useLayoutEffect会阻塞浏览器渲染真实DOM,优先执行Effect链表中的callback;
  useEffect不会阻塞浏览器渲染真实DOM,在渲染真实DOM的同时,去执行Effect链表中的callback;
    + useLayoutEffect设置的callback要优先于useEffect去执行!!
    + 在两者设置的callback中,依然可以获取DOM元素「原因:真实DOM对象已经创建了,区别只是浏览器是否渲染」
    + 如果在callback函数中又修改了状态值「视图又要更新」
      + useEffect:浏览器肯定是把第一次的真实已经绘制了,再去渲染第二次真实DOM
      + useLayoutEffect:浏览器是把两次真实DOM的渲染,合并在一起渲染的

  视图更新的步骤:
    第一步:基于babel-preset-react-app把JSX编译为createElement格式
    第二步:把createElement执行,创建出virtualDOM
    第三步:基于root.render方法把virtualDOM变为真实DOM对象「DOM-DIFF」
      useLayoutEffect阻塞第四步操作,先去执行Effect链表中的方法「同步操作」
      useEffect第四步操作和Effect链表中的方法执行,是同时进行的「异步操作」
    第四步:浏览器渲染和绘制真实DOM对象


const Demo = ()=>{
  const [num,setNum] = useState(0)
  
 useLayoutEffect(() => {
    console.log("第一个输出")
  },[num])
  
  useEffect(() => {
    console.log("第二个输出")
  },[num])
  const handle = () => {
    setNum(num + 1);
  };
  
  return <>
      <div>{num}</div>
       <button onClick={handle}>新增</button>
  </>
}

export default Demo
```

useEffect的处理机制.jpg