标签不渲染内容,需用clonenode或importnode手动克隆并插入dom;custom elements需在connectedcallback中操作dom,通过observedattributes监听属性变化;ssr需注意hydration时机,构建内联适合静态组件,运行时加载需避免重复请求和内存泄漏。

template 标签本身不加载任何资源,必须手动克隆才能用
很多人把 <template></template> 当成 Vue 的 <template></template> 或 Web Component 的容器,写完就以为能渲染——结果页面空空如也。浏览器遇到 <template></template> 会直接跳过解析:里面的 <img src="logo.png"> 不发起请求,<script>console.log(1)</script> 不执行,连 data-* 属性都不解析。
真正能用的方式只有一种:document.importNode(tmpl.content, true) 或 tmpl.content.cloneNode(true),再手动插入 DOM。注意两点:
-
importNode()更安全,尤其含<input>或<select></select>时,避免表单状态重置 - 别在
tmpl.content上直接改 DOM——它不属于当前文档树,所有修改无效 - 占位符如
{{title}}必须靠 JS 遍历文本节点替换,不能靠innerHTML = html简单插值,否则事件绑定和输入状态全丢
fetch + insertAdjacentHTML 是最轻量的 HTML 片段按需加载方式
适合纯结构+样式的组件(比如页脚、弹窗模板),比 Custom Element 启动更快,也不依赖构建工具。但有几个硬限制必须绕开:
-
<script></script>标签不会自动执行——要用DOMParser解析后遍历并动态创建script元素(仅限可信源,否则 XSS 风险) - 路径以入口 HTML 所在目录为基准,不是 JS 文件位置;推荐用
new URL('./footer.html', import.meta.url)构造绝对路径 - 开发时若用
file://协议双击打开,fetch直接因 CORS 失败——必须起本地服务(如npx serve) - 重复加载同一片段时,加
cache: 'force-cache'或拼时间戳参数,否则可能拿到 stale response
Custom Elements 才算真正封装的组件,但生命周期容易写错
写个 class CardComponent extends HTMLElement 并调用 customElements.define('my-card', CardComponent),才算完成封装。但常见错误是把 DOM 操作写在构造函数里:
-
constructor()中不能操作 DOM(此时元素还没挂载),所有初始化逻辑得移到connectedCallback() - 想响应属性变化,必须声明
static get observedAttributes() { return ['title']; },并实现attributeChangedCallback() - Shadow DOM 默认不继承外部样式,
<slot></slot>是内容分发唯一标准方式;别用querySelector去穿透取子元素 - SSR 场景下,服务端渲染出的
<my-card></my-card>在客户端 hydration 前是空白的,需配合defer或异步脚本加载
构建阶段内联 vs 运行时加载,选错会导致首屏白屏或缓存失效
构建时内联(如 rollup-plugin-html 或 vite-plugin-html)适合内容稳定、无运行时变量的组件(比如全局 header)。但它不支持传参,所以带 title 这类变量的组件必须升级为 Custom Element 或模板引擎。
运行时加载(fetch 或 import())灵活,但风险更高:
-
import('./card.js')必须包裹在函数或事件回调里,否则变成静态导入,失去按需意义 -
connectedCallback中无条件调用import()会导致 Custom Element 被反复创建/销毁时重复请求 - 资源生命周期常被忽略:移除组件时,没在
disconnectedCallback清理IntersectionObserver、定时器或事件监听器,内存泄漏比 jQuery 时代更难排查
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











