useLayoutEffect和useeffect区别

1,212 阅读3分钟

ResizeObserver

该接口可以监听到 Element 的内容区域或 SVGElement的边界框改变, 可以在回调里得到此时元素的尺寸,快速做相应针对布局的操作。比如根据宽度进行一些操作。

const resizeObserver = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius = Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
//监听一个元素的布局变化
resizeObserver.observe(document.querySelector('.box:nth-child(2)'));

useLayoutEffect

先来看下该函数的声明。

type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };

type EffectCallback = () => (void | Destructor);

function useLayoutEffect(
effect: EffectCallback, deps?: DependencyList): void;

react官网对其的描述:

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。它会阻塞视觉更新。

它会在浏览器dom渲染之前进行同步执行,即有阻塞渲染的作用。 useLayoutEffect可以在里面设置对布局更新的监听。 useEffect是在每次render完成并且完成dom更新以后才会被调用。所以 两者的区别是: useLayoutEffect是在做实际的dom更新以前执行,类似于,一个团队里面的发版,有一个人说我马上要发版(setstate)了,有没有谁发版,然后useLayoutEffect定义的effect说我也要发(更新),等一下我(setstate),或者告诉发版的那个人,等你发完后通知我(更新完后通知我 ResizeObserver设置监听) 有点像先知先觉,知道马上要做dom的更新操作了。所以他们会出现在说如果有一个操作改变了state, useEffect是在dom更新以后被调用,是后知后觉。

例子

import "./styles.css";
import React, { useState, useEffect, useLayoutEffect } from "react";
export default function App() {
  let [a, setA] = useState(1);
  console.log('render')
  useEffect(() => {
    console.log('bind')
    var p = new ResizeObserver((e) => {
      console.log(1, e);
    });
    return () => {
      console.log('unbind')
      p.disconnect();
      p.unobserve(document.querySelector(".App"));
    };
  });
  useLayoutEffect(() => {
    
    console.log('layout')
    var p = new ResizeObserver((e) => {
      console.log(2, e);
    });
    p.observe(document.querySelector(".App"));

    return () => {
      console.log('unlayout')
      p.disconnect();
      p.unobserve(document.querySelector(".App"));
    };
  });
  return (
    <div className="App" onClick={() => setA(a + 1)}>
      <h1>Hello CodeSandbox{a}</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}


当点击页面的时候,打印如下:

render 
unlayout 
layout 
unbind 
...这里进行dom更新...
bind 
2

先留个问题,为什么 没有打印useeffect里面的 1 呢?

下面来分析一下:

刚开始 设置了 useEffect回调 useLayoutEffect回调

点击后 ++a;,触发了hooks的重新调用, 所以执行并打印了 render.

useLayoutEffect是先知先觉,effect知道马上要进行dom更新了,先执行了LayoutEffectDestructor,打印出了 unlayout,在里面设置了对某个元素布局变化的监听。开始执行 LayoutEffect .等页面dom更新完成以后,触发了上面对布局监听的回调 打印出了2 然后再去执行 useEffectDestructor,先把上一次对元素布局监听给取消掉,打印出了unbind,然后再打印bind设置监听。 useeffect 因为在useeffect里面每一次更新的监听都在该次的组件更新以前被取消了。再在dom更新以后设置监听,所以无法生效。

总结

  • useLayoutEffect是先知先觉,每次在dom实际更新以前被执行,里面可以再次setstate或者 设置更新布局的监听回调,

  • useEffect 后知后觉,在dom更新以后被执行。可以hold住大多数的场景