纯HTML不支持组件复用,真正可行的原生方案是Custom Elements;需用带短横线的名称注册,结合template、shadow DOM与BEM规范实现可复用、可维护的静态组件。

纯 HTML 本身不支持组件复用,硬写 <include> 或直接复制粘贴 header/footer,改一处漏九处——这不是组件化,是债务累积。
为什么<template>标签不能直接当组件用
很多人把 <template id="header"><header>...</header></template> 往页面里一放就以为完事了,结果页面空白、样式不生效、JS 找不到元素。原因很实在:<template> 的内容默认不渲染、不执行脚本、不加载资源,它只是个“待命的 DOM 片段”,必须手动克隆并插入文档流。
- 常见错误现象:页面里写了
<template>,但没写 JS 激活逻辑,DOM 中完全看不到内容 - 容易踩的坑:
template.content是一个DocumentFragment,直接appendChild(template)会失败,必须用cloneNode(true) - 如果模板里有
<img src="logo.png">,它不会在<template>阶段加载;但一旦克隆插入,就会立刻请求——这点常被忽略,导致首屏延迟 - 模板内不能写
<script>或<style>标签,它们不会执行或生效;样式得靠外部 class 或 JS 动态注入
不用框架时,真正能跑起来的组件方案只有 Custom Elements
浏览器原生支持、零构建、不依赖后端、可直接粘贴到任意 HTML 文件里运行——目前唯一满足这些条件的,就是 Web Components 中的 customElements.define()。
- 必须带短横线:组件名如
"nav-bar"合法,"navbar"或"NavBar"会被浏览器拒绝注册 -
<template>只负责结构,样式和行为要分开注入:用this.attachShadow({ mode: "open" }),再用adoptedStyleSheets或动态<style>插入 - 事件监听必须绑定在
this.shadowRoot上,document.addEventListener对 Shadow DOM 内部无效 - 若想让全局 CSS 生效(比如重置字体、通用间距),别设
mode: "closed",否则样式完全隔离,连body的字体都继承不到
类名前缀 + BEM + data-* 是静态页面组件化的底线
如果你压根不想碰 JS,或者项目明确禁止运行时逻辑,那“组件化”就退回到组织层面:靠约定让 HTML 块可识别、可移植、不冲突。
立即学习“前端免费学习笔记(深入)”;
- 每个组件外层必须带唯一命名空间前缀,如
class="cmp-header",禁用class="header"这种泛化名 - 子元素严格按 BEM 规范:
cmp-header__logo、cmp-header__nav-item,不嵌套过深,不跨组件用类名 - 用
data-属性暴露配置点,例如<div class="cmp-banner" data-theme="dark" data-closable="true">,后续加 JS 也方便对齐 - 所有尺寸单位用
rem或em,禁用px;颜色、间距、圆角统一走 CSS 变量,比如--cmp-spacing-md: 1.5rem - 组件 HTML 必须自闭合:复制整块代码到新页面,不改 class、不补 JS,也能正常显示——这是检验是否真“可复用”的硬标准
别急着拆,先做一次“重复结构审计”
组件化失败最常见的原因是:拆了一堆没人用的“组件”。不是所有重复都值得抽,也不是所有结构都适合提前封装。
- 只抽那些在 ≥2 个页面出现、且内容字段稳定(如 FAQ 的
question/answer)、未来肯定要批量更新的模块 - 如果某个卡片只在首页用一次,文案也不固定,那就留在页面内写,别硬套
cmp-card - 优先抽页头、导航、Hero 区、FAQ、表单区、页脚——它们变化频率高、结构稳定、复用路径清晰
- 抽之前先统一样式:按钮的 padding、圆角、禁用态颜色、卡片阴影值……如果这些还没收口,抽组件只会把混乱复制成 10 个文件
最易被忽略的一点:组件化不是为了“看起来像现代项目”,而是为了下次改版时,你能精准定位、小范围修改、快速验证。如果一个改动仍要 grep 全局、逐页检查、手动替换,那所谓组件,只是换了个名字的重复劳动。



















