You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect中createElement创建元素出现重复翻倍问题

问题说明
  • 排查范围仅需关注useEffect相关逻辑,无需理解其余代码
  • 异常现象:悬停text元素时,自定义鼠标光标上的文本出现重复翻倍
  • 实现目标:悬停对应文本元素时,将指定文本追加到自定义光标节点上
  • 现有逻辑说明:所有事件绑定均在useEffect中执行,选择createElement而非innerHTML实现节点追加,是为了满足后续向光标节点追加更多类型内容的扩展需求

原始问题代码

const cursorIntro = document.querySelector(".cursor");
const options = document.querySelector(".introduction .nav-options");

options.addEventListener("mousemove", function s(e) {
  var rect = options.getBoundingClientRect();
  var x = e.clientX - rect.left; //x position within the element.
  var y = e.clientY - rect.top;
  cursorIntro.style.left = x + "px";
  cursorIntro.style.top = y + "px";
});
function OnSelect() {
  const optionsSelection = document.querySelectorAll(".options");
  optionsSelection.forEach((elem, i) => {
    elem.children[1].children[0].children[0].addEventListener(
      "mouseleave",
      () => {
        cursorIntro.removeChild(cursorIntro.lastChild);
      }
    );

    elem.children[1].children[0].children[0].addEventListener(
      "mouseenter",
      () => {
        const createElement = document.createElement("h4");
        createElement.innerText =
          elem.children[1].children[0].children[0].dataset.name;
        cursorIntro.appendChild(createElement);
      }
    );
  });
}

OnSelect();
问题根因

文本重复是两个逻辑缺陷共同导致的:

  1. useEffect未配置正确的依赖项,且没有编写事件解绑的清理逻辑。组件重渲染时会重复给DOM元素绑定相同的事件监听,一次悬停会触发多次节点追加逻辑,直接导致文本数量翻倍。
  2. 节点删除逻辑不可靠。使用removeChild(cursorIntro.lastChild)删除文本时,没有校验被删除的节点是否为之前追加的文本节点,一旦光标内存在其他子元素,就会出现误删其他节点、旧文本残留的问题,后续悬停追加新节点时就会出现重复内容。
修复方案
  1. 给useEffect传入空依赖数组[],保证绑定逻辑仅在组件挂载时执行一次;同时在useEffect的返回值中编写清理函数,组件卸载时解绑所有事件监听,避免重复绑定。
  2. 给动态追加的光标文本设置专属类名,追加前先清除已存在的同类型文本节点,避免重复添加;离开时仅删除对应类名的文本节点,不会误删光标内的其他内容。

修复后参考代码

useEffect(() => {
  const cursorIntro = document.querySelector(".cursor");
  const options = document.querySelector(".introduction .nav-options");
  // 抽离事件回调,方便后续解绑
  const handleMouseMove = (e) => {
    const rect = options.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    cursorIntro.style.left = `${x}px`;
    cursorIntro.style.top = `${y}px`;
  };
  options.addEventListener("mousemove", handleMouseMove);

  // 定义hover文本专属类名,用于精准DOM操作
  const HOVER_TEXT_CLASS = "cursor-hover-text";

  const handleMouseEnter = (e) => {
    // 追加前先清除已有hover文本,从根源避免重复
    const existText = cursorIntro.querySelector(`.${HOVER_TEXT_CLASS}`);
    if (existText) existText.remove();

    const textEl = document.createElement("h4");
    textEl.className = HOVER_TEXT_CLASS;
    textEl.innerText = e.currentTarget.dataset.name;
    cursorIntro.appendChild(textEl);
  };

  const handleMouseLeave = () => {
    // 仅删除hover文本节点,不影响光标其他内容
    const existText = cursorIntro.querySelector(`.${HOVER_TEXT_CLASS}`);
    if (existText) existText.remove();
  };

  const optionsSelection = document.querySelectorAll(".options");
  const bindTargetEls = [];
  optionsSelection.forEach((elem) => {
    const target = elem.children[1].children[0].children[0];
    bindTargetEls.push(target);
    target.addEventListener("mouseenter", handleMouseEnter);
    target.addEventListener("mouseleave", handleMouseLeave);
  });

  OnSelect();

  // 清理函数:组件卸载时解绑所有事件,避免内存泄漏和重复绑定
  return () => {
    options.removeEventListener("mousemove", handleMouseMove);
    bindTargetEls.forEach((el) => {
      el.removeEventListener("mouseenter", handleMouseEnter);
      el.removeEventListener("mouseleave", handleMouseLeave);
    });
  };
}, []);

内容的提问来源于stack exchange,提问作者Myth Vince

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 03:06:29