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

React 18中Dropdown组件点击事件绑定后立即触发的问题咨询

React 18 createRoot 下点击事件监听器首次绑定即触发的问题分析

环境版本

"dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }

示例代码

import { MouseEvent, useEffect, useState } from 'react';
import type { ReactNode } from 'react';

type DropdownProps = {
  target: HTMLElement | null;
  children: ReactNode;
};

function Dropdown({ target, children }: DropdownProps) {
  useEffect(() => {
    if (!target) return;

    const listener = () => {
      console.log('listener executed');
    };

    document.addEventListener('click', listener);
    return () => document.removeEventListener('click', listener);
  }, [target]);

  if (!target) {
    return null;
  }

  return <div>{children}</div>;
}

function App() {
  const [target, setTarget] = useState<HTMLButtonElement | null>(null);

  return (
    <div>
      <button
        onClick={(e: MouseEvent<HTMLButtonElement>) =>
          setTarget(e.currentTarget)
        }
      >
        Button
      </button>
      <Dropdown target={target}>dropdown test</Dropdown>
    </div>
  );
}

export default App;

问题现象

首次点击按钮时,绑定到document上的click监听器会立即执行;预期行为是首次点击仅完成监听器绑定,后续点击才触发执行。该问题仅在React 18使用createRoot时出现,React 17使用ReactDom.render时无此现象。

原因分析

核心差异来自React 18 createRoot模式下的事件处理与副作用执行逻辑调整:

  1. 合成事件与副作用的执行时机:React 17中,组件的useEffect副作用会在合成事件处理完成、原生事件冒泡结束后才执行;但React 18在createRoot模式下,状态更新触发的副作用会在合成事件处理过程中同步执行。
  2. 事件冒泡的捕获:点击按钮触发setTarget后,Dropdown组件的target状态更新,随即执行useEffect绑定document的click监听器。此时原生click事件仍处于冒泡阶段,刚绑定的监听器会直接捕获到当前正在冒泡的同一事件,导致立即触发。
  3. 自动批处理的间接影响:React 18的自动批处理特性虽主要作用于状态更新合并,但也间接影响了副作用的调度时机,使得监听器绑定与当前事件冒泡的时序关系发生了变化。

内容的提问来源于stack exchange,提问作者Jeongmin Lee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 06:45:36