HTML本身不支持组件拆分,必须靠JavaScript驱动;template和slot仅为声明性容器,需JS调用cloneNode、attachShadow等API才能实例化、挂载并实现内容分发与作用域隔离。

HTML 本身不支持组件拆分,必须靠 JS 驱动
纯 HTML 没有“组件”概念,<template>、<slot> 或自定义元素(Custom Elements)只是容器或占位符,不自动渲染、不管理生命周期、不隔离作用域。所谓“HTML 实现组件拆分”,本质是用 HTML 做结构声明,靠 JavaScript 控制加载、实例化和挂载。
常见误区是把多个 <div class="component-home"> 写死在 index.html 里再 show/hide——这不算拆分,只是 DOM 切换,代码仍耦合、无法复用、不可按需加载。
-
<template id="home">只是内存中的文档片段,需 JS 调用content.cloneNode(true)才能插入 DOM - 用
<script type="module">加载组件 JS 文件时,路径必须是相对或绝对 URL,不能写./home.js后直接执行——浏览器会报 CORS 或 MIME 类型错误 - 若用
import()动态导入,返回的是 Promise,必须await或.then(),否则innerHTML会塞进去个 [object Module]
用 <template> + JS 实现最小可行组件拆分
这是最轻量、无框架的落地方式,适合静态内容为主、路由简单的小型 SPA。
关键不是写多少 HTML,而是怎么组织加载时机和作用域:
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 每个组件对应一个
<template id="xxx">,内部可含内联样式(<style scoped>不生效,得靠类名前缀或 CSS 自定义属性隔离) - 组件 JS 文件导出一个函数,如
renderHome(container),接收目标 DOM 节点,负责克隆模板、绑定事件、初始化状态 - 路由切换时,清空
main内容,再调用对应 render 函数,例如:renderAbout(document.querySelector('main')) - 避免反复
innerHTML = ...,改用replaceChildren()或textContent = ''+appendChild(),防止事件监听器丢失
为什么不能只靠 <link rel="import"> 或 <iframe>
<link rel="import"> 已被 Chrome 废弃(2019 年起),Firefox 从未支持;<iframe> 隔离太强:父页面无法直接访问子页面 DOM,通信需 postMessage,样式/脚本不共享,滚动、焦点、SEO 全部断裂。
真实项目中踩过的坑:
- 用
<iframe src="about.html">当组件,结果点击链接跳转整个 iframe,而非主页面 URL ——history.pushState对 iframe 无效 - 试图用
document.write()向 iframe 写入 HTML,但 iframe 加载完成前写入会清空其文档,且跨域时直接报错 - 依赖
shadowRoot做样式封装,但手动 attach shadow DOM 后,querySelector默认查不到影子节点内的元素,必须显式调用shadowRoot.querySelector
语义化标签如何配合组件拆分
<main> 是组件挂载的唯一合法父容器,所有路由视图必须严格包裹其中;<header> 和 <nav> 属于壳层(shell),不应随路由销毁重建;而 <article>、<section> 才是组件级语义单位。
容易忽略的细节:
- 每次切换组件后,要重设
document.title,否则浏览器历史记录里全是初始标题 - 用
<aside>放侧边栏时,确保它在<main>外——否则屏幕阅读器会把它当成当前页面内容的一部分 - 加载中状态应放在
<main>内部,用<div aria-busy="true">而非全局遮罩,保证无障碍焦点流连续 - 服务端返回的 index.html 必须包含完整壳层(
<header>、<nav>、<main>),否则 SSR 或直出时结构断裂


















