操作 DOM
DOM 代表 HTML 页面,使你可以用 JavaScript 操作其内容、结构和样式。LWC 支持四种 DOM 模式:Shadow DOM(浏览器原生封装)、Synthetic Shadow(LWC 为旧浏览器提供的 polyfill)、Light DOM(无封装,全局样式)和 Mixed Shadow Mode(Beta,迁移到原生 shadow)。
访问组件拥有的元素 —— 概述
核心规则:不要用 window 或 document 全局属性查询 DOM——Lightning Locker 阻止此操作。不推荐用 JavaScript 操作 DOM(除非使用第三方库 + lwc:dom="manual"),优先使用 LWC HTML 指令写声明式代码。
两种访问方式:querySelector()(传统 CSS 选择器查询)和 Refs(lwc:ref,无需选择器的直接元素引用——推荐方式)。Shadow DOM 用 this.template.querySelector(),Light DOM 用 this.querySelector()。
querySelector() —— Shadow DOM vs Light DOM
// Shadow DOM
this.template.querySelector("div"); // 返回组件 shadow tree 中的第一个
this.template.querySelectorAll("div"); // 返回所有
// Light DOM
this.querySelector("div"); // 搜索 light DOM 子元素
五大注意事项:① 元素顺序不保证——不要依赖 DOM 顺序;② 条件渲染的元素不在结果中;③ 绝不用 ID 选择器——ID 在渲染时转换为全局唯一值,JS 中选择器不匹配;④ Light DOM 的 this.querySelector() 可能搜索到其他组件的元素——使用更具体的选择器;⑤ Locker 下 querySelector 有潜在内存泄漏——迁移到 LWS 或用 refs 替代。参考 lwc-recipes miscDomQuery。
Refs —— 推荐的元素访问方式
Refs 无需选择器即可定位 DOM 元素,只查询指定模板中的元素:
<div lwc:ref="myDiv"></div>
renderedCallback() {
console.log(this.refs.myDiv); // 返回 元素
}
优势:无需选择器、模板结构变化后仍有效、Shadow/Light DOM 行为一致、更高效、TypeScript 类型安全。
规则:必须先定义 lwc:ref 再访问 this.refs;重复 ref 返回最后一个元素;this.refs 是只读对象——修改会运行时错误;不能用在 <template>、light DOM 的 <slot> 或 for:each/iterator:* 循环中。
Refs —— 多模板与条件渲染
this.refs 指向最近渲染的模板。模板变化时 refs 对象也随之变化:
render() { return this.count % 2 === 0 ? a : b; }
// 每次调用 increment() 切换模板,this.refs 反映当前活跃模板的 refs
条件渲染中用相同 ref 名跨分支:
<template lwc:if={darkMode}>
<button lwc:ref="toggleDarkMode">Enable Light Mode</button>
</template>
<template lwc:else>
<button lwc:ref="toggleDarkMode">Enable Dark Mode</button>
</template>
<!-- this.refs.toggleDarkMode → 当前渲染的按钮 -->
访问父元素 —— hostElement(API v62.0+)
// 适用于 Shadow DOM 和 Light DOM
renderedCallback() { console.log(this.hostElement); } // 输出
// Light DOM: this.template.host → undefined
// Shadow DOM: this.hostElement === this.template.host(可互换)
hostElement 提供模式无关的一致性访问方式。用途:获取组件标签名、访问宿主元素属性、与父元素通信、外部库集成。
Shadow DOM —— 封装与 Shadow Tree
Shadow DOM 是封装组件内部 DOM 结构的 Web 标准。#shadow-root 文档片段定义普通 DOM 和 shadow tree 之间的边界——边界以下的元素在 shadow tree 中。
Shadow DOM 如何影响开发:
- CSS:父组件样式不泄漏到子组件——
todoApp.css 中的 p 样式不影响 c-todo-item 中的 p
- 事件:事件冒泡跨越 shadow 边界时属性值被重定向(Event Retargeting)以保护内部细节
- 访问元素:外部代码不能用
document.querySelector() 访问 shadow tree——必须用 this.template.querySelector()
- 访问插槽:通过插槽传入的元素不在 shadow tree 中——用
this.querySelector()(不带 .template)
由于并非所有浏览器都原生支持 Shadow DOM,LWC 在 Lightning Experience 中使用 synthetic shadow polyfill。LWC OSS 或 Lightning Out 中则使用原生 shadow。
Shadow DOM —— 受限制的 DOM API
在 Lightning Locker 组织下,不要使用以下 API 穿透 shadow tree:所有 Document.prototype 查询方法(getElementById、querySelector、querySelectorAll、getElementsByClassName、getElementsByTagName 等)及其 document.body 对应方法。
LWS 方式不同:这些 API 不被阻止,但 LWS 将所有组件的 ShadowRoot mode 强制为 'closed'——阻止外部访问。
MutationObserver 警告:Shadow DOM polyfill 包含 MutationObserver 补丁。使用时必须断开连接否则造成内存泄漏。组件只能观察自己模板内的变更,不能观察其他自定义元素的 shadow tree。转换工具:用 lwc-codemod 自动转换。
Synthetic Shadow —— 概述
Synthetic shadow 是 Salesforce 维护的 polyfill,用于在旧浏览器中模拟原生 shadow DOM 行为。默认用于 Lightning Experience 和 Experience Builder。
当前状态:所有 Lightning Experience 支持的浏览器现已提供原生 shadow 支持——polyfill 不再必要。Salesforce 正通过 Mixed Shadow Mode 迁移到原生 shadow。
<c-hello-input lwc-66unc5l95ad-host>
<lightning-input class="slds-form-element" lwc-66unc5l95ad-host>
</c-hello-input>
LWC 生成混淆字符串(lwc-66unc5l95ad-host)作为属性来限制 CSS 作用域。不要依赖这些属性——它们是内部实现,随时可能变化。API v59.0+ 的令牌格式从组件名可读变为混淆哈希。
Synthetic Shadow —— 样式与级联顺序
Synthetic shadow 在全局文档级别实现样式,但用属性限制作用域——LWC 将带属性选择器的 <style> 标签追加到 <head> 中。
级联顺序(优先级从高到低):
- 组件 CSS 文件中定义的样式(有作用域——仅本组件)
- 通过
@import 导入的 CSS(有作用域——仅本组件)
- 通过
loadStyle 加载的 CSS(全局——影响所有组件)
共享样式表的优势:文档顶层的一个样式表可以为所有组件设置样式——这解释了 SLDS 如何在 Lightning Experience 中工作。多个组件导入相同样式表不影响性能——LWC 自动去重。
Light DOM —— 概述与优势
Light DOM 让组件不在 shadow DOM 中渲染——避开 shadow DOM 的限制。组件内容直接附加到宿主元素,可以像普通文档内容一样访问。
三大优势:
- CSS 主题化和品牌化:全局样式自然工作——轻松应用自定义品牌到组件和子组件
- 第三方工具和测试:标准浏览器查询 API(querySelector 等)无需穿越 shadow root
- 无障碍访问:ID 不被作用域隔离——不同组件中的
<label for="my-input"> 可以引用 <input id="my-input">
参考:lwc-recipes 中以 "lightDom" 开头的组件(如 lightDomQuery)。
Light DOM —— 指南与安全考量
安全警告:Light DOM 暴露组件给 DOM 抓取——处理敏感数据时用 Shadow DOM。DOM 对其他组件和第三方工具开放遍历,你负责保护 light DOM 组件。
最佳实践:Light DOM 和 Shadow DOM 组件可互相嵌套。推荐将深层嵌套的 light DOM 组件封装在顶层单个 Shadow DOM 组件内——在该 shadow root 下自由共享样式。用 CSS 自定义属性(--*)和 ::part 覆盖 shadow DOM 样式——组件所有者控制暴露哪些扩展点。
Light DOM 不可用的功能:限制到特定命名空间(不支持)、分发 light DOM 组件(不支持)、基础组件始终用 Shadow DOM、Aura 不能用 light DOM、slot 生命周期钩子不触发、for:each 中的 slot 不支持。
Light DOM —— Locker 考量与对比
顶层 Light DOM 组件不受 Locker 或 LWS 保护——始终将 light DOM 嵌套在 Shadow DOM 组件内。
维度 Shadow DOM Light DOM
安全 强封装,防未授权访问 弱封装,开放访问
可移植性 高,通过公共 API 控制 易受破坏性变更影响
样式 需要 CSS 自定义属性覆盖 轻松覆盖样式
第三方集成 兼容性有限 简单集成
选择指南:大多数组件用 Shadow DOM(默认)。高度可定制 UI、第三方库集成、需要全局样式时用 Light DOM。第三方分析工具不一定要 light DOM——Shadow DOM 组件暴露正确 API(如在自定义元素上加 click handler)也能满足需求。
启用 Light DOM —— renderMode 与 lwc:render-mode
// JS
static renderMode = "light"; // 默认 'shadow'
// HTML 根模板必须加
<template lwc:render-mode="light">
两者缺一不可。实例化后修改 renderMode 无影响。推荐结构:顶层一个 Shadow DOM + 内部嵌套 Light DOM 组件——在边界内自由共享样式同时保证安全。
Light DOM —— 无障碍与 CSS
无障碍优势:Light DOM 不隔离 ID——<label id="my-label">(组件 A)和 <input aria-labelledby="my-label">(组件 B)可以跨组件引用。
CSS:Shadow DOM 的样式不穿透到子组件,Light DOM 允许根文档的样式目标任意 DOM 节点。Shadow DOM 父组件的样式级联到Light DOM 子组件——但仅限原生 shadow;synthetic shadow 目前不级联。为防止样式泄漏出组件,使用 *.scoped.css 文件。Light DOM 组件渲染顺序影响样式表注入顺序→直接影响 CSS 规则优先级。
Light DOM —— 访问元素
关键变化:this.template 返回 null(无 shadow root)。从 Shadow DOM 迁移到 Light DOM 时:this.template.querySelector() → this.querySelector()。标准 DOM API(getElementById、getElementsByClassName、getElementsByTagName)全部正常工作。
注意事项:this.querySelectorAll() 可能返回其他 Light DOM 组件的元素;IDs 运行时保留(不像 synthetic shadow 那样被修改)——可以用 ID 选择器;仍推荐 this.refs——两种模式下行为一致。
Light DOM —— 事件与插槽
事件不被重定向:event.target 返回实际触发事件的元素(不是包含它的组件)。事件即使 composed=false 也会冒泡——因为没有 shadow root。
插槽在 Light DOM 中被模拟:<slot> 元素不渲染到 DOM——因此 slotchange 事件、::slotted CSS 伪选择器均不支持,slot 生命周期钩子不触发。插槽内容在运行时展平到父元素。未分配到任何 slot 的内容不渲染,其生命周期钩子也不触发。
Light DOM 插槽 —— 样式继承与 slot 属性
样式继承:父组件中定义的样式会级联到插槽内的所有元素(无论 Shadow 还是 Light DOM),但提供插槽的 Shadow DOM 容器组件自身不接收这些样式。
slot 属性行为(API v61.0+):LWC 移除插入到 Light DOM 插槽的元素上的 slot 属性。例外——slot 转发:原生和 synthetic shadow 插槽仍保留 slot 属性以支持转发。API v60.0 及更早版本保留 slot 属性。如果 CSS 选择器或 querySelector 依赖 slot 属性——改用其他选择器或 lwc:ref。检查 Jest 快照是否受此变更影响。
Light DOM —— Slot 转发
Slot 转发 = 将内容从一个插槽传递到另一个插槽。API v61.0+——直接转发,无需包装元素:
<template lwc:render-mode="light">
<c-inner>
<slot name="namedSlot" slot="forwardedSlot"></slot>
</c-inner>
</template>
<!-- namedSlot 接收内容(name)并转发到 forwardedSlot(slot 属性) -->
API v60.0 及更早:需要包装 <div slot="forwardedSlot"> 包裹 <slot>。两种方式结果相同。
Scoped Slots —— 概述与基本用法
Scoped slots 让你在父组件中访问子组件的数据并渲染到插槽内容中。子组件必须用 Light DOM,父组件可以是 Shadow 或 Light DOM:
<!-- 子组件:lwc:slot-bind 绑定数据 -->
<slot lwc:slot-bind={item}></slot>
<!-- 父组件:lwc:slot-data 接收并引用数据 -->
<template lwc:slot-data="item">
<span>{item.id} - {item.name}</span>
</template>
父组件拥有插槽内容(父组件的 scoped 样式适用),子组件控制循环逻辑——为每个 item 创建一个 slot 实例。
Scoped Slots —— 多重绑定与混合
多个命名 scoped slots 可共存,但只能有一个默认 scoped slot。不同 scoped slots 可绑定到相同数据源,但同一 slot 名不能绑定到不同数据源(编译器错误)。不能在同一组件中混合标准 slot 和 scoped slot(同类型/同名)。父组件的 slot 类型必须与子组件匹配——不匹配则内容不渲染。
灵活性:scoped slots 可嵌套——内层可引用外层和内层数据。可同时引用组件绑定(来自父组件)和作用域绑定(来自子组件)。
Light DOM —— Scoped 样式(*.scoped.css)
myCmp/
├── myCmp.html
├── myCmp.css ← 无作用域样式
└── myCmp.scoped.css ← 有作用域样式
/* myCmp.scoped.css */
p { background: silver; color: black; }
/* 编译后:p.c-lightCmp_lightCmp { ... } —— 带作用域类 */
如果同时存在 .css 和 .scoped.css:① .css 先注入(无作用域)② .scoped.css 后注入(有作用域,后声明优先)。但无作用域样式可能泄漏到组件外!Aura 容器中 Light DOM 组件只能使用 *.scoped.css。
Light DOM —— Scoped 区域与 :host 选择器
Scoped 样式用 CSS 类(不是 synthetic shadow 的属性)限制作用域。只应用于组件自己的元素,不应用于 lwc:dom="manual" 中的手动注入内容。
:host 选择器:无作用域样式中指向最近的 shadow root 宿主;scoped 样式中编译器转换 :host 为 light DOM 组件的根元素。不支持 :host-context()。
与 Synthetic Shadow 的差异:Light DOM scoped 用类 → Synthetic shadow 用属性;不支持 @import;不应用于手动注入的 DOM。
Mixed Shadow Mode —— 概述(Beta)
Beta 服务——受 Beta Services Terms 约束。
即使应用了 synthetic shadow polyfill,Mixed shadow mode 也允许组件使用原生 shadow DOM。性能提升:原生 shadow 组件在某些场景下快 50%;未来移除 polyfill 可将 LWC JS 体积减半。
关键限制:Synthetic shadow 组件可以包含原生 shadow 组件 ✅,但原生 shadow 组件不能包含 synthetic shadow 组件 ❌。迁移策略:从叶组件开始,向上逐级测试。
启用与使用 Mixed Shadow Mode
static shadowSupportMode = "native"; // 整个子树用原生 shadow
// 'any' —— 已废弃;'reset' —— 子类退出继承
父组件用 'native' → 所有后代强制原生 shadow。Slots 不算父子关系——插槽内容的 shadow 模式由拥有内容的组件决定,不是提供插槽的组件。继承规则表:native 父 + reset 子 = 仍然 native(父优先);reset 父 + native 子 = 父 synthetic / 子 native。
Mixed Shadow Mode —— 检查与对比
this.template.synthetic // synthetic → true, native → undefined
Native vs Synthetic 关键差异:
- 样式:Synthetic 允许全局样式表;Native 每个组件必须导入自己的样式——跨 shadow 边界用 CSS 自定义属性或传属性值
- 无障碍:Native 中 ID 按组件隔离——将关联元素放在同一组件或用 aria-label 替代 aria-labelledby
- 基础组件:暂不支持 Mixed Shadow Mode——放入原生 shadow 组件内样式可能出错
- 事件:blur/focus 在 synthetic 中不冒泡,native 中正常工作
- Web API:innerHTML 在 synthetic 中暴露 shadow 内容,native 中隐藏(封装保护)
感谢阅读本指南。如需继续学习,请参阅下一章:访问 Salesforce 资源。