DOM 操作与焦点管理
DOM 操作与焦点管理 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
焦点、滚动这类 DOM 状态,React 的声明式数据流管不到,需要 ref 接管。这一课你会在对话框场景里完成一套完整的焦点闭环:打开自动聚焦、Esc 关闭、关闭后焦点还给触发按钮。
问题场景
「新增课程」对话框第一版:点「打开」弹出对话框,但输入框没有焦点,键盘用户必须先用鼠标点一下输入框;关闭后焦点直接丢到页面上,Tab 得从头再来一遍。
function CourseDialog({ onClose }) {
// 打开后没人管焦点,全靠用户自己找
return (
<div className="dialog">
<input placeholder="课程名称" />
<button onClick={onClose}>关闭</button>
</div>
);
}
对鼠标用户只是「不顺手」,对键盘和读屏用户则是不可用。
核心解释
焦点是 DOM 的命令式状态:它不体现在 JSX 里,只能通过调用 DOM 方法改变。React 管数据流,DOM 命令交给 ref,两者配合的固定套路是:
- 用
useRef(null)建引用,绑到 JSX 元素上; - 在
useEffect里操作它——Effect 在 DOM 提交之后运行,此刻节点必然已挂载; - 组件卸载前(Effect 清理函数)做善后,比如移除监听、归还焦点。
拿到节点后,node.focus() 聚焦,node.scrollIntoView() 把它滚进可视区,document.activeElement 随时能查「现在焦点在谁身上」——这就是本课的验证手段。
示例拆解
完整的对话框焦点闭环:
function CourseDialog({ onClose }) {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus(); // 挂载后聚焦输入框
function onKeydown(e) {
if (e.key === 'Escape') onClose();
}
document.addEventListener('keydown', onKeydown);
return () => {
document.removeEventListener('keydown', onKeydown); // 卸载前移除监听
};
}, [onClose]);
return (
<div role="dialog" aria-label="新增课程">
<input ref={inputRef} placeholder="课程名称" />
<button onClick={onClose}>关闭</button>
</div>
);
}
function Page() {
const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
return (
<div>
<button ref={triggerRef} onClick={() => setOpen(true)}>打开对话框</button>
{open && (
<CourseDialog
onClose={() => {
setOpen(false);
triggerRef.current?.focus(); // 关闭后焦点还给触发按钮
}}
/>
)}
</div>
);
}
inputRef.current?.focus():条件渲染下节点可能还不存在,?.判空是标配。- Esc 监听挂在
document上,清理函数必须移除,否则组件卸载后还在响应按键。 - 焦点归还是父组件在
onClose里做的:卸载之后子组件的 ref 已失效,能还焦点的只有还活着的触发按钮。
动手任务
打开本课实验区:
- 打开对话框后按 Esc,确认对话框关闭且焦点回到「打开对话框」按钮(直接再按 Enter 能重新打开,说明焦点真的在那里)。
- 把自动聚焦目标从姓名输入框换成「取消」按钮,验证改动只需要换绑 ref。
- 列表区加一个「定位到第 9 条」按钮,用
scrollIntoView+focus让目标项可见且带高亮样式。
常见错误
- 在组件函数体里直接调
focus():渲染期间操作 DOM 是副作用,可能执行多次且时机不对;一律放进useEffect。 - 对没挂载的节点调用方法:条件渲染下
ref.current是null,不判空直接.focus()会抛 TypeError。 - 监听器忘了清理:Esc 监听挂在
document上,卸载不移除就会重复响应、重复关闭。 - 只在桌面浏览器验证:移动端软键盘弹起会挤压视口、改变滚动位置,实验区(桌面环境)验证不了,要用真机按同一份清单再过一遍。
检查与复盘
完成下方检查题确认理解。自查清单:
- 打开自动聚焦、Esc 关闭、焦点归还三步都在实验区验证过
- 能说清为什么焦点操作写在 useEffect 里(DOM 提交后才运行)
- 所有挂在 document/window 上的监听都有对应的移除
参考资料
- React 官方文档:用 ref 操作 DOM —— ref 与 DOM 命令式操作的系统讲解
- React 官方文档:useRef —— API 参考
- MDN:HTMLElement.focus() —— focus 的参数与浏览器行为
读完这一课了?
标记完成后,工作台会安排接下来的学习。