React使用useLayoutEffect解决操作DOM页面闪烁问题

1. 概述

useLayoutEffect 是 React 中用于处理副作用的 Hook 之一,它与 useEffect 功能相似,但执行时机有着显著差异。useLayoutEffect 会在所有的 DOM 变更之后同步执行,这一特性使其特别适合用于需要基于最新 DOM 结构进行操作的场景,例如获取元素尺寸、操作 DOM 样式等,能有效避免因异步执行导致的视觉闪烁或布局抖动问题,确保页面渲染的稳定性和一致性。

2. 基本原理与语法

useLayoutEffect 的原理基于 React 的渲染机制。在 React 进行组件渲染时,会先构建虚拟 DOM,计算出需要更新的部分,然后将这些更新应用到真实 DOM 上。useLayoutEffect 恰好在真实 DOM 更新完成,但浏览器尚未进行绘制之前执行,这就保证了开发者可以在这个阶段安全地访问和操作最新的 DOM 结构。

在语法使用上,useLayoutEffect 需要从 react 库中导入,它接受一个回调函数作为参数,该回调函数中可以编写副作用逻辑。同时,useLayoutEffect 也支持第二个可选参数,即依赖项数组,用于控制副作用的重新执行时机。其语法示例如下:

jsx 复制代码
import React, { useLayoutEffect, useState } from'react';

function App() {
  const [count, setCount] = useState(0);
  useLayoutEffect(() => {
    document.title = `Count is ${count}`;
  }, [count]);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在上述代码中,useLayoutEffect 会在每次 count 状态发生变化,且 DOM 已更新后,立即更新页面的标题。依赖项数组 [count] 确保只有当 count 改变时,useLayoutEffect 的回调函数才会重新执行。

3. 应用场景

下面是一些实际的应用场景:

3.1 获取元素尺寸和位置

在开发图表、拖拽交互等功能时,常常需要获取元素的尺寸和位置信息。例如,在一个动态生成的柱状图组件中,每个柱子的高度需要根据数据动态计算并设置。通过 useLayoutEffect,可以在组件渲染完成后,立即获取容器元素的尺寸,再根据数据计算柱子高度并设置相应的样式,保证图表能够准确渲染,避免因异步计算导致的图表闪烁问题。

jsx 复制代码
import React, { useLayoutEffect, useState, useRef } from'react';

function BarChart({ data }) {
  const chartRef = useRef(null);
  const [barHeights, setBarHeights] = useState([]);

  useLayoutEffect(() => {
    if (chartRef.current) {
      const containerWidth = chartRef.current.offsetWidth;
      const newBarHeights = data.map((value) => {
        // 简单计算柱子高度,实际应用中更复杂
        return (value / Math.max(...data)) * containerWidth;
      });
      setBarHeights(newBarHeights);
    }
  }, [data]);

  return (
    <div ref={chartRef}>
      {data.map((_, index) => (
        <div
          key={index}
          style={{
            width: '20px',
            height: `${barHeights[index]}px`,
            backgroundColor: 'blue',
            marginRight: '5px',
            display: 'inline-block'
          }}
        />
      ))}
    </div>
  );
}

export default BarChart;

3.2 强制同步更新 DOM 样式

当需要根据状态变化立即更新 DOM 样式,且不希望出现视觉延迟时,useLayoutEffect 是最佳选择。比如在实现一个主题切换功能时,用户点击切换主题按钮后,页面的整体样式需要立即改变。利用 useLayoutEffect,可以在状态更新后,同步修改 DOM 的样式类名或直接设置内联样式,确保用户能即时看到主题切换效果,不会出现短暂的样式错乱。

3.3 处理浏览器事件与 DOM 交互

在绑定和处理一些与 DOM 紧密相关的浏览器事件时,useLayoutEffect 能确保事件绑定在正确的 DOM 结构上。例如,为一个可滚动的列表添加滚动事件监听器,需要在列表渲染完成后进行绑定。使用 useLayoutEffect 可以在 DOM 更新后立即绑定滚动事件,并且在组件卸载时正确移除事件监听器,避免内存泄漏,保证交互功能的稳定性。

4. 与其他相关Hook的对比

在 React 的 Hook 体系中,useLayoutEffectuseEffect 最为相似,但它们的执行时机决定了不同的应用场景。useEffect 会在浏览器完成绘制后异步执行,适用于不影响页面视觉效果的副作用操作,如数据请求、订阅事件等。例如,从 API 获取数据并更新组件状态,这种操作不需要即时反馈给用户,使用 useEffect 不会影响页面的流畅度。

useLayoutEffect 由于同步执行的特性,如果在其回调函数中执行复杂计算或耗时操作,可能会阻塞浏览器的渲染,导致页面卡顿。因此,在选择使用时,开发者需要根据副作用操作是否与 DOM 渲染紧密相关,以及是否对即时性有要求来决定使用 useLayoutEffect 还是 useEffect

此外,useLayoutEffectuseStateuseReducer 等状态管理 Hook 也有所不同。useStateuseReducer 主要用于管理组件的状态,而 useLayoutEffect 专注于处理与状态变化相关的副作用,它们在 React 应用开发中承担着不同的角色,相互配合以实现丰富的功能。

5. 结合其他Hook使用

useLayoutEffect 可以与其他 Hook 灵活结合使用,以满足更复杂的需求。例如,与 useRef 结合可以更方便地操作 DOM 元素。在一个实现自动聚焦的输入框组件中,通过 useRef 获取输入框元素的引用,再利用 useLayoutEffect 在组件渲染完成后立即将焦点设置到输入框上,实现用户进入页面即可直接输入的效果。

jsx 复制代码
import React, { useLayoutEffect, useRef } from'react';

function AutoFocusInput() {
  const inputRef = useRef(null);
  useLayoutEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);
  return <input type="text" ref={inputRef} />;
}

export default AutoFocusInput;

同时,useLayoutEffect 也能和 useState 配合,在状态更新后基于最新 DOM 进行操作。比如在一个可折叠面板组件中,当面板展开或收起的状态改变时,useLayoutEffect 可以在 DOM 更新后获取面板的高度等信息,用于实现过渡动画或其他交互效果。

6. 注意事项

  • 避免在 useLayoutEffect 中执行长时间运行的任务或复杂计算,因为它会阻塞浏览器的渲染线程,导致页面卡顿,影响用户体验。如果有复杂计算需求,尽量将其放在 useEffect 或其他异步操作中进行。
  • 合理使用依赖项数组,确保 useLayoutEffect 的回调函数在正确的时机重新执行。错误设置依赖项数组可能会导致副作用执行次数过多或过少,引发难以排查的问题。
  • 由于 useLayoutEffect 会在 DOM 更新后立即执行,频繁触发可能会带来性能问题。在设计组件逻辑时,要尽量减少不必要的 useLayoutEffect 调用,或者通过优化逻辑减少其执行频率。
  • useLayoutEffect 中包含事件监听器等资源时,一定要在组件卸载时正确清理,避免内存泄漏。通常可以在 useLayoutEffect 返回的清理函数中移除事件监听器或取消订阅等操作。

本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~

往期文章

相关推荐
你的代码我的心3 分钟前
微信开发者工具开发网页,不支持tailwindcss v4怎么办?
开发语言·javascript·ecmascript
esmap3 分钟前
OpenClaw与ESMAP AOA定位系统融合技术分析
前端·人工智能·计算机视觉·3d·ai·js
2501_921930838 分钟前
基础入门 React Native 鸿蒙跨平台开发:Video 全屏播放与画中画 鸿蒙实战
react native·react.js·harmonyos
毕设源码-钟学长11 分钟前
【开题答辩全过程】以 基于node.js vue的点餐系统的设计与实现为例,包含答辩的问题和答案
前端·vue.js·node.js
努力d小白11 分钟前
leetcode438.找到字符串中所有字母异位词
java·javascript·算法
小白路过13 分钟前
记录vue-cli-service serve启动本地服务卡住问题
前端·javascript·vue.js
2501_9219308314 分钟前
基础入门 React Native 鸿蒙跨平台开发:react-native-switch 开关适配
react native·react.js·harmonyos
We་ct17 分钟前
LeetCode 1. 两数之和:两种高效解法(双指针 + Map)
前端·算法·leetcode·typescript·哈希算法
LYFlied23 分钟前
边缘智能:下一代前端体验的技术基石
前端·人工智能·ai·大模型
1024小神24 分钟前
用css的clip-path裁剪不规则形状的图片展示
前端·css