useEffect和useLayoutEffect的区别

文章详细解释了useLayoutEffect钩子的工作原理,它在浏览器重绘屏幕前执行,强调了其与useEffect的区别,尤其是在性能和渲染顺序上的差异。作者通过实例演示了如何在Tooltip组件中应用useLayoutEffect来计算和调整tooltip的位置。
摘要由CSDN通过智能技术生成

useLayoutEffectuseEffect 的一个版本,在浏览器重新绘制屏幕之前触发,可以理解为布局渲染副作用

useLayoutEffect(setup, dependencies?)

setup 处理副作用的函数。
setup 函数选择性返回一个清理(cleanup)函数。在将组件首次添加到 DOM 之前,React 将运行 setup 函数。在每次因为依赖项变更而重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除之前,React 将最后一次运行 cleanup 函数

可选 dependencies setup 代码中引用的所有响应式值的列表。
响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数。如果你的代码检查工具 配置了 React,那么它将验证每个响应式值都被正确地指定为一个依赖项。依赖项列表必须具有固定数量的项,并且必须像 [dep1, dep2, dep3] 这样内联编写。React 将使用 Object.is 来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行副作用函数。

注意useLayoutEffect 可能会影响性能。尽可能使用 useEffect

用法:在浏览器重新绘制屏幕前计算布局

返回值: useLayoutEffect 返回 undefined

注意事项:

useLayoutEffect` 是一个 Hook,因此只能在 组件的顶层 或自己的 Hook
中调用它。不能在循环或者条件内部调用它。如果你需要的话,抽离出一个组件并将副作用处理移动到那里。

  • StrictMode 启用时,React 将在真正的 setup 函数首次运行前,运行一个额外的开发专有的 setup + cleanup 周期。这是一个压力测试,确保 cleanup 逻辑“映照”到 setup 逻辑,并停止或撤消 setup
    函数正在做的任何事情。如果这导致一个问题,请实现清理函数。
  • 如果你的一些依赖项是组件内部定义的对象或函数,则存在这样的风险,即它们将 导致 Effect 重新运行的次数多于所需的次数。要解决这个问题,请删除不必要的 对象 和 函数 依赖项。你还可以 抽离状态更新 和 非响应式逻辑 到
    Effect 之外。
  • Effect 只在客户端上运行,在服务端渲染中不会运行。
  • useLayoutEffect 内部的代码和所有计划的状态更新阻塞了浏览器重新绘制屏幕。如果过度使用,这会使你的应用程序变慢。如果可能的话,尽量选择 useEffect

比如,我们实现一个tooltip的操作。在tooltip渲染之前我们需要计算鼠标在DOM 的位置,如果空间足够,则在DOM元素上方渲染,反之则在下方渲染。在tooltip渲染之前我们需要计算这些高度,然后再实现渲染。这就需要用到useLayoutEffect这个hooks

function ShowTooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0); // 你还不知道真正的高度

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height); // 现在重新渲染,你知道了真实的高度
  }, []);

  // ... 在下方的渲染逻辑中使用 tooltipHeight ...
}

useLayoutEffect和useEffect的区别:
useLayoutEffect会计算DOM布局,在渲染最终效果,会等,会有顺序的渲染(先渲染初始值,再渲染计算后的值,最终渲染实际的效果,会排先后),所以会阻塞最终渲染的效果,会阻塞浏览器的渲染,但不会引起闪烁(不会显示初始值的渲染效果,只显示最终想要的效果)

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipWrapper from './TooltipWrapper.js';

export default function Tooltip({ children, targetDom }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetDom !== null) {
    tooltipX = targetDom.left;
    tooltipY = targetDom.top - tooltipHeight;
    if (tooltipY < 0) {
      // 它不适合上方,因此把它放在下面。
      tooltipY = targetDom.bottom;
    }
  }

  return createPortal(
    <TooltipWrapper x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipWrapper>,
    document.body
  );
}

useEffect不会顺序渲染,它是同步进行,不会等,所以会显示初始效果,在显示最终效果,渲染2次,不会阻塞渲染,但是会闪烁(会显示初始值)

import { useRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipWrapper from './TooltipWrapper.js';

export default function Tooltip({ children, targetDom }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetDom !== null) {
    tooltipX = targetDom.left;
    tooltipY = targetDom.top - tooltipHeight;
    if (tooltipY < 0) {
      // 它不适合上方,因此把它放在下面。
      tooltipY = targetDom.bottom;
    }
  }

  return createPortal(
    <TooltipWrapper x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipWrapper>,
    document.body
  );
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值