React Portal绑定的onClick事件仅第二次点击才生效如何修复
问题原因
该问题通常由两类原因导致:
- React 17+ 版本调整了合成事件的委托绑定节点,从之前的全局
document改为了React应用初始化时挂载的根DOM节点。你当前的portal容器直接挂载在body下,不在React根节点的DOM树范围内,首次渲染Portal组件时React事件系统尚未完成对portal容器内事件的关联绑定,导致第一次点击无法被合成事件捕获,首次点击触发后React内部更新了事件绑定关系,后续点击即可正常响应。 - 若你是通过其他元素的点击事件条件渲染
DocumentUploadItem组件,第一次点击会先触发组件渲染逻辑,组件完成挂载时点击事件的传播流程已经结束,自然无法触发刚挂载组件上的onClick回调,第二次点击时组件已存在即可正常触发。
修复方案
- 方案一(最推荐):将portal容器移入React应用的根节点内部
提前在你的React根DOM节点中新增一个portal专用容器,再获取该节点作为portal的挂载目标,保证portal内的元素在React事件委托的覆盖范围内:// 假设你的根HTML中已有结构:<div id="root"><div id="portal-root"></div></div> const portal: HTMLElement = document.getElementById('portal-root'); portal.style.cssText += 'position:relative;z-index:10000'; const DocumentUploadItem = ({ condition }) => { const child = ( <div onClick={() => console.log('click')}> Test </div> ) return ReactDOM.createPortal(child, portal); } - 方案二:改用原生DOM事件绑定,绕开React合成事件的限制
如果你必须将portal挂载到body下,可以直接给元素绑定原生点击事件:import { useRef, useEffect } from 'react'; const portal: HTMLElement = document.createElement('div'); portal.style.cssText += 'position:relative;z-index:10000'; document.body.appendChild(portal); const DocumentUploadItem = ({ condition }) => { const clickRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleClick = () => console.log('click'); clickRef.current?.addEventListener('click', handleClick); return () => clickRef.current?.removeEventListener('click', handleClick); }, []); const child = ( <div ref={clickRef}> Test </div> ) return ReactDOM.createPortal(child, portal); } - 方案三:调整条件渲染的触发时机
如果是条件渲染导致的首次点击无响应,将触发组件显示的事件从click改为mousedown,保证点击事件传播到目标元素前,组件已经完成挂载。
内容的提问来源于stack exchange,提问作者Alopwer
相关产品推荐
相关产品推荐

