必须在子应用生命周期中主动接管焦点管理——挂载时用requestAnimationFrame确保layout后聚焦主内容区tabindex="-1"元素,卸载前缓存并恢复触发源焦点,激活时通过inert隔离非活跃模块,全局维护焦点上下文以保障Tab导航连续性。

微前端场景下,焦点连贯性断裂不是“偶尔出错”,而是必然发生——子应用挂载/卸载时 DOM 节点被移除或重建,document.activeElement 会丢失、Tab 序列中断、屏幕阅读器播报错乱。必须在子应用生命周期中主动接管,不能依赖浏览器默认行为。
子应用挂载后焦点不自动进入主内容区
子应用 HTML 插入后,焦点仍停留在上一个主应用的按钮或输入框,用户按 Tab 会跳到页面顶部,而非新模块的首个可聚焦项。
- 在子应用
mount回调里,不要直接element.focus()—— 此时 DOM 可能尚未完成样式计算或 layout,尤其含 CSS transition 或动态渲染的组件 - 改用
requestAnimationFrame(() => { /* 查找并 focus */ }),确保浏览器已执行 layout 和 paint - 主内容区应设为
tabindex="-1"占位(如<main id="app-main" tabindex="-1">),避免混入 Tab 流;挂载后立即mainEl.focus() - 若子应用使用 React/Vue,需等框架 commit 完成:React 用
useEffect(() => { mainRef.current?.focus() }, []);Vue 用onMounted(() => nextTick(() => mainEl.focus()))
子应用卸载时焦点回退失效或跳转错误
关闭子应用后,焦点常落到 document.body 或消失,下一次 Tab 从头开始,破坏导航连续性。
- 卸载前必须缓存触发源(例如点击进入子应用的按钮),推荐存为
data-trigger-id属性而非闭包引用 —— 防止该元素被父应用重渲染或移除 - 卸载逻辑中,在 DOM 移除前检查触发源是否仍在文档中:
if (triggerEl && triggerEl.offsetParent !== null) triggerEl.focus() - 若触发源已不存在(比如路由切换导致父组件销毁),退而求其次聚焦主应用的导航栏第一个链接或
#main-content占位元素 - 避免监听
blur或focusout做恢复 —— 子应用 DOM 被删后事件无法可靠触发
跨子应用 Tab 导航越界或顺序混乱
当多个子应用共存(如侧边栏 + 主内容区均为独立微前端),Tab 键可能在它们之间无序跳跃,甚至跳进未激活的子应用 DOM。
立即学习“前端免费学习笔记(深入)”;
- 每个子应用容器必须加
inert属性(或降级用aria-hidden="true"+style="display: none")来隔离非活跃模块 - 激活子应用时,移除其
inert并确保其内部首个可聚焦元素可被快速定位(建议统一约定 class 如.focusable-entry) - 不要依赖全局
querySelectorAll('button, input, [tabindex]')—— 它会跨子应用抓取,导致焦点列表混杂;应在当前激活子应用的根节点下调用root.querySelector(...) - 若使用 Shadow DOM 封装子应用,注意
delegatesFocus: true必须在attachShadow时声明,否则点击宿主不会透传焦点
动态加载的子应用内容延迟聚焦失败
子应用内部分模块(如异步加载的表单、弹窗)渲染完成后,focus() 调用静默失败。
- 对动态插入的内容,禁止在
innerHTML或appendChild后立刻.focus() - 统一用
requestAnimationFrame包裹,且确保目标元素已挂载到当前子应用的 DOM 树中(可用el.getRootNode() === subAppRoot校验) - 若子应用框架提供生命周期钩子(如 qiankun 的
afterMount),优先在其回调中处理,而非监听自定义事件 - 避免用
setTimeout(() => ..., 0)—— 它不保证渲染完成,在微前端多层嵌套下失败率更高
最易被忽略的一点:微前端的“焦点管理”不是子应用各自为政,而是主应用必须维护一份全局焦点上下文(比如当前激活模块、上一焦点源、主内容区占位符),并在生命周期事件中显式同步 —— 否则任何子应用的 mount/unmount 都会成为可访问性的断点。



















