React系列:Hooks-useEffect、useLayoutEffect、useInsertionEffect

1,003 阅读4分钟

一、简介

React hooks提供的api,用于弥补函数组件没有生命周期的缺陷。

1. useEffect

useEffect 基础介绍:

useEffect(()=>{ 
   return destory 
},dep)
  • useEffect 第一个参数 callback, 返回的 destory , destory 作为下一次callback执行之前调用,用于清除上一次 callback 产生的副作用。

  • 第二个参数作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次callback 返回的 destory ,和执行新的 effect 第一个参数 callback 。

  • 对于 useEffect 执行, React 处理逻辑是采用异步调用 ,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。

useEffect 基础用法:

import React, { useState, useRef, useEffect } from "react";
import { Button } from "antd";
import axios from "axios";

const App = (props) => {
  // 电影数据
  const [cinemaList, setCinemaList] = useState([]);
  // 获取dom
  const divRef = useRef();

  const [number, setNumber] = useState(0);
  /* 模拟事件监听处理函数 */
  const handleResize = () => {
    console.log("Resize");
  };
  /* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/
  useEffect(() => {
    /* 请求数据 */
    axios({
      url: "https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=3085018",
      method: "get",
      headers: {
        "X-Client-Info": '{"a":"3000","ch":"1002","v":"5.2.0","e":"1646314068530784943341569"}',
        "X-Host": "mall.film-ticket.cinema.list",
      },
    })
      .then((res) => {
        console.log(res.data);
        setCinemaList(res.data.data.cinemas);
      })
      .catch((err) => {
        console.log(err);
      });

    /* 定时器 延时器等 */
    const timer = setInterval(() => console.log("定时器"), 5000);
    /* 操作dom  */
    console.log(divRef.current); /* div */
    /* 事件监听等 */
    window.addEventListener("resize", handleResize);
    /* 此函数用于清除副作用 */
    return function () {
      clearInterval(timer);
      window.removeEventListener("resize", handleResize);
    };
    /* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行,
       如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */
  }, [props.a,number]);

  return (
    <div ref={divRef}>
      <ul>
        {cinemaList.map((item) => (
          <li key={item.cinemaId}>{item.name}</li>
        ))}
      </ul>

      <Button type="primary" onClick={() => setNumber(1)}>
        {number}
      </Button>
    </div>
  );
};

export default App;

如上在 useEffect(来源)中做的功能如下:

  • ① 请求数据。
  • ② 设置定时器,延时器等。
  • ③ 操作 dom , 在 React Native 中可以通过 ref 获取元素位置信息等内容。
  • ④ 注册事件监听器, 事件绑定,在 React Native 中可以注册 NativeEventEmitter 。
  • ⑤ 还可以清除定时器,延时器,解绑事件监听器等。

2. useLayoutEffect

useLayoutEffect 基础介绍:

useLayoutEffect 和 useEffect 不同的地方是采用了同步执行,那么和useEffect有什么区别呢?

  • 首先 useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前,这样可以方便修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,如果修改 DOM 布局放在 useEffect ,那 useEffect 执行是在浏览器绘制视图之后,接下来又改 DOM ,就可能会导致浏览器再次回流和重绘。而且由于两次绘制,视图上可能会造成闪现突兀的效果。

  • useLayoutEffect callback 中代码执行会阻塞浏览器绘制。

useEffect 基础用法:

const DemoUseLayoutEffect = () => {
    const target = useRef()
    useLayoutEffect(() => {
        /*我们需要在dom绘制之前,移动dom到制定位置*/
        const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
        animate(target.current,{ x,y })
    }, []);
    return (
        <div >
            <span ref={ target } className="animate"></span>
        </div>
    )
}

3. useInsertionEffect

useInsertionEffect 基础介绍:

useInsertionEffect 是在 React v18 新添加的 hooks ,它的用法和 useEffect 和 useLayoutEffect 一样。那么这个 hooks 用于什么呢?

在介绍 useInsertionEffect 用途之前,先看一下 useInsertionEffect 的执行时机。

import React, { useEffect, useLayoutEffect, useInsertionEffect } from "react";

const App = () => {
  useEffect(() => {
    console.log("useEffect 执行");
  }, []);

  useLayoutEffect(() => {
    console.log("useLayoutEffect 执行");
  }, []);

  useInsertionEffect(() => {
    console.log("useInsertionEffect 执行");
  }, []);
};

export default App;

打印: image.png

可以看到 useInsertionEffect 的执行时机要比 useLayoutEffect 提前,useLayoutEffect 执行的时候 DOM 已经更新了,但是在 useInsertionEffect 的执行的时候,DOM 还没有更新。本质上 useInsertionEffect 主要是解决 CSS-in-JS 在渲染中注入样式的性能问题。这个 hooks 主要是应用于这个场景,在其他场景下 React 不期望用这个 hooks 。

useInsertionEffect使用场景:

import React, {useInsertionEffect } from "react";

const App = () => {
  useInsertionEffect(() => {
    /* 动态创建 style 标签插入到 head 中 */
    const style = document.createElement("style");
    style.innerHTML = `
         .css-in-js{
           color: pink;
           font-size: 12px;
         }
       `;
    console.log("style: " , style);
    document.head.appendChild(style);
  }, []);

  return <div className="css-in-js"> useInsertionEffect使用场景 </div>;
};

export default App;

4. useEffect 函数的总结, 如有错误, 感谢指正

useEffect(()=> { return ()=> {}},[])
  • 参数 1 effect 函数 => 发送请求 修改数据 ...
  • 参数 2 effect 函数依赖参数 决定effect 的更新方式
  • return 返还函数 ==> 组件要卸载的时候做一些事情

执行机制

  • 挂载阶段 => 1 先执行useEffect 函数 , 并把effect 函数存入队列等待执行
  • 挂载完成 => 执行effect 函数队列

更新阶段

  • 执行新的的useEffect 函数 , 并肩effect 函数存入队列等待等待执行
  • 执行返还函数队列, 并观察返还函数书否有依赖参数, 有依赖参数, 追踪依赖参数是否改变, 改变执行, 没有改变不执行
  • 执行effect 函数队列, 观察effect 函数是否有依赖参数,有依赖参数, 追踪依赖参数是否改变, 改变执行, 没有改变不执行

卸载阶段

  • 执行返还函数队列