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();有惊喜哦~

往期文章

相关推荐
zhengxianyi5152 小时前
vue devSever中如何配置多个proxy 代理及pathRewrite路径重写
前端·javascript·vue.js·proxy·前后端分离·devserver·pathrewrite
登山人在路上2 小时前
Vue3 Watch 完全指南:深度监听与性能优化
前端·javascript·vue.js
CodeSheep2 小时前
公司开始严查午休…
前端·后端·程序员
Rhys..2 小时前
js-运算符 ||
前端·javascript·vue.js
哟哟耶耶2 小时前
component-Echarts圆环数据展示-延长线,label,鼠标移动提示,圆环数据中心总数,信息面板
前端·javascript·echarts
全栈软件开发2 小时前
Fidelity充电桩投资理财系统源码-前端uniapp纯源码+后端PHP
前端·uni-app·php
程序员刘禹锡2 小时前
文档流与盒子模型 (12.25日)
前端·css·css3
plmm烟酒僧2 小时前
使用 OpenVINO 本地部署 DeepSeek-R1 量化大模型(第二章:前端交互与后端服务)
前端·人工智能·大模型·intel·openvino·端侧部署·deepseek
Rhys..2 小时前
js-三元运算符
前端·javascript·数据库