原生 template 标签不够用,需选轻量模板引擎;lit-html 适合多层嵌套渲染,但须确保数据已解析且每层有唯一 key,避免模板内执行复杂逻辑或副作用函数。

模板引擎选型:原生 template 标签够不够用?
原生 template 标签本身不渲染,只做内容占位,必须配合 JS 手动克隆、填充、插入——对两层嵌套(比如列表+子项)尚可,但遇到“用户→订单→商品→SKU属性”这种四层结构,手写 innerHTML 拼接极易出错,且无法自动处理 undefined 或 null 值。
- 优先考虑轻量级模板引擎:如
mustache(无逻辑、纯变量替换)、handlebars(支持 if/each,但需预编译)、或现代方案lit-html(响应式、类型友好、支持嵌套表达式) - 避免在模板里写复杂 JS 表达式:
{{user.orders.map(o => o.items).flat().length}}这类写法在mustache中直接报错,在handlebars中需注册 helper,在lit-html中虽支持但会破坏可读性 - 若数据来自 API,注意后端返回的字段命名是否统一(如
product_idvsproductId),模板变量名不匹配会导致整块区域空白,且无任何错误提示
lit-html 渲染多层嵌套的实际写法
lit-html 的 html 标签函数支持嵌套模板函数调用,适合分层封装:
const renderProduct = (p) => html`<div class="product">${p.name} — ¥${p.price}</div>`;
const renderItem = (item) => html`
订单 #${order.id}
- ${order.items.map(renderItem)}
${user.name}
- 每层函数职责单一,便于单元测试和复用
- 注意:所有传入
html的参数必须是已解析的数据对象,不能传 Promise 或未 await 的响应体 -
map返回空数组时,${[]}在lit-html中自动渲染为空,不会报错,但若误写成${user.orders?.map?.(...)}且user.orders是undefined,则整个renderUser调用会静默失败(返回空 DocumentFragment)
数据扁平化还是保持嵌套结构?
深层嵌套数据(如树形分类、评论+回复+楼中楼)直接渲染易触发重复计算与内存泄漏:
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
- 不建议在模板里反复调用
find、filter或递归函数;应提前在 JS 层完成结构转换 - 示例:评论区有 3 层嵌套(评论 → 回复 → 楼中楼),可先用 DFS 扁平为带
depth和parentId的一维数组,再用单层map+ CSSmargin-left控制缩进 - 若必须保留嵌套,确保每层都有唯一
key(如item.id),否则lit-html的 diff 算法可能错位更新 DOM,导致输入框失焦或状态丢失
性能陷阱:模板里调用函数 vs 提前计算
模板内每次重渲染都执行函数调用,哪怕结果不变:
- 错误写法:
${getFormattedTime(order.createdAt)}—— 每次 render 都 new Date() + format - 正确做法:在数据准备阶段一次性计算好,存为
order.formattedTime - 对于带副作用的函数(如日志、API 请求),绝对禁止出现在模板中;
lit-html可能因内部优化多次调用同一表达式 - 复杂计算(如金额汇总、权限判断)建议提取为 memoized 函数,或使用
computed(如lit-element的@property({ hasChanged: ... }))
真实项目里,最难调试的往往不是语法错误,而是某一层数据意外为 null 导致整段模板静默消失,或者嵌套层级过深触发浏览器栈溢出——别依赖模板兜底,数据校验和降级 UI 要写在 JS 层。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










