了解React useEffect和useLayoutEffect挂钩的区别

147 阅读4分钟

useEffect 钩子和useLayoutEffect 钩子感到困惑?

在这篇文章中,我们将研究useEffectuseLayoutEffect有什么不同以及它们的使用情况。

在我们深入了解两者的区别之前,值得一提的是,这两个钩子------------------都是

  • 处理React中的副作用
  • 有一个相同的签名

让我们考虑一个计数器的简单例子。

import { useEffect, useState, useLayoutEffect } from 'react';

function App() {
 const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('useEffect is fired');
    //Side effect
  }, [count]);

return(
 <div>
    <h1>Count: {count} </h1>
    <button onClick={() => setCount(count + 1)}>Increment count</button>
 </div>
 )
}

当组件被装入Count: {count} ,屏幕上就会出现。当按钮被点击时,计数器就会递增。

useLayoutEffect被替换成useEffect 时,也会出现同样的行为。

useEffectuseLayoutEffect 有什么不同?

它们之间的区别在于其调用的时间。

这张图将帮助我们更好地理解这个流程。

图片来源 : 钩子流程

useEffect 在渲染器被绘制到屏幕上之后异步运行,解除了浏览器绘制过程的阻塞。

所以这看起来像。

  1. 我们以某种方式引起渲染(通过状态,或父级重新渲染或上下文变化)。 在我们的例子中,通过点击增量按钮。
  2. React内部更新状态。计数器的值被更新为1。
  3. React处理DOM的突变。 <h1>Count: 0 </h1> 被改变为<h1>Count: 1 </h1>
  4. 浏览器将这个DOM变化画在浏览器的屏幕上。在我们的例子中,Count: 1 被画在屏幕上。
  5. useEffect 函数在浏览器绘制完DOM变化后被触发。 打印useEffect is fired

然而,useLayoutEffect 在所有DOM突变之后同步触发。

所以这看起来像。

  1. 我们以某种方式引起渲染(通过状态,或父级重新渲染或上下文变化)。 在我们的例子中,通过点击增量按钮。
  2. React内部更新状态。计数器的值被更新为1。
  3. React处理DOM的突变。 <h1>Count: 0 </h1> 被改变为<h1>Count: 1 </h1>
  4. useLayoutEffect 是在DOM突变后立即触发的。
  5. 浏览器会把这个DOM变化画在浏览器的屏幕上。在我们的例子中,Count: 1 ,被画在屏幕上。

简单地说,useLayoutEffect并不关心浏览器是否已经绘制了DOM变化。 它在DOM变化被计算出来后立即触发这个函数。

useEffect 的用例和useLayoutEffect

决定在useEffect和useLayoutEffect之间选择哪个钩子可能很棘手。

了解情况是关键!

99%的情况下,useEffect 是我们想要使用的。大多数时候,我们是在获取数据和设置事件处理程序,不需要立即发生。 它也不影响页面外观。对于所有这些情况,我们应该使用useEffect 钩子。

那么,什么是使用useLayoutEffect 的正确时机呢?

如果我们的效果会改变DOM(比如获得滚动位置或某个元素的其他样式)或涉及到动画,请选择使用LayoutEffect而不是useEffect。

原因是。

useEffect的钩子是在屏幕画完之后被调用的。 因此,在画完屏幕后立即再次突变DOM,如果突变被客户端看到,将导致闪烁的效果。

让我们以一个工具提示为例。 在这里,我们从DOM测量button ,并操纵工具提示的位置。

const App = () => {
  const [showToolTip, setShowTooltip] = useState(false);
  const buttonRef = useRef();
  const tooltipRef = useRef();

  useEffect(() => {
    if (buttonRef.current == null || tooltipRef.current == null) return;

    const { left, top } = buttonRef.current.getBoundingClientRect();
    tooltipRef.current.style.left = `${left + 120}px`;
    tooltipRef.current.style.top =  `${top - 20}px`;
  }, [showToolTip]);

  return (
    <div>
      <button ref={buttonRef}
        onClick={() => setShowTooltip(prevState => !prevState)}>
        Toggle tooltip
      </button>

      {showToolTip &&
        (<div ref={tooltipRef} 
        style=
        {{ position: 'absolute', 
           border: '1px solid gray', 
           padding: '20px', 
           backgroundColor: 'lightgray'
        }}
        >
          This is a Tooltip!
        </div>)}
    </div>
  )
};

我们看到在按钮被点击后有一小段时间的闪烁。 这是因为useEffect是异步触发的。

用useLayoutEffect替换useEffect可以消除闪烁。

React 18中的useEffect

在React 18中运行同样的useEffect代码不会导致闪烁。

是的,这是真的!

在React 18中,当useEffect是离散输入的结果时,它就会同步发射。

现在,什么是离散输入?

离散输入是一种事件类型,一个事件的结果可以影响下一个事件的行为,比如点击。

在我们的例子中,点击按钮,同步地启动useEffect,避免了闪烁。

要想了解React 18中useEffect的变化,请查看这个链接

结束语

  • 用useLayoutEffect替换useEffect钩子在简单的应用程序中可能不会有明显的效果,强烈建议不要这样做。
  • useLayoutEffect的运行成本很高,所以应该只在需要时使用。
  • 在React 18中,只有在离散事件中useEffect才会同步启动。 对于大多数情况(非离散事件),React会将效果推迟到绘制之后。

请参考这个链接,了解useLayoutEffect和服务器端渲染的情况。