React中使用useEffect时按ESC键同时关闭两个弹窗如何解决
弹窗ESC键同时关闭多个弹窗解决方案
问题根因
你当前的实现中,每个弹窗打开时都会直接在全局window对象上绑定ESC按键监听事件,多个弹窗同时开启时,所有绑定的回调函数会被同时触发,因此会出现按一次ESC关闭所有弹窗的问题。
解决方案
方案1:全局弹窗栈管理(推荐,适配多弹窗复杂场景)
全局维护一个弹窗打开队列,仅允许最后打开的最顶层弹窗响应ESC关闭操作:
- 首先定义全局弹窗栈:
// 全局存储打开的弹窗关闭方法,栈尾为当前最顶层弹窗 const popupStack: Array<() => void> = []
- 修改弹窗的事件绑定逻辑:
useEffect(() => { let escHandler: ((event: KeyboardEvent) => void) | null = null if(isOpen && closePopupByClickingOutside) { // 弹窗开启时将当前关闭方法推入栈顶 popupStack.push(closePopup) escHandler = (event: KeyboardEvent) => { if (event.key === 'Escape') { // 仅调用栈顶的弹窗关闭方法 const topCloseFn = popupStack.pop() topCloseFn?.() // 阻止事件触发其他绑定的监听回调 event.stopImmediatePropagation() } } window.addEventListener('keydown', escHandler) } return () => { if(escHandler) { window.removeEventListener('keydown', escHandler) // 弹窗卸载时从栈中移除对应的关闭方法 const currentIndex = popupStack.indexOf(closePopup) if(currentIndex > -1) popupStack.splice(currentIndex, 1) } } // 补充完整依赖项,避免闭包问题 }, [isOpen, closePopup, closePopupByClickingOutside])
方案2:组件内独立绑定事件(适合轻量场景)
将按键事件绑定到弹窗自身DOM节点而非全局window,仅让当前聚焦的弹窗响应ESC:
- 给弹窗根节点设置可聚焦属性并绑定按键事件:
const popupRef = useRef<HTMLDivElement>(null) return ( <div className="popup-root" ref={popupRef} // 设置tabIndex让div可接收键盘事件 tabIndex={-1} onKeyDown={(e) => { if(e.key === 'Escape') closePopup() }} > <div className="close-popup" onClick={() => closePopup()}></div> {/* 其他弹窗内容 */} </div> )
- 弹窗打开时自动聚焦到自身节点:
useEffect(() => { if(isOpen) { popupRef.current?.focus() } }, [isOpen])
注意事项
原useEffect缺少closePopup、closePopupByClickingOutside依赖项,修改时请补充完整,避免出现闭包导致的逻辑异常。
内容的提问来源于stack exchange,提问作者edwardthatch
相关产品推荐
相关产品推荐

