HTML 模板 — Salesforce LWC 模板系统完全指南

全面介绍 Lightning Web Components 的 HTML 模板系统。涵盖模板基础与嵌套模板、数据绑定与 JavaScript Getter、HTML 类和内联样式绑定、复杂模板表达式(Beta, API 66.0+)全部类型详解(字面量/三元/箭头函数/数组/可选链)、最佳实践与限制、HTML 语法合规性、条件渲染(lwc:if/elseif/else)、列表渲染(for:each/iterator/key 指令)以及多模板渲染(render() 方法)。...

📅 2026/7/19 ✍️ ponybai 🏷️ lwc, salesforce, frontend

HTML 模板

HTML 模板

Lightning Web Components 的核心能力是其模板系统,它使用虚拟 DOM 来智能高效地渲染组件。最佳实践是让 LWC 操作 DOM,而不是编写 JavaScript 来手动操作。使用 <template> 根标签编写 HTML 模板,还可以使用嵌套的 <template> 标签配合指令(directives)。

HTML 模板基础与嵌套模板

HTML 模板基础与嵌套模板

每个 UI 组件必须有一个 HTML 文件(最大 128 KB),根标签为 <template>。当组件渲染时,<template> 标签会被替换为 <namespace-component-name>,例如 myComponent.html 渲染为 <c-my-component>

嵌套模板指令

嵌套的 <template> 标签必须包含以下指令之一:

  • for:each —— 遍历数组
  • iterator:iteratorname —— 带 first/last/index 的迭代器
  • lwc:if / lwc:elseif / lwc:else —— 条件渲染
  • if:true|false —— 旧版条件渲染(不推荐)
重要规则:嵌套 <template> 标签不能使用其他指令或 HTML 属性(如 class)。需要属性时使用 <div><span> 包装。无效使用会被忽略——模板及其子元素不会渲染到 DOM 中,并在加载组件时返回警告。

数据绑定 —— 属性与 Getter

数据绑定 —— 属性与 Getter

使用 {property} 语法(不加空格)将模板中的属性绑定到 JavaScript 类中的属性。

简单绑定

<!-- hello.html -->
<template>
  Hello, {greeting}!
</template>

// hello.js
import { LightningElement } from "lwc";
export default class Hello extends LightningElement {
  greeting = "World";
}

绑定规则

  • {property} —— 不能有空格({ data } 无效)
  • 有效形式:{data}{data.name}
  • 不支持计算表达式:person[2].name['John']
  • 属性应为原始值(在 for:each/iterator 中使用时除外)

事件绑定

<lightning-input value={greeting} onchange={handleChange}></lightning-input>

handleChange(event) {
  this.greeting = event.target.value;
}

使用 Getter 代替表达式

Getter 是带有 get 关键字的 JavaScript 函数,比内联表达式更强大,且支持单元测试

get uppercasedFullName() {
  return this.firstName + ' ' + this.lastName.toUpperCase();
}
// 模板中:{uppercasedFullName}
响应性:所有字段都是响应式的——当属性值发生变化时,组件会重新渲染,模板中使用的所有表达式都会被重新计算。

绑定 HTML 类(API v62.0+)

绑定 HTML 类

从 API v62.0 开始,类对象绑定简化了动态类名生成:

<div class={computedClassNames}>Evaluate some classes here</div>

get computedClassNames() {
  return [
    "div__block",
    this.position && "div_" + this.position,
    {
      "div_full-width": this.fullWidth,
      hidden: this.hidden,
    },
  ];
}
// 渲染结果:class="div__block div_left div_full-width"

类绑定规则(API v62.0+)

  • 数组:用空格连接,falsy 项自动移除
  • 对象:truthy 值的键被应用
  • 布尔值、数字、函数:被移除(不转为字符串)——使用 String(value) 来渲染

v62.0 vs v61.0 行为变化

v62.0v61.0
false""(空)"false"
10""(空)"10"
["a","b"]"a b""a,b"
{x:true,y:false}"x""[object Object]"
[false,'note',null]"note""false,note,"

绑定内联样式

绑定内联样式

使用 style 属性绑定到返回 CSS 字符串的 getter:

<div class="block" style={inlineStyles}></div>

get inlineStyles() {
  return "width: " + this.percentage + "%; font-size: 20px";
}
最佳实践:优先使用 class + 样式表以提高可重用性;只在需要基于条件的动态计算值时使用内联样式。使用 kebab-case 的 CSS 属性键(如 font-size)。

复杂模板表达式 —— 概述(Beta)

复杂模板表达式概述
API 66.0+。Beta 功能 —— 不要在生产环境中使用。仅用于开发/测试、概念验证、内部工具和学习。

复杂模板表达式允许在 HTML 模板中直接添加 JavaScript 表达式,减少对仅用于显示计算的 getter 的需求。

启用方式:.js-meta.xml 中将 apiVersion 设置为 66.0+。较低版本会回退到基本属性绑定。

前后对比

// 当前(需要 getter):
get propertyName() { /* 计算值 */ }

// Beta 版本(内联表达式):
<div>{`Hello ${name}!`}</div>
<div>{isLoggedIn ? 'Welcome!' : 'Please log in'}</div>
<div>{user && user.name}</div>
<div>{error || 'No error occurred'}</div>
<div>{user?.profile?.name ?? 'Anonymous'}</div>

复杂表达式 —— 字面量与模板字面量

字面量与模板字面量

支持的表达式类型

  • 字符串字面量:{'Hello World'}
  • 数字字面量:{42}{42.42}{0b1001011}(二进制)、{0o42}(八进制)、{0x42}(十六进制)、{2 ** 3}(指数)
  • 布尔字面量:{true}{false}
  • Null 字面量:{null}

模板字面量

<div>{`User: ${firstName} ${lastName}`}</div>
<div>{`Status: ${isActive ? 'Active' : 'Inactive'}`}</div>
<div>{`Price: $${price.toFixed(2)}`}</div>

标签模板字面量

<div>{formatCurrency`Total: ${amount}`}</div>

formatCurrency(strings, amount) {
  return "$" + amount.toFixed(2);
}

复杂表达式 —— 三元与逻辑运算符

三元与逻辑运算符

三元运算符(支持嵌套)

<div>{isLoggedIn ? 'Welcome back!' : 'Please log in'}</div>
<div>{age >= 18 ? 'Adult' : 'Minor'}</div>
<div>{score > 80 ? 'Excellent' : score > 60 ? 'Good' : 'Needs Improvement'}</div>

逻辑与一元运算符

<div>{user && user.name}</div>
<div>{error || 'No error occurred'}</div>
<div>{!foo}</div>
<div>{typeof foo}</div>

二元运算符

算术:+-*/** | 关系:>>====!== | 按位:&|^>><<

HTML 合规关键:< 字符不能在文本节点表达式中使用!使用 >=> 替代。< 仅在带引号的属性中有效。

复杂表达式 —— 函数、数组与对象

函数、数组与对象

函数调用

<div>{formatCurrency(price)}</div>
<div>{items.map(item => item.name).join(', ')}</div>

数组与对象表达式

<div>{[firstName, lastName].join(' ')}</div>
<div>{[1, 2, 3, 4, 5].reduce((a, b) => a + b, 0)}</div>
<div>{({ name: 'John', age: 30 }).name}</div>

可选链与空值合并

<div>{user?.profile?.name ?? 'Anonymous'}</div>
<div>{data?.items?.length ?? 0}</div>

计算属性(方括号记法)

<div>{user['firstName']}</div>
<div>{items[selectedIndex]}</div>
<div>{config[prefix + 'Setting']}</div>

复杂表达式 —— 箭头函数与迭代器

箭头函数与迭代器

箭头函数 —— 内联转换

<div>{items.map(item => item.name.toUpperCase())}</div>
<div>{numbers.filter(n => n > 10).length}</div>
<div>{users.find(user => user.id === currentId)?.name}</div>

箭头函数中允许的赋值

<button onclick="{() => myField = 'foo'}">Set Label</button>
<button onclick="{() => foo++}">Increment Foo</button>

箭头函数中的限制

  • 不允许:块体箭头函数({ return ... }
  • 不允许:异步箭头函数(async () => ...
  • 不允许:函数声明(function(){}

迭代器配合复杂表达式

<template for:each="{getBentoItems()}" for:item="okazu">
  <div key="{okazu}">
    <a onclick="{() => taberu(okazu)}">one</a>
  </div>
</template>

复杂表达式 —— 用户输入与列表渲染示例

用户输入示例

实时格式化

<input value="{name}" oninput="{handleInput}"
    placeholder="Enter your name" />
<div>{name ? `Hello, ${name.trim().toUpperCase()}!`
    : 'Please enter your name'}</div>
<div>{name ? `Your name has ${name.length} characters`
    : ''}</div>
列表渲染

for:each 配合复杂表达式

<template for:each="{getContacts()}" for:item="contact">
  <div key="{contact.id}">
    <p>{`${contact.firstName} ${contact.lastName}`}</p>
    <p>{contact.email ? contact.email : 'No email'}</p>
    <p>{contact.age >= 18 ? 'Adult' : 'Minor'}</p>
  </div>
</template>

iterator 配合 first/last

<template iterator:it="{getActiveContacts()}">
  <div key="{it.value.id}" class="{it.first ? 'list-first'
      : it.last ? 'list-last' : ''}">
    <p>{it.value.email || 'No email'}</p>
  </div>
</template>

更多用法示例

更多用法示例

动态类名

<div class="{isActive && !isDisabled ? 'active' : 'inactive'}">
  <span class="{`status-${getStatus().toLowerCase()}`}">{getStatus()}</span>
</div>

条件渲染

<div>{getUser() ? `Welcome, ${getUser().name}!` : 'Please log in'}</div>
<div>{isLoggedIn && user ? `Hello, ${user.name.toUpperCase()}!` : 'Guest'}</div>

数据格式化

<p>Price: {formatCurrency(getPrice())}</p>
<p>Last updated: {formatDate(lastUpdated ?? Date.now())}</p>
<p>Items: {getItems().length > 0 ? `Found ${getItems().length} items` : 'None'}</p>

列表处理

<p>Active items: {getAllItems().filter(item => item.active).length}</p>
<p>Average price: {getAllItems().reduce((sum,item) =>
    sum + item.price, 0) / getAllItems().length}</p>

复杂表达式最佳实践

复杂表达式最佳实践
  • 保持表达式简单:多行嵌套逻辑 → 使用 getter
  • 复杂逻辑使用 Getter:{totalPrice}{formattedDate} → 更好的可测试性和可重用性
  • 优先使用组件方法:将如 formatCurrency() 等格式化函数放在 API 模块组件中
  • 充分测试(Beta!):单元测试 getter/方法、集成测试渲染、测试边界情况(null、undefined、空数组)
  • 避免深层嵌套:2-3 层可选链足够,更深 → 使用中间变量或 getter
  • 箭头函数使用有意义的名称:items.map(item => ...) 而非 items.map(x => ...)
  • 属性中的复杂表达式必须加引号:class="{isActive ? 'active' : 'inactive'}"
  • 确保 HTML 合规:在文本节点中用 >= 替代 <
  • 关注性能:重复计算 → 在 getter 中缓存;大型数组操作可能影响渲染性能

复杂表达式 —— 注意事项与限制

注意事项与限制(一)

版本要求

  • API 66.0+(.js-meta.xml 中设置),较低版本 → 编译器错误

不被支持的操作

不支持错误示例替代方案
this 关键字{this.property}直接使用 {property}
函数声明{function getName(){}}使用箭头函数
块体箭头{items.map(item => { return ... })}去掉花括号和 return
async/await{await fetchData()}使用 connectedCallback() 的异步方法
赋值/更新运算符{count++}仅在箭头函数内允许(如 onclick)
注意事项与限制(二)

更多限制

  • 不允许:newdeletethrowyieldsuperimport、类表达式、正则字面量、BigInt 字面量、表达式内注释、逗号运算符
  • 替代方案:在 JavaScript 类中定义值/方法,在模板中引用

HTML 语法兼容性与常见错误

HTML 语法兼容性与常见错误

HTML 合规关键规则

  • < 不能在文本节点中使用:{foo < bar} → 解析错误!使用 {bar > foo}
  • 属性中的复杂表达式必须加引号:class="{expr}",简单绑定可不加引号:value={simpleProp}
  • HTML 实体不被解码:避免 {'Price < $100'}

常见错误消息

错误消息原因
Expected quoted attribute value属性中的复杂表达式未加引号
Unexpected token文本节点中使用了 <,或表达式格式错误
'this' is not allowed使用了 this 关键字
'new' operator is not allowed使用了 new 运算符
Function declarations are not allowed使用了 function 而非箭头函数
Arrow functions with block bodies are not allowed箭头函数中使用了 { return }

条件渲染 —— lwc:if/elseif/else

条件渲染

使用 lwc:iflwc:elseiflwc:else 替代旧版的 if:true/false

<template lwc:if={property1}>
  Statement1
</template>
<template lwc:elseif={property2}>
  Statement2
</template>
<template lwc:else>
  Statement3
</template>

交互示例

<lightning-input type="checkbox" label="Show details"
    onchange={handleChange}></lightning-input>

<template lwc:if={areDetailsVisible}>
  <div>These are the details!</div>
</template>

使用规则

  • 可用于 <template><div>、自定义组件和基础组件
  • lwc:elseif/else 必须紧跟 lwc:if 或另一个 lwc:elseif
  • 支持嵌套条件
  • JavaScript 只改变属性值,不直接操作 DOM
最佳实践:为布尔属性设置默认值 false,以实现干净的切换行为。

渲染列表 —— for:each 与 iterator

渲染列表

for:each —— 简单数组遍历

<template for:each={contacts} for:item="contact">
  <li key={contact.Id}>
    {contact.Name}, {contact.Title}
  </li>
</template>

iterator —— 访问 first/last/index

<template iterator:it={contacts}>
  <li key={it.value.Id}>
    <div lwc:if={it.first} class="list-first"></div>
    {it.value.Name}, {it.value.Title}
    <div lwc:if={it.last} class="list-last"></div>
  </li>
</template>

iterator 属性

  • value —— 当前项对象
  • index —— 列表中的位置
  • first —— 仅第一项为 true
  • last —— 仅最后一项为 true
关键:通过 it.value.propertyName 访问属性,不是 it.propertyName

Key 指令 —— 两者都必需

  • 在迭代内的第一个元素上设置 key={uniqueId}
  • 必须是字符串或数字(不能是对象,不能用索引)
  • 框架使用 key 来仅重新渲染变更的项(性能优化)
  • 运行时不在 DOM 中反映

选择指南:需要 first/last 样式或索引 → 使用 iterator;简单遍历 → 使用 for:each。两者都支持复杂模板表达式。

渲染多个模板

渲染多个模板

通过导入多个 HTML 模板并覆盖 render() 方法,可以实现条件性模板选择:

import { LightningElement } from "lwc";
import templateOne from "./templateOne.html";
import templateTwo from "./templateTwo.html";

export default class MiscMultipleTemplates extends LightningElement {
  showTemplateOne = true;

  render() {
    return this.showTemplateOne ? templateOne : templateTwo;
  }

  switchTemplate() {
    this.showTemplateOne = !this.showTemplateOne;
  }
}

额外模板的 CSS 规则

  • CSS 文件名必须与额外模板文件名匹配(如 templateTwo.htmltemplateTwo.css
  • 不能引用主组件或其他模板的 CSS
建议:大多数情况下优先使用 lwc:if|elseif|else 在单模板内处理。只有当你需要完全不同的标记结构时才使用多个模板。

感谢阅读本指南。如需继续学习,请参阅下一章:为组件添加样式。