🔥React新手必看:如何用useLayoutEffect提升用户体验🔥

100 阅读5分钟

前言

在现代 Web 开发中,React 为我们提供了强大的工具来构建动态和响应式的用户界面。然而,随着应用复杂度的增加,一些常见的 UI 问题也随之浮现,其中最令人困扰的问题之一就是闪烁或布局抖动 这种现象通常发生在组件首次渲染或状态更新时,用户会看到一个短暂的、不正确的布局,随后才看到最终的正确布局。 这种情况不仅影响用户体验,还可能导致用户对应用的稳定性和专业性产生质疑。

比如这个例子:

function Modal() {
  // 弹窗
  const ref = useRef();
  useEffect(() => {
    const height = ref.current.offsetHeight;
    ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`
  }, [])
  return <div ref={ref} style={{ position: 'absolute', background: 'skyblue', width: '200px', height: '200px' }}>我是弹窗</div>
}

弹窗有几率会闪烁一下再到正确位置。那么该如何解决呢?就要用到 useLayoutEffect这个函数了。

在 React 中,useEffectuseLayoutEffect 是两个常用的 Hook,它们都用于处理副作用操作,但在执行时机和行为上有所不同。useEffect 的回调函数在浏览器绘制之后异步执行,不会阻塞渲染过程,但这也意味着在某些情况下,用户可能会看到不正确的中间状态。而 useLayoutEffect 的回调函数则在浏览器绘制之前同步执行,可以确保用户看到的是最终的正确布局,从而避免闪烁问题。

本文将深入探讨 useLayoutEffect 的工作原理及其在解决闪烁问题中的应用。我们将通过具体的示例来展示如何使用 useLayoutEffect 来避免布局抖动,并与 useEffect 进行对比,帮助你更好地理解何时以及如何使用这两个 Hook。

useLayoutEffect

什么是 useLayoutEffect ?

useLayoutEffectReact 中的一个 Hook,它的行为与 useEffect 非常相似,但有一个关键区别:useLayoutEffect 在浏览器绘制(即DOM更新)之前同步执行。这意味着它会在所有的 DOM 变更之后立即被调用,并且在任何新的渲染之前完成其工作。

它是这么用的(和useEffect一模一样):

useLayoutEffect(() => {  
   console.log('天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳。')  
  }, [])

里面接收一个回调函数依赖项,和useEffect的功能一模一样,只不过useEffect是异步的,而useLayoutEffect是同步的。

useLayoutEffect的执行原理是什么样的?

在这之前我们先看看useEffect的执行的原理吧!看完这一个我们比较熟悉的Hook函数,再看看useLayoutEffect的执行原理你会更加清晰。

useEffect的执行是这样的:

image.png

它在整个页面绘制之后才执行,如果它修改了dom元素的样式等,这个时候我们可能看见修改的中间状态,也就是我们说的闪烁,你会发现页面很快的闪烁了一下,虽然这好像问题不大,但是如果是一个模态框的话,当用户的网速慢的时候,最开始它刷新在左上角,后来才移动到了button下,这就会让人觉得有点捞....

为了避免闪烁问题,我们的useLayoutEffect就登场了!

useLayoutEffect的执行是这样的:

image.png 它是在渲染的过程中,在绘画之前,Layout布局之后进行的。

闪烁问题的解决

这样我们就明白为什么它能解决闪烁问题了,闪烁是因为dom更新了,浏览器必须重画那一部分,而我们利用useLayoutEffect,在绘画之前就改变了元素的位置和样式,触发了Layout,这样浏览器画像素点的时候就是按照我们新设定的位置画的。

通俗点讲,就是我们利用这个Hook函数,把一开始的设计图改了,它一开始想画在右边,我们修改设计图,让浏览器把这个东西画在我们想要的位置上了。

所以我们只需要将例子中的代码由useEffect改为useLayoutEffect就可以完美解决闪烁问题了。

阻塞渲染

由于它是在渲染过程之中执行的,所以它还会阻塞渲染,当useLayoutEffect中有任务没完成的时候,它就会相应地延长渲染时间,导致无法绘画。(当然它本身执行任务就耗时,就会阻塞渲染,只是不明显

二次重排?

useLayoutEffect可能会导致二次重排,这种情况下发生在它操纵dom元素的时候:

假设有一个模态框,我们的需求是:当点击按钮,就在按钮的上方或下方展开(取决于上下所剩下的空间够不够),这个时候我们就需要动态计算按钮上下的空间了吧。

所以我们肯定给它一个初始化的位置,而这个位置就触发了第一次重排Layout,在这里最开始布局的时候,这个模态框处于初始化的位置,之后调用了useLayoutEffect,我们改变了它的位置,由此又触发了重排Layout(因为我们改变了dom元素的位置)

这就是二次重排的原因。

性能问题

它虽然能解决闪烁问题,但它也有性能问题。

比如,useLayoutEffect同步执行的,若回调中包含大量计算或DOM操作,会阻塞浏览器绘制(Paint):

useLayoutEffect(() => {
  // 耗时的DOM操作
  for (let i = 0; i < 10000; i++) {
    document.getElementById("list").appendChild(new Item());
  }
}, []);

它还可能陷入无限循环:

useLayoutEffect(() => {
  setCount(count + 1);  // 每次执行都会触发重新渲染!
}, [count]); // 依赖项包含被修改的状态

所以我们还是要看情况使用哈~

结语

这一期我们学习了useLayoutEffect,它和useEffect很相似,但是useLayoutEffect在渲染过程中执行,useEffect在渲染后执行,而且useEffect是异步函数,useLayoutEffect则是同步函数。

根据它的性质,它能很好的解决闪烁问题,提高用户的体验感,但使用不当也会带来很多的性能问题,所以要酌情使用哦,比如,有耗时性的任务最好交给useEffect异步执行~

这一期就到这里了,我们下次再见xdm!

军人王有胜一脸得意的笑GIF表情包_爱给网_aigei_com.gif