React子组件中按ESC键无法关闭卡片的问题求助
解决ESC键无法关闭非模态全屏卡片的问题
问题背景
需求为按下ESC键关闭全屏非模态卡片,避免表单数据丢失。当前卡片作为子组件已通过useEffect添加全局键盘监听,但ESC键无响应;点击CrossIcon按钮可正常关闭,怀疑与非Modal类组件的特性有关。
可能原因及修复方案
1. 函数引用不稳定导致监听频繁重建
当前handleCloseEditCard函数每次组件渲染都会重新创建,导致useEffect依赖变化,反复执行移除/添加监听操作,可能引发时序问题导致监听失效。
修复: 使用useCallback包裹关闭函数,稳定引用:
const handleCloseEditCard = useCallback((): void => { setIsDrawerOpen(false); }, [setIsDrawerOpen]);
2. 全局事件被其他元素阻止冒泡
页面中若有表单输入框等元素捕获keydown事件并调用event.stopPropagation(),会导致window层面的监听不到事件。
修复: 开启事件捕获模式,确保监听优先触发:
window.addEventListener('keydown', handleKeyDown, true); // 移除监听时需对应传入第三个参数 return () => { window.removeEventListener('keydown', handleKeyDown, true); };
3. 卡片未聚焦导致事件优先级问题
非模态框默认不会自动获取焦点,浏览器可能优先处理其他聚焦元素的键盘事件。
修复: 给卡片根元素添加tabIndex,在卡片打开时主动聚焦:
// 添加ref引用卡片根元素 const cardRef = useRef<HTMLDivElement>(null); // 卡片打开时自动聚焦 useEffect(() => { if (isDrawerOpen && cardRef.current) { cardRef.current.focus(); } }, [isDrawerOpen]); // 给根Stack组件绑定ref和tabIndex <Stack ref={cardRef} tabIndex={-1} // 允许聚焦但不参与tab导航 // ...其他属性 >
4. 键值判断兼容性问题
部分旧版浏览器对Escape键的键值判断存在差异,可兼容处理:
const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) { handleCloseEditCard(); } };
完整修复代码示例
import { useCallback, useEffect, useRef } from 'react'; import { Stack, Typography, Chip } from '@mui/material'; import { CrossIcon, FashionForwardIcon } from './your-icons-path'; import { useStyleDialog } from './your-hooks-path'; import { useTheme } from '@mui/material/styles'; export const EditStyleCardHeader = ( { isMoreSustainableChoice = false, isBi2Style, hasAllData }: EditStyleCardHeaderProps ): React.ReactElement => { const { setIsDrawerOpen, editStyleNumber, isDrawerOpen } = useStyleDialog(); const { palette } = useTheme(); const cardRef = useRef<HTMLDivElement>(null); const handleCloseEditCard = useCallback((): void => { setIsDrawerOpen(false); }, [setIsDrawerOpen]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) { handleCloseEditCard(); } }; window.addEventListener('keydown', handleKeyDown, true); return () => { window.removeEventListener('keydown', handleKeyDown, true); }; }, [handleCloseEditCard]); useEffect(() => { if (isDrawerOpen && cardRef.current) { cardRef.current.focus(); } }, [isDrawerOpen]); return ( <Stack ref={cardRef} tabIndex={-1} direction="row" justifyContent="space-between" height={132} paddingLeft={9} paddingRight={10} minHeight={132} sx={{ borderBottom: `1px solid ${palette.divider}` }} > <Stack direction="row"> <Stack direction='column' marginY={9}> <Stack direction='row' alignItems='center'> <CrossIcon style={{ cursor: 'pointer', marginRight: 16, }} onClick={handleCloseEditCard} /> <Typography variant="headings.h2">EDIT STYLE</Typography> </Stack> <Stack direction='row' marginTop={2} marginLeft={10}> <Typography marginRight={2} variant='body.default' sx={{ color: palette.text.secondary }} > {editStyleNumber} </Typography> {isMoreSustainableChoice && ( <FashionForwardIcon color={palette.success.main} /> )} {isBi2Style && hasAllData && <Chip size='small' label='BI2 Style' sx={{ marginLeft: 2 }} />} </Stack> </Stack> </Stack> </Stack> ); };
内容的提问来源于stack exchange,提问作者Jdeveloper
相关产品推荐
相关产品推荐

