React模态框按Enter关闭后触发打开按钮事件导致重开问题
问题原因
这个问题是键盘事件时序+同步焦点还原共同导致的:
- 你按Enter键关闭模态框时,事件触发顺序是:
keydown在关闭按钮上触发 → 执行关闭逻辑,模态框开始卸载 →componentWillUnmount里同步执行焦点还原,触发元素(a标签)立刻获得焦点 → 之后keyup事件才进入派发阶段,此时焦点已经在a标签上,事件直接派发给a标签,触发你绑定的onKeyUp逻辑调用click,模态框就被重新打开了。 - 你调用
e.stopPropagation()不生效的原因是:这个keyup事件从一开始的目标元素就是获得焦点的a标签,不是从关闭按钮冒泡上来的,在关闭按钮上阻止事件冒泡根本影响不到它。
可行解决方案
方案1:延迟焦点还原(改动最小,推荐)
把模态框卸载时的焦点还原逻辑放到宏任务队列里,等当前整个键盘事件的生命周期完全执行完毕、keyup事件派发完成后,再把焦点还给触发元素,就不会误捕获到关弹窗的按键事件。
修改ModalContainer的componentWillUnmount代码即可:
componentWillUnmount() { const prevFocusEl = this.nodeFocusedBeforeActivation; if (prevFocusEl) { // 延迟0ms即可,等当前事件循环清空后再执行焦点还原 setTimeout(() => prevFocusEl.focus(), 0); } }
方案2:过滤非自身触发的keyup事件
给触发元素加标记,只有当Enter键的keydown和keyup都发生在触发元素自身时,才响应按键触发点击的逻辑,过滤掉焦点跳转后“接住”的外来keyup事件。
函数组件写法参考如下:
import { useRef } from 'react'; // 组件内部 const keyDownOnTrigger = useRef(false); <a onKeyDown={(e) => { if (e.key === 'Enter') keyDownOnTrigger.current = true; }} onKeyUp={(e) => { if (e.key === 'Enter' && keyDownOnTrigger.current) { keyDownOnTrigger.current = false; e.currentTarget.click(); } }} href="/" onClick={(e) => { e.preventDefault(); setOpen((prev) => !prev); }} > Open Modal </a>
补充说明
实际上带href属性的原生<a>标签、以及<button>这类默认可交互元素,本身就内置了Enter键触发点击的无障碍行为,不需要额外绑定onKeyUp手动触发click。如果不是有特殊兼容需求,移除这个多余的onKeyUp监听也能直接解决问题,同时避免后续其他键盘事件时序问题。
内容的提问来源于stack exchange,提问作者Shawn
相关产品推荐
相关产品推荐

