HTML模板不是虚拟DOM的来源,也不参与diff过程;它只是浏览器原生的不渲染DOM片段容器,克隆后直接插入真实DOM,无VNode构造、无响应性、无key比对,仅适用于静态结构复用。

HTML模板不是虚拟DOM的来源,也不参与diff过程
HTML模板(<template></template>)只是浏览器原生的、不渲染的DOM片段容器,它被克隆后直接插入真实DOM,全程不经过VNode构造或diff比对。你写一个<template id="item"><li>{{text}}</li></template>,用cloneNode(true)插入页面,这和虚拟DOM完全无关——没有h()调用,没有render函数,也没有key比对逻辑。
常见误解是“把<template></template>当虚拟DOM用”,其实它连JS对象都不是,只是文档片段(DocumentFragment)。Vue或React的<template></template>标签在SFC或JSX中会被编译器吃掉,最终产出的是h()或React.createElement()调用,跟HTML里的<template></template>元素毫无关系。
- HTML
<template></template>仅用于客户端手动克隆+插入,无响应性、无数据绑定 - 框架中的
<template></template>(如Vue SFC)是编译入口,不是运行时实体 - 服务端返回的HTML字符串里包含
<template></template>,hydrate时也不会把它当VNode复用依据——只看key和节点type
虚拟DOM组件必须显式声明key,HTML模板无法提供这个能力
虚拟DOM diff靠key + type识别同类型节点是否可复用。而HTML模板克隆出来的节点,既没key字段,也没VNode的type(比如函数组件、Fragment等),所以根本进不了diff流程——它就是一次性的DOM插入操作。
如果你用document.querySelector('template').content.cloneNode(true)批量生成列表项,又没手动加id或data-key,后续更新时浏览器只会按顺序替换,删第一个,后面所有节点全跟着位移,输入框内容错乱、事件监听丢失都是必然结果。
来自 Vercel Engineering 的 React 和 Next.js 性能优化指南。用于编写、审查或重构 React/Next.js 代码以确保最佳性
立即学习“前端免费学习笔记(深入)”;
-
key必须是字符串,且在同级节点中唯一;用index作key在动态列表中等于放弃复用 - HTML模板里写
<li key="1">无效——key属性只在JSX或h()调用中被框架读取,HTML解析器会忽略它 - 哪怕你给克隆后的节点手动加
dataset.key,虚拟DOM diff也看不到——它只认VNode上的key字段
HTML模板适合静态结构复用,虚拟DOM适合状态驱动的增量更新
两者适用场景本质不同:HTML模板解决的是“怎么快速插入一段固定结构”,虚拟DOM解决的是“状态变了,DOM怎么最小化更新”。前者快在初始渲染(无JS开销),后者赢在频繁变更(避免全量重排)。
比如做一个只读的配置表格,每行结构固定、数据一次性加载,用<template></template>克隆100行,比React渲染100个<tr>快得多;但要是这个表格支持实时排序、搜索高亮、单元格编辑,虚拟DOM的局部patch就立刻体现出价值——HTML模板只能整块删了重插,触发重排重绘。
<ul>
<li>HTML模板插入后,修改其中文本要用<code>textContent或innerHTML,每次都是全量更新子树
<template></template>生成骨架,再用虚拟DOM接管交互区域Web Components中<template></template>与虚拟DOM可以共存,但职责分明
自定义元素(customElements.define())内部可以用<template></template>定义shadow DOM结构,也可以用JSX或h()生成VNode再patch进去——这是两种正交的技术路径,不是替代关系。
例如一个<my-list></my-list>组件,它的shadow root里用<template></template>放默认样式和空容器,但实际列表项由框架(如Preact)用虚拟DOM渲染到那个容器里。此时<template></template>只管静态壳子,虚拟DOM管动态内容,互不干扰。
- Shadow DOM的
<template></template>不参与外部框架的diff,它是独立的DOM作用域 - 若在shadow root中用
render(vnode, root),那vnode的key只在该root内生效,和light DOM无关 - 不要试图用
innerHTML往shadow root里塞JSX字符串——那只是HTML字符串,不是VNode


















