在现代React应用中,无障碍访问(Accessibility)已成为不可或缺的开发要求。本文将深入探讨如何通过自定义Hook实现焦点管理,确保用户能够通过键盘导航顺畅地操作应用。
核心需求分析
当用户使用键盘导航时,焦点应当在可交互元素间正确流转。特别是在动态内容、模态框、下拉菜单等场景中,焦点管理显得尤为重要。传统的focus()方法虽然有效,但在复杂组件结构中容易出现焦点丢失或错误聚焦的问题。
自定义Hook实现
import { useEffect, useRef } from 'react';
const useAccessibilityFocus = (shouldFocus = true) => {
const focusRef = useRef(null);
useEffect(() => {
if (shouldFocus && focusRef.current) {
// 确保元素已渲染并可获取焦点
const element = focusRef.current;
if (element && typeof element.focus === 'function') {
// 检查元素是否可见且可交互
if (element.offsetParent !== null ||
element.offsetWidth > 0 ||
element.offsetHeight > 0) {
element.focus({ preventScroll: true });
}
}
}
}, [shouldFocus]);
return focusRef;
};
高级应用示例
针对模态框场景,我们可进一步封装:
const useModalFocus = (isOpen) => {
const modalRef = useRef(null);
const initialFocusRef = useRef(null);
useEffect(() => {
if (isOpen) {
// 记录当前焦点
initialFocusRef.current = document.activeElement;
// 聚焦到模态框
if (modalRef.current) {
modalRef.current.focus();
}
// 键盘导航控制
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
// ESC键关闭模态框
return;
}
// Tab键循环聚焦
if (e.key === 'Tab') {
const focusableElements = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen]);
// 关闭时恢复焦点
useEffect(() => {
return () => {
if (initialFocusRef.current &&
typeof initialFocusRef.current.focus === 'function') {
initialFocusRef.current.focus();
}
};
}, []);
return modalRef;
};
使用方式
const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const modalRef = useModalFocus(isOpen);
return (
<>
<button onClick={() => setIsOpen(true)}>打开模态框</button>
{isOpen && (
<div ref={modalRef} tabIndex={-1}>
<h2>模态框标题</h2>
<input placeholder="输入内容" />
<button>确认</button>
<button onClick={() => setIsOpen(false)}>关闭</button>
</div>
)}
</>
);
};
性能优化建议
- 使用useRef避免不必要的重新渲染
- 合理使用防抖和节流处理高频事件
- 在组件卸载时清理事件监听器防止内存泄漏
- 通过条件渲染减少不必要的焦点操作
通过以上实现,我们不仅解决了焦点管理的核心问题,还提供了良好的可复用性和扩展性,让无障碍访问变得更加简单可靠。

讨论