Lightning Web 安全

Lightning Web Security 旨在让您的组件更轻松地使用安全编码实践。Lightning Locker 一直是所有 Lightning 组件的默认安全架构。Lightning Web Security (LWS) 正在取代 Lightning Locker for Lightning 组件。适用于 Lightning Web 组件和 Aura 组件的 LWS 已正式发布。

Lightning Web Security 以最新的 TC39 标准为蓝本,可为任何 UI 组件提供创新的可用安全性。这种方法使团队能够安全地使用来自第三方的代码,而不必担心跨站点脚本 (XSS) 攻击等威胁。其结果是,在UI层中控制代码执行的更安全方法,而不会迫使开发人员跳过障碍或放弃所需的功能。

好消息是,此技术不需要重构您的 LWC 代码,因为您的代码已经遵守 Lightning Locker 要求的安全实践。大多数现有的 Lightning Web 组件都可以在这种新架构中工作。

更好的消息是,您可以选择重构代码,让您的组件利用这项技术带来的新自由。您的组件在专用于命名空间的隔离 JavaScript 环境中运行,但它们可以从其他命名空间导入组件和模块,就好像它们都在同一环境中运行一样。命名空间隔离对组件是透明的,因为体系结构会无缝地为你处理它。因此,组件在 Lightning Locker 中运行时观察到的一些限制在 Lightning Web Security 中不是必需的。

我们将这些隔离环境称为 JavaScript 沙箱。每个命名空间都有一个专用的 JavaScript 沙盒。该体系结构管理在不同 JavaScript 沙箱中运行的组件的虚拟交互。

Lightning Web Security 的工作原理

与 Lightning Locker 一样,Lightning Web Security 的目标是防止 Lightning 组件干扰或访问属于平台代码或来自其他命名空间的组件的数据。Lightning Web Security 的体系结构使用不同的方法保护 Lightning Web 组件。

Salesforce 页面可以包含由多个公司创建的组件。由组织的开发人员团队创建的组件与 Salesforce 创建的组件共存。如果您创建应用程序并将其部署在 AppExchange 上,则您的组件将与 Salesforce 创建的组件以及安装应用程序的客户的组件共存。

如果不采取预防措施,组件可以访问全局对象,并从页面上的其他组件获取专用资源或数据。一种预防措施是按命名空间隔离组件,以便恶意组件无法访问其命名空间之外的组件资源。window

通过虚拟化实现隔离

实现隔离的行业标准是虚拟化。代码在虚拟环境中运行,虚拟环境是主机环境的副本。虚拟环境将主机资源的一小部分公开给在虚拟环境中运行的代码。多个虚拟环境在主机环境中同时运行,但在一个虚拟环境中运行的代码无法访问另一个虚拟环境的任何资源。恶意代码只能影响运行它的虚拟环境。

虚拟环境由在主机环境中运行的虚拟化引擎创建和管理。虚拟化引擎对主机环境的资源具有完全访问权限,并严格控制哪些资源可供各个虚拟环境使用。硬件平台和操作系统级别的虚拟化引擎示例包括 VMWare 和 Docker。

浏览器的虚拟化

将虚拟化概念深入到 Web 应用程序级别,主机环境是浏览器。Lightning Web Security 是在主机环境中运行的虚拟化引擎,用于创建和控制虚拟环境。命名空间 JavaScript 沙箱是虚拟环境。Lightning Web Security 授予每个虚拟环境对主机环境中特定资源的有限访问权限。这些资源包括全局对象、网络访问、Cookie 访问、本地存储等。

由于组件在其命名空间自己的虚拟环境中运行,Lightning Web Security 可以通过修改 JavaScript API 级别的代码来防止不安全的行为。修改或扭曲将应用于沙箱中。

虚拟化效果

为了说明通过沙盒进行虚拟化的效果,让我们看一些 cookie。组件可以存储 Cookie。平台代码(包括 LWS)也可以存储 cookie。在浏览器主机环境中,代码不受限制地运行。如果未启用 LWS,组件代码将在该领域中执行,并且可以访问所有 cookie,并且可以不受限制地查看其中的所有内容并对其执行任何操作。您不希望组件将 Cookie 存储在其他代码可以访问的地方。启用 LWS 后,组件代码通过在命名空间沙箱中运行来保护。当组件尝试访问 cookie 时,它们只能看到它们已设置的 cookie 以及他们有权访问的 cookie,因为 LWS 通过将它们包含在沙盒中来控制它们可以访问的内容。

例如,在命名空间的沙盒中,组件只能访问它设置的 cookie。LWS 通过向密钥添加前缀以将其与组件的沙箱相关联,在浏览器主机环境中以不同的方式呈现该 cookie。想要读取此 cookie 的代码在沙盒环境中运行,它可以按预期的方式读取 cookie,其中 .该组件不需要知道 cookie 密钥在浏览器主机环境中已更改,因为该组件无权访问其中的 cookie。如果在沙盒中更改了 cookie 的值,LWS 将负责在主机环境中更新 cookie 的值。LWS 管理 cookie 键前缀以将其关联到命名空间沙箱,并防止来自不同命名空间的组件访问主机环境中的 cookie。cfoo=1foo=1

注意

cookie 前缀是 ,因此在此示例中,cookie 键更改为 。前缀被视为内部实现细节,如有更改,恕不另行通知。代码不能依赖于前缀保持常量。从服务器接收的 cookie 具有前缀,您可以在浏览器的开发人员工具中看到该前缀。例如,Chrome DevTools 中的“应用程序”选项卡会显示 Cookie 详细信息。LSKey-{namespace}$LSKey-c$foo

失真

Lightning Web Security 使用一种称为失真的技术,而不是 Lightning Locker 中使用的安全包装器。此技术是指更改在 JavaScript 沙盒中运行的代码,以防止不安全行为。

失真会动态修改本机 JavaScript API 以:

  • 防止 API 尝试更改 JavaScript 沙盒外部的内容和数据
  • 将运行代码限制在沙盒的边界内
  • 限制或减少 JavaScript 沙箱内对 DOM 和共享全局对象(如 )和数据(如 Cookie)的访问window.location

Lightning Web Security 中的大多数 API 失真都属于以下几类。

  • 内容过滤 – 过滤掉访问其他沙盒中的属性的尝试,例如在 、 、 中,但允许它们在当前沙盒中document.cookielocalStoragesessionStorage
  • 清理 – 去除恶意代码,例如从 和 元素中去除恶意代码innerHTMLouterHTML
  • 属性访问器修改 – 防止读取或写入某些属性的值,例如shadowRoot.mode

失真记录在 LWS 失真查看器中,该工具旨在使人们更容易理解单个失真的效果。

Lightning Web Security 与 Lightning Locker 的比较

Lightning Web Security 会阻止或修改不安全的 API 的行为,就像 Lightning Locker 一样。此外,新架构还支持 Lightning Locker 所不支持的功能。

以下是 Lightning Web Security 支持的其他功能的摘要。跨命名空间组件使用

您的 Lightning Web 组件可以从其他命名空间导入组件或模块,并通过组合或扩展来使用它们。组件隔离在它们自己的命名空间 JavaScript 沙箱中,但它对您是透明的,因为安全体系结构在后台执行虚拟通信。与全局对象的交互

由于每个命名空间都有其自己的独立 JavaScript 沙箱,因此我们可以直接公开 、 和全局对象,而无需使用包装器来保护组件。Lightning Web Security 会修改这些对象的行为,以防止组件访问来自另一个命名空间的组件使用的全局对象。windowdocumentelement访问内容和标识iframe

您的 Lightning Web 组件可以访问元素中的内容,但 Web 浏览器会阻止访问来自不同来源的内容。iframe 标识也跨沙盒边界进行维护,以便检查事件源的标识正常工作。Lightning Locker 会阻止访问 iframe 中的内容。iframepostMessage提高性能

在 JavaScript 沙盒中执行代码的速度比在 Lightning Locker 中更快,因为 Lightning Web Security 不使用安全包装器,这可能会降低性能。更好地支持第三方 JavaScript

库可以使用操作全局对象等技术,因为它们在 JavaScript 沙盒中运行,并且它们对全局变量的更改不会影响其他命名空间中的组件。Lightning Locker 提供了 、 和 对象的安全包装器版本,以防止访问这些全局对象的某些 API。包装器阻止使用某些访问这些受保护 API 的第三方库。windowdocumentelement随着标准 JavaScript 的发展,与标准 JavaScript 的兼容性

Lightning Web Security 以最新的 TC39 标准为蓝本,这些标准随着浏览器平台的发展而发展。

下表比较了 Lightning Locker 功能与 Lightning Web Security。

特征闪电储物柜Lightning Web 安全
JavaScript 严格模式执行执行
DOM 访问遏制组件只能访问它们创建的元素。请参阅 DOM 访问遏制。Lightning Locker 可防止您破坏 Lightning Web 组件之间的 Shadow DOM 封装。您无法修改属性。shadowRoot组件对 DOM 的访问由浏览器通过 Shadow DOM 控制,Shadow DOM 是在 Web 浏览器中实现的 Web 标准。Lightning Web Security 通过要求 to be 强制执行 closed,这提供了额外的封装限制。参见 MDN Web 文档:ShadowRoot.mode。shadowRootmodeclosed
安全包装器Lightning Locker 将 JavaScript 对象包装在更安全的版本中,以防止不安全的行为。与对象的所有交互都是通过对象的安全版本进行的。请参阅安全包装器。Lightning Web Security 不使用包装器。它使用失真来有选择地修改启用非安全行为的 API。
是否允许自定义元素?Lightning Locker块。请参阅带有 Lightning Locker 的第三方 Web 组件。customElementsLightning Web Security 虚拟化 API,以便在组件命名空间中使用自定义元素。customElementRegistry
eval()功能限制eval()仅限于 Lightning Locker 的全球范围。请参阅 eval() 函数受 Lightning Locker 限制eval()不会被阻止,但会通过沙盒中的扭曲来阻止执行不安全的活动。
Blob 对象中允许的 MIME 类型Lightning Locker 允许允许的 MIME 类型列表。允许使用相同的 MIME 类型。但是,在创建 Blob 对象时,必须指定 MIME 类型。
传递给子组件的数组过滤组件之间传递的阵列会导致 Lightning Locker 中出现性能问题。请参阅传递给子组件时代理的数组。Lightning Web Security 解决了这个问题,因为它不过滤 DOM 请求,而是依赖于影子 DOM。
返回到以前版本的 LockerLightning Locker 支持将组织设置为使用以前版本的 Locker 规则。请参阅选择组织的 Locker API 版本。Locker API 版本设置对 Lightning Web Security 没有影响。使用 Lightning Web Security 的设置不与任何 API 版本关联。启用 Lightning Web Security 后,组织中的所有 Lightning 组件都会受到影响,直到您禁用它。

Lightning Web Security 支持哪些组件

Lightning Web Security 已正式发布,可用于 Lightning Web 组件和 Aura 组件。LWS 会影响在您的组织中创建或通过软件包安装的自定义 Lightning 组件。

如果您不确定您的组织是否具有自定义 Lightning 组件,请使用“设置”列出组织中的组件。

在“设置”中,键入“快速查找”,然后选择“Lightning 组件”。Lightning Components

Lightning 组件页面列出了您组织的组件,指示组件的类型以及组件是否来自托管软件包。

  • “类型”列指定 Aura 或 LWC。按类型对列表进行排序以分组并快速查看任何 LWC。
  • 托管包图标图标表示组件来自托管包。此类组件只能由 AppExchange 应用程序的所有者修改。如果未显示图标,则表示组件不受管理,必要时可以修改代码。

提示

如果 Lightning 组件列表为空,则 Lightning Web Security 不会影响您的组织。

如果列出了自定义 Lightning 组件,请继续阅读。

何时启用 Lightning Web安全

使用此表可根据组织中存在的组件类型确定是否在生产组织中启用 Lightning Web Security。

默认情况下,新组织启用了 Lightning Web Security。如果您计划使用预先存在或打包的 Lightning Web 组件填充新组织,请务必先在启用了 LWS 的沙盒环境中进行测试。有关更多信息,请参阅使用 Lightning Web Security 试用 LWC 的工作流程。

注意

如果您的组织中使用了 Experience Cloud,请在启用 LWS 之前参阅 Experience Builder 站点和 LWS。

要确定组织中存在的组件类型,请参阅 Lightning Web Security 支持哪些组件。

组织中的组件是否可以在生产环境中启用 LWS?测试方法
无组件是的不需要测试,因为如果组织中没有组件,LWS 将不起作用。但是,始终建议在沙盒组织中测试新版本。
仅限 LWC是的,在沙盒中测试后我们强烈建议您首先在组织的沙盒暂存环境中试用 Lightning Web Security。请参阅使用 Lightning Web Security 试用 LWC 的工作流程。如果测试显示您的组件在沙盒组织中运行良好,请在生产组织中将其打开。如果出现问题,您可以通过取消选择 LWS 设置轻松恢复使用 Lightning Locker for Lightning 组件。
Aura 和 LWC是的,在沙盒中测试后我们强烈建议您首先在组织的沙盒暂存环境中试用 Lightning Web Security。请参阅使用 Lightning Web Security 试用 LWC 的工作流程。如果测试显示您的组件在沙盒组织中运行良好,请在生产组织中将其打开。如果出现问题,您可以通过取消选择 LWS 设置轻松恢复使用 Lightning Locker for Lightning 组件。如果您的组织在 Spring ’23 之前启用了 LWS 设置,并且您的组织包含自定义 Aura 组件,则 LWS 设置将保持启用状态,并继续仅影响您的 Lightning Web 组件。您的组织已被排除在 LWS for Aura 测试版之外,因此它不会影响您在 23 年春季的 Aura 组件。在 Summer ’23 中,如果您的组织之前被排除在 LWS for Aura 之外,LWS 将保持启用状态,并继续仅影响 Lightning Web 组件。我们逐步推出 LWS 的目标是从预计不会受 LWS 影响的组织开始,并最终为所有组织启用 LWS。将某些组织排除在 LWS for Aura 之外仅适用于生产组织。LWS for Aura 在 Summer ’23 沙盒中为被排除在外的组织启用,以便您可以开始测试 Aura 组件。
仅光环是的,在沙盒中测试后我们强烈建议您首先在组织的沙盒暂存环境中试用 Lightning Web Security。请参阅使用 Lightning Web Security 试用 LWC 的工作流程。同样的工作流程也适用于 Aura 组件。如果测试显示您的组件在沙盒组织中运行良好,请在生产组织中将其打开。如果出现问题,您可以通过取消选择 LWS 设置轻松恢复使用 Lightning Locker for Lightning 组件。

在组织中启用 Lightning Web安全

首先在沙盒环境中测试 Lightning Web Security,然后在生产环境中测试。

默认情况下,新组织启用了适用于 Lightning Web 组件的 Lightning Web Security 和 Aura 组件。如果您计划使用预先存在或打包的 Lightning Web 组件填充新组织,请务必先在启用了 LWS 的沙盒环境中进行测试。有关更多信息,请参阅使用 Lightning Web Security 试用 LWC 的工作流程。

有关临时组织的信息,请参阅在临时组织中启用和禁用 LWS。

重要

在启用之前,请参阅何时启用 Lightning Web Security,以确定是否在生产组织中启用它。

要在组织中使用 Lightning Web Security:

  1. 在“设置”的“快速查找”框中,输入 ,然后选择“会话设置”。Session
  2. 在“会话设置”页面上,选择将 Lightning Web Security 用于 Lightning Web 组件和 Aura 组件并保存。
  3. 启用或禁用 Lightning Web Security 后清除浏览器缓存,以确保在浏览器中加载正确的文件。如果您怀疑正确的安全体系结构未生效,请参阅延迟启用或禁用 LWS。

要报告问题、提供反馈或询问有关 Lightning Web Security 的问题,请转到 Trailhead 上的 Lightning 组件开发组。

重要

LWS 依靠 Stricter CSP 来全面实施其安全措施。我们强烈建议您在启用 LWS 时保持启用更严格的内容安全策略设置处于打开状态。

在临时组织中启用和禁用 LWS

默认情况下,新组织启用了 LWS for Lightning Web 组件和 Aura 组件。新的临时组织也启用了 LWS。

您可以使用临时组织定义文件在临时组织中启用和禁用 LWS。布尔值启用和禁用 LWS,而不是 Lightning Locker。例如,将这些行添加到定义文件中,以在临时组织中禁用 LWS。lockerServiceNext

”settings": {
     "securitySettings": {
         "sessionSettings": {
            "lockerServiceNext": false
           }
      }
}

禁用 LWS 会返回您的临时组织,以便将 Lightning Locker 用于 Lightning Web 组件和 Aura 组件。

Lightning Web Security 的工具支持

ESLint 规则执行静态代码分析,以帮助您查找代码中触发 Lightning Web Security 失真的区域。当 JavaScript 代码在沙盒中运行时,Lightning Web Security 控制台会对 JavaScript 代码执行动态检查,以帮助您确定代码问题是否与 LWS 相关。LWS 失真查看器描述了失真对 API 的影响。

结合使用这些工具,帮助创建与 Lightning Web Security 一起运行的安全代码。

安装 Lightning Web Security 的 ESLint 规则

我们创建了 ESLint 规则,以帮助您开发适用于 Lightning Web Security 的代码。您可以将它们安装在开发计算机上,并在源代码上运行它们。

ESLint 规则以 npm 包的形式提供,您可以将其安装在本地系统上,并在 IDE 或命令行中使用。

ESLint 规则记录在此 GitHub 存储库中。

注意

新架构是在品牌更改为 Lightning Web Security 之前以 Locker 名称开发的。您可以在代码和 npm 包名称中看到 used。但是,lint 规则支持 Lightning Web Security,而不是当前的 Lightning Locker。locker

添加对 Lightning Web Security 的 ESLint 支持

  1. 在开发系统上,打开项目目录中的终端窗口。
  2. 安装节点项目依赖项。$ npm install
  3. 从 https://www.npmjs.com/package/@locker/eslint-config-locker 安装 ESLint 规则和配置。$ npm install --save-dev eslint @locker/eslint-config-locker
  4. 添加到项目中的属性。默认情况下,此文件位于目录中。@locker/eslint-config-lockerextends.eslintrcforce-app/main/default/lwc此行配置 ESLint 规则,其中仅包含错误。base{ "extends": [ "eslint:recommended", "@salesforce/eslint-config-lwc/recommended", "@locker/eslint-config-locker" ] }
  5. 如果除了错误之外还希望收到警告通知,请使用 .ESLint 警告有助于调试特定问题,但会生成日常使用不需要的额外信息。recommended.eslintrc{ "extends": [ "eslint:recommended", "@salesforce/eslint-config-lwc/recommended", "@locker/eslint-config-locker/recommended" ] }
  6. 如果您正在开发用作静态资源的 Aura 组件或代码,请添加配置。uncompiled{ "extends": [ "eslint:recommended", "@salesforce/eslint-config-lwc/recommended", "@locker/eslint-config-locker/recommended", "@locker/eslint-config-locker/uncompiled" ] }

注意

当您的组织升级到新的 Salesforce 版本时,请运行,以便您的 ESLint 规则与该版本中的 LWS 失真相匹配。有关版本中添加的失真列表,请参阅 Salesforce 发行说明。npm update

在 VS Code 中使用 ESLint 规则

Salesforce 建议将 Visual Studio Code 与 Salesforce Extensions for Visual Studio Code 结合使用来开发 Lightning Web 组件。针对 Lightning Web Security 的错误的 ESLint 规则显示在 VS Code 中,您的代码违反了这些规则。这些规则映射到影响代码的扭曲。规则冲突的弹出窗口包括指向规则文档的链接。

在这里,您可以看到 lint 规则冲突的弹出窗口,以及指向相关规则文档的链接。

此弹出窗口显示 lint 警告,而不是错误。

针对项目运行基本规则

  1. 安装 ESLint 规则和配置,如添加 ESLint 对 Lightning Web 安全的支持部分所述。
  2. 运行常用命令以对代码进行 lint 检查。如果您使用的是 Salesforce DX 项目,请运行适合您环境的命令。npmyarn$ npm run lint$ yarn lint如果您使用的不是 Salesforce DX 项目,则可以在 LWC 组件上运行该命令。例如,如果您的组件包含在文件夹中:eslintlwc$ eslint **/lwc/**无论哪种情况,输出都类似于此示例。10:16 error Document#fullscreen is prohibited in Lightning Locker @locker/locker/distorted-document-blocked-properties ✖ 1 problem (1 error, 0 warnings) error Command failed with exit code 1.

在 Lightning Web Security 控制台中评估 JavaScript

在 Lightning Web Security Console 中评估您的代码,因为它在启用和禁用 LWS 的情况下运行。此工具可以帮助您确定 JavaScript 代码的问题是否与其与 LWS 的交互有关。

Lightning Web Security 控制台可从组件库导航栏访问。

以下是该工具的直接链接:https://developer.salesforce.com/docs/component-library/tools/lws-console

Lightning Web Security 控制台模拟在 Salesforce 组织中运行的 LWS 的行为。控制台提供了一个使用 LWS 执行代码的环境,因此您可以在开发组件的 JavaScript 时进行快速检查。但是,控制台无法创建组织的完整执行环境,因此在生产中使用组件之前,您仍必须在沙盒组织中执行功能测试。

注意

控制台不会评估包含 or 语句或 LWC 装饰器(如 和 )的 JavaScript 模块。该工具仅用于评估纯 JavaScript。importexport@api@wire

以下是 Lightning Web Security 控制台工具用户界面的概述。

代码控制台 (1)

在此处粘贴或键入 JavaScript 代码以对其进行评估。动作 (2)

  • 启用或禁用 LWS。
  • 单击“评估”以运行代码控制台中显示的代码。
  • 单击清除”以清除结果,但不清除代码。
  • 选择 LWC 或 Aura,具体取决于使用 JavaScript 的组件类型。LWS 对 Aura 组件和 Lightning Web 组件应用类似的失真。但是,组件类型的运行时环境不同,这可能会导致 LWS 阻止 LWC 中允许的 Aura 组件中的某些属性。

评价结果 (3)

显示 LWS 评估的结果,包括失真返回的错误消息。棉绒结果 (4)

显示在评估期间触发的 LWS ESLint 规则。

当您单击“评估”时,Lightning Web Security 控制台将设置执行环境,然后在浏览器的 JavaScript 沙盒环境中运行代码。运行代码时出现的任何错误都会显示在右上方的面板中。否则,该工具将报告“成功”。

提示

如果失真效果是使代码能够在沙盒中安全运行,则该工具会报告“成功”,并且不指示应用了失真。如果失真阻止代码执行,该工具将报告失真生成的错误。

在 LWS 失真查看器中查找失真细节

LWS 将失真应用于在 JavaScript 沙盒中运行的代码,以帮助防止不安全的行为。LWS Distortion Viewer 通过解释受影响 API 的漏洞以及缓解漏洞的扭曲行为来描述失真。此工具可以帮助您了解如何修改 JavaScript 代码,以避免影响组件的失真。

LWS 失真查看器显示被 Lightning Web Security 阻止或修改的 JavaScript API。如果此处未列出 API,则 LWS 允许 API 的本机行为。有关失真的更多信息,请参阅 Lightning Web Security 的工作原理。

LWS 失真查看器可从组件库导航栏获得。

以下是该工具的直接链接:https://developer.salesforce.com/docs/component-library/tools/lws-distortion-viewer。

LWS Distortion Viewer 按对象名称的字母顺序列出 JavaScript API 的失真,以及指示对象失真的 API 数量的数字。单击左侧面板中的对象,然后单击右侧面板中的 API 以查看有关 API 失真的信息。

提示

有关在应用 LWS 失真后解决代码的某些问题的信息,请参阅在 Lightning Web Security 中进行故障排除。

使用 Lightning Web Security 试用组件的工作流程

以下是建议的工作流程,供开发人员评估使用 Lightning Web Security 运行的 Lightning Web 组件。

注意

当您尝试在 Lightning Web Security 中运行的组件时,我们建议您仅进行传递 ESLint 规则所需的代码更改。如果您的组件在 LWS 和 Lightning Locker 中都能正常工作,则在出现意外问题时,您可以更轻松地在它们之间切换。如果重构组件以利用仅限 LWS 的功能,请将它们限制在开发组织中,并且不要将它们推送到生产环境。请参见使用仅限 LWS 的功能。

  1. 下载并配置 ESLint 规则,如安装适用于 Lightning Web Security 的 ESLint 规则中所述。
  2. 针对组件运行基本规则集,如针对项目运行基本规则中所述。
  3. 修复基本 ESLint 规则显示的任何错误。
  4. 修复错误时,请尝试在 Lightning Web Security 控制台中修改后的代码,看看它是否可以与 LWS 一起运行。
  5. 如果您的代码受到失真的影响,请查看 LWS 失真查看器中的失真描述,以帮助理解这些问题。
  6. 修复 linting 错误后,请为沙盒组织中的 Lightning Web 组件和 Aura 组件启用使用 Lightning Web Security,如在组织中启用 Lightning Web Security 中所述。
  7. 将组件部署到沙盒组织。
  8. 手动测试组件,也可以通过运行自动测试来测试组件。
  9. 如果发现任何异常行为或测试结果,请尝试在组件代码上运行规则集以调试任何其他问题。如果您在 Lightning Web Security 控制台中运行代码,则可以跳过此步骤。该工具运行两组 ESLint 规则。recommended
  10. 如果您的组件在启用 Lightning Web Security 的情况下无法正常运行,或者您无法修复 ESLint 错误,请转到 Trailhead 上的 Lightning 组件开发组进行讨论。
  11. 如果您修改了组件以通过 ESLint 规则,并且它们现在表现良好并在沙盒中通过了测试,请关闭对沙盒组织中的 Lightning Web 组件和 Aura 组件使用 Lightning Web Security
  12. 在开始测试修改后的组件之前,清除浏览器缓存以确保在浏览器中为 Lightning Locker 加载正确的文件。如果清除缓存后的结果不是预期的,请尝试延迟启用或禁用 LWS 中的其他与缓存相关的解决方案。
  13. 在沙盒组织中测试修改后的组件,以确保在更改它们以传递 LWS 的 ESLint 规则后,它们在 Lightning Locker 下仍能正常工作。如果您的组件在两种安全体系结构中都工作,则在以后出现意外问题时,您可以更轻松地在它们之间切换。
  14. 当您确信这些组件可以与沙盒组织中的 Lightning Locker 和 Lightning Web Security 配合使用时,请将更改部署到生产环境中。
  15. 请您的 Salesforce 管理员在生产环境中为 Lightning Web 组件和 Aura 组件启用使用 Lightning Web Security

使用仅限 LWS 的功能

在全面采用 Lightning Web Security 之前,请记住,启用后,组织中的所有自定义 Lightning 组件都会受到影响。所有自定义组件都在新体系结构中运行。

Lightning Web Security 无法在单个组件上禁用,并且不与 API 版本关联。

假设您重构了一个组件以利用 Lightning Web Security 与 Lightning Locker 的比较中描述的仅限 LWS 的功能,例如从另一个命名空间导入模块。该更改与 Lightning Locker 不兼容,因此该组件现在依赖于 Lightning Web Security。

如果后来发现另一个组件在运行 LWS 时出现问题,则无法通过返回 Lightning Locker 来解决问题,因为重构的组件使用需要 LWS 的功能。

警告

在生产组织中启用所有 Lightning 组件之前,请确保所有 Lightning 组件都已准备好在 LWS 中运行。

启用 LWS 进行调试

要在启用了 LWS 的组织中调试 Lightning 组件代码,请使用 Chrome DevTools。

对于 Lightning Web 组件,请使用调试 Lightning Web 组件中描述的技术。

有关启用 LWS 时调试差异的信息,请参阅调试启用了 LWS 的组织中的组件。

有关启用 LWS 时调试中的一些已知限制的信息,请参阅启用 LWS 时进行调试的限制。

启用 LWS 时进行调试的限制

启用 LWS 时的调试体验会受到浏览器限制的影响。只有当浏览器供应商实施修复程序并采用新标准时,才能解决这些限制。Salesforce正在与供应商合作。

  • 启用调试模式时,无法重现在生产模式下遇到的某些问题。
  • 启用 LWS 时,某些 Aura 错误不会显示。
  • 控制台消息中引用的源代码行号和源代码文件不正确。
  • 最新版本的 Firefox 有时在启用 LWS 时不会遇到断点。使用推荐的 Chrome DevTools。
  • 某些浏览器扩展和插件可能会干扰调试。如果遇到问题,请尝试禁用它们。

Lightning Web Security 中的故障排除

开发人员在开发和测试要在 LWS 中运行的组件时可能会遇到一些常见问题。

查看您的问题是否已在此处得到解决。如果您尝试这些故障排除技术未成功,则可以在临时组织中禁用 Lightning Web Security 并继续进行故障排除。我们建议您尽快重新启用 Lightning Web Security。

确定是否启用了 LWS

有时,可能很难快速确定正在运行的代码上是否使用了 LWS。以下是您可以尝试的一些技术。

添加代码以触发 LWS 失真

在 Aura 组件的 JavaScript 中,添加一些触发 LWS 失真的代码。有关 LWS 失真的描述,请参见 LWS 失真查看器。

将 Aura 组件部署到您的组织进行测试。

最简单的方法是检查失真返回的值。例如,如果此检查返回 ,则 LWS 正在运行,并且组织不会从 LWS for Aura 中排除。true

navigator.serviceWorker === undefined;

您可以在浏览器开发人员控制台中看到错误。

error: The navigator.serviceWorker getter is distorted by Lightning Web Security.

在浏览器控制台中检测 LWS

此方法使用现有组件代码,无需修改。您可以在生产环境中运行的代码上使用它。

在 Chrome DevTools 中,查看“”选项卡的“页面”面板,并在文件夹 your-org-domain 或特定命名空间(如果不使用命名空间)中找到组件 JavaScript 文件。/components/cc

提示

不要在文件夹中查找 JavaScript。该文件夹使用源映射从视图中删除额外的运行时代码。lwslws

如果您在运行时组件 JS 代码中找到这些字符串,则组织正在使用 LWS 运行。LWC 组件

搜索字符串sandbox.evaluateInSandbox

Aura 组件

搜索字符串$A.lockerService.createForDefNext

LWS 的延迟启用或禁用

如果您的组件使用的功能是 Lightning Web Security 允许但被 Lightning Locker 阻止的,则在启用 LWS 后仍可阻止该功能。如果您禁用了 LWS,但看到允许的行为,但您希望它被 Lightning Locker 阻止,则反之亦然。原因可能是您的浏览器由于缓存而仍在使用以前的环境。

若要检查代码是否使用 LWS 运行的证据,请参阅确定是否启用了 LWS。

可以有多种类型的缓存生效。对于最简单的解决方案,请尝试刷新浏览器窗口并清除浏览器缓存。

但是,有时这些措施不足以获得正确的行为,尤其是在开发和测试时频繁打开和关闭 LWS 时。

如果问题仍然存在,请尝试此处所述的措施。

在浏览器中禁用缓存

受支持的浏览器使您能够在使用浏览器开发人员工具时禁用缓存。在开发人员工具窗口的“网络”面板中,选中“禁用缓存”复选框。然后,当开发人员工具窗口打开时,浏览器不会从缓存加载。

在 Salesforce 中禁用安全缓存

在沙盒或 Developer Edition 组织中进行开发时,禁用安全和持久的浏览器缓存设置,以查看任何代码更改的效果,而无需清空缓存。

缓存设置通过避免与服务器的额外往返来提高页面重新加载性能。

警告

禁用安全和持久的浏览器缓存会对 Lightning Experience 的性能产生重大负面影响。始终在生产组织中启用该设置。

  1. 在“设置”中,输入“快速查找”框,然后选择“会话设置”。Session
  2. 取消选中“启用安全且持久的浏览器缓存以提高性能”复选框。
  3. 点击保存

属性评估与 LWS 相同undefined

LWS 可以使值的计算结果为 。undefined

下面是一些条件,这些条件会导致属性像在使用 LWS 运行的组件中一样进行计算,以及如何处理它。undefined

未定义的值在全局范围内

如果你的代码或你使用的第三方库将值存储在对象中,则它正在访问全局范围。Lightning Web Security 强制执行 JavaScript 严格模式,以防止创建全局变量。如果您尝试创建全局变量,浏览器会引发错误。window

LWS 失真仅允许在组件的命名空间沙箱中更改虚拟对象。如果组件尝试访问在另一个命名空间中创建的全局范围属性,则 LWS 返回的值为 。windowundefined

LWS 沙盒使您可以访问全局属性,但实际上您正在访问对象的虚拟副本。在命名空间沙箱中,LWS 允许您访问全局属性,但在全局范围内存储任何内容都被视为不好的做法。LWS 允许这样做,但如果将具有突变的对象传递给在命名空间沙盒外部运行的代码,则无法访问更改的属性。window

相反,将其他命名空间中的函数或对象可以访问的任何共享数据作为函数或构造函数参数传递。

注意

启用 LWS 时,第三方库不应显式设置严格模式。请参阅 LWS 的第三方库注意事项。

undefined 值由函数参数传递

如果您发现某些作为参数传递的值是在启用 LWS 之后,则问题可能是由于对象突变或对组件不拥有的数据进行了修改。undefined

数据所有权是在命名空间级别定义的,这意味着在命名空间中定义的代码可以自由修改自己的数据。但是,如果代码从另一个命名空间接收数据并修改该数据,则修改并不总是反映在命名空间之外。

提示

如果被突变的对象是纯 JavaScript 对象、数组或类型化数组,则 LWS 允许对原始对象进行突变,并且更改会反映在命名空间之外。我们特别建议使用这些对象而不是地图。

对于全局对象和从自定义类构造的对象,如果对象不归命名空间所有,则 LWS 不允许突变。在命名空间沙箱外部添加的属性的计算结果为 。windowundefined

在此示例中,在命名空间 A 中定义,在命名空间 B 中定义。redFunctionblueFunction

//module in namespace A

function redFunction(arg) {
  arg.foo = "bar"; // mutation on the arg object
  console.log(arg.foo); // "bar"
}

//module in namespace B

function blueFunction() {
  var someObject = {};
  redFunction(someObject); // blueFunction expects foo property added by redFunction
  console.log(someObject.foo); // undefined
}

当 uses 时,它不会接收在命名空间 A 中完成的属性的突变。命名空间可以通过相互传递数据进行通信,但突变不会持续到命名空间边界之外。blueFunctionredFunctionfoo

如果代码更改了从其他命名空间接收的对象,请重构代码。

有关如何重构代码的示例,请参阅更改对象的替代方法。

注意

最好在设计函数时考虑不可变性,以使它们具有幂等性。有关不可变性编码范式的更多信息,请参阅此 Redux 指南。

变异对象的替代方案

一个常见的有问题的编码做法是改变命名空间不拥有的对象。更改对象时,可以添加或删除对象的属性或更改属性的值。更改对象的“形状”。当您在 LWS 中运行的组件中更改对象时,该更改不会传播到命名空间沙盒之外,这会导致错误。

突变可能导致值为 。有关详细信息,请参阅在 LWS 中将属性评估为未定义。undefined

本节讨论这些属性突变的替代方案。

  • 函数参数
  • 类实例属性
  • 事件中接收的对象

函数参数的突变替代方案

在此示例中,函数通过添加值为 的属性来改变参数。然后调用 ,它期望在 上定义。foo()objfoofoo()otherFunctionprop = fooobj

function foo(obj) {
  obj.prop = "foo"; // 'prop' did not exist on obj
  otherFunction(obj);
}

这种突变是有问题的,因为可以从不同的呼叫者接收。然后可以发送给不同的消费者,他们希望有不同的形状。突变会带来不确定性和不一致性。更改可能会在不知不觉中破坏其他代码,尤其是当代码库庞大而复杂时。foo()objfoo()objobjobj

不要向对象添加属性,而是使用对象的相关属性创建浅层克隆。在示例代码中,重构以创建对象的克隆并添加到克隆中。foo()prop

下面是一个单行更改,它使用克隆对象删除突变。

function foo(obj) {
  const newObj = { ...obj, prop: "foo" };
  otherFunction(obj);
}

类实例属性的突变替代项

对于类,我们建议公开可突变属性的 getter 和 setter。避免直接更改用于计算的类属性。

JavaScript 现在支持私有类字段。在任何面向对象的编程语言中,最好的做法是将所有内部状态属性定义为私有属性,并且仅公开外部代码可以变异的属性的 getter 和 setter。

事件中接收的对象的突变替代项

对于事件,没有简单的解决方法,因为事件是通过 DOM 传播的。

可能的解决方案是:

  • 克隆事件数据,对其进行更改,然后使用新数据重新调度相同的事件。
  • 克隆事件数据,对其进行更改,然后调度具有不同名称的新事件。

具有异步函数的静态资源

当您在 Lightning Web 组件中使用 Lightning Web 组件访问静态资源并启用 Lightning Web Security (LWS) 时,这些静态资源中的某些 API 可能会在各种浏览器之间表现出不一致的行为。lightning/platformResourceLoader

启用 LWS 后,从静态资源使用的 JavaScript 函数在基于 Chromium 的浏览器中工作,但目前在 Firefox 中不起作用。该问题是Firefox中的一个错误。async

您可以在 Firefox 中通过预编译静态资源中的代码来解决此问题,以便将函数替换为等效代码。async

如果你在静态资源中的函数有问题,建议使用这个 Babel 插件: https://babeljs.io/docs/en/babel-plugin-transform-async-to-generatorasync

或者,您可以重构以使用其他方法,例如 JavaScript promise。

标准 JavaScript 对象的自定义属性

将自定义属性直接添加到标准内置 JavaScript 对象可能会导致序列化对象时出现问题。当对象跨命名空间传递或传递到全局范围时,出于安全原因,LWS 不会反映这种突变。

如果必须向 DOM 元素添加自定义数据,请使用 DOM 元素的 dataset 属性,而不是向元素对象添加自定义属性。

在组件之间传递的对象是代理的

在不同命名空间中的组件之间传递或传递到全局 DOM 中的对象可能存在兼容性问题。为了保持命名空间隔离并减少不兼容性,LWS 在对象周围使用代理包装器。当 LWS 尝试解开这些值时,有时会失败。例如,您可以在浏览器控制台中看到这些值为 null 或代理。

同样,由组件触发的自定义事件(其值用于浏览器扩展)会跨越组件沙箱边界,因此 LWS 会代理这些事件。由于代理包装器,浏览器扩展可以接收 null 值。detaildetail

为避免与代理包装器相关的问题,请将值作为 JSON 字符串传递,并在以后对其进行分析。

在此示例中,命名空间中的组件将事件数据序列化为 JSON 字符串。命名空间中的组件在处理之前解析数据。foobar

// namespace foo
window.addEventListener(
  "message",
  (event) => {
    let dataObj = JSON.serialize(event.data);
  },
  false,
);

// namespace bar
let dataStr = JSON.parse(data);
window.postMessage(dataStr, targetOrigin);

Map 对象限制

在序列化对象时,使用启用了 LWS 的 Map 对象可能会遇到问题。在大多数情况下,您可以使用纯 JavaScript 对象,而不是使用 Map 实例。

如果必须使用映射,请使用该方法插入键值对,而不是添加自定义属性。如果必须序列化 Map 中传递的数据,请将自定义替换函数传递给 。set()JSON.stringify(...)

命令式和有线 Apex 调用都不支持在 Lightning Web 组件中使用地图。

转换为提取调用的脚本标记

LWS 将标签转换为调用。LWS 下载脚本以在沙盒中对其进行评估。<script>fetch

要使调用成功,请执行以下操作:fetch

  • 在运行组件的 Salesforce 站点上配置 connected-src CSP 指令,以允许访问脚本资源服务器。请参阅管理受信任的 URL。
  • 使用跨域资源共享 (CORS) 配置资源服务器,以将 Salesforce 站点的源添加到其允许的源列表中。然后,该组件可以访问服务器资源。

本地存储限制

LWS 为每个沙盒创建合成存储。合成存储可防止一个沙箱中的代码检索属于另一个命名空间的数据或在 LWS 沙箱外部的全局范围内设置的数据。

localStorage并按键分区。启用 LWS 后,无法访问在命名空间外部设置的键值,因为 LWS 会创建命名空间唯一的键名称。sessionStorage

若要与在不同命名空间中运行的代码共享存储在本地存储中的数据,请创建一个函数并将其从一个命名空间导出,以允许另一个命名空间导入和调用该函数。

注意

Lightning Locker 和 LWS 使用不同的机制来保护本地存储。启用 LWS 时,无法访问启用 Locker 时保存到本地存储的数据,反之亦然。如果在将数据保存到本地存储中后 LWS 设置发生更改,则即使由同一命名空间中的组件设置,也无法访问数据。启用或禁用 LWS 后重写 和 数据。localStoragesessionStorage

LWS 不允许的 Aura 端点

Lightning Web Security 不允许访问包含 和 的 URL 端点,因为它们是 Lightning 组件框架的一部分。/aura/webruntime

这些 JavaScript API 会受到影响。

  • fetch
  • Navigator.sendBeacon
  • XMLHttpRequest.open
  • HTMLObjectElement.prototype.data setter

被 LWS 阻止的 Salesforce 全局变量

LWS 阻止访问其他 Salesforce 功能可用的某些全局 JavaScript 对象。

Lightning Web 组件无权访问

  • $A
  • Aura
  • Sfdc
  • sforce

LWS 限制 Lightning Web 组件对这些对象的访问,因为它们已被弃用或不受 LWC 支持。

LWS 的第三方库注意事项

如果第三方脚本具有这些行为,则在启用 LWS 的情况下运行时可能会遇到问题。

在全局窗口对象上设置的第三方库

某些第三方脚本希望将自己添加到全局对象中。LWS 将它们限制在沙箱中,在沙箱中它们访问全局对象的虚拟副本。windowwindow

要确保代码在 LWS 沙箱的全局范围内运行,而不是尝试访问实际的全局范围,请修改要分配给 的脚本代码。selfglobal

(function (global) {
  global = global || self;
  global.myFunction = function () {};
})(this);

在此代码中,作为 传递给闭包函数。使用 LWS 时,并且未定义,因此此代码可确保其评估结果为 sandbox 。thisglobalthisglobalglobalglobal

第三方库设置严格模式

如果第三方库脚本显式设置了 ,则当您的组件在 LWS 中运行时使用该脚本时,浏览器可能会引发错误。"use strict"

例如,如果脚本尝试在全局对象中设置某些内容,则会发生错误。LWS 隐式实施最严格的模式限制,但允许写入其 JavaScript 沙箱中的虚拟全局对象。在代码中显式设置严格模式会干扰 LWS。

解决方法是在第三方脚本中删除。"use strict"

或者,修改要分配给的第三方代码,如上所示。selfglobal

Experience Builder 站点和 LWS

如果在包含 Experience Builder Aura 站点的组织中启用了 LWS,则 LWS 会保护 Aura 站点中的 Lightning Web 组件和 Aura 组件。

LWS 还可以保护 LWR 站点中的 Lightning Web 组件和 Aura 组件。但是,LWR 站点有自己的 LWS 实例,该实例不受组织的 LWS 设置的影响。

有关更多信息,请参阅《Experience Cloud 开发人员指南》中的开发安全站点:CSP、Lightning Locker 和 LWS。

LWS 对 Aura 组件的局限性

以下是使用 LWS 运行的 Aura 组件的一些已知限制。

该属性必须与这些访问器一起使用。top

  • window.top
  • globalThis.top
  • self.top

该属性必须与这些访问器一起使用。location

  • window.location
  • document.location
  • globalThis.location
  • self.location
  • document.defaultView.location

这些模式会产生运行时错误:

  • async/await
  • 动态导入

Aura 组件不能使用这些技术,因为它们是 ES6 中引入的功能,而 Aura 不支持这些功能。此语法的使用通常由验证规则在开发时标记。

如果 Aura 组件使用使用 / 或动态导入的静态资源,则在组件遇到错误的运行时才会检测到它。asyncawait

Aura 分量中全局变量$A局限性

当 Aura 组件与 LWS 一起运行时,全局变量对函数或构造函数不可用。$Aeval()Function

没有与 一起使用的解决方法。$Aeval()

提示

在 LWC 中使用 with 刷新视图不适用于 LWS。请改用 RefreshView API。eval()force:refreshView

在函数中使用$A的解决方法

在 Aura 组件中,当启用 LWS 时,您可以使用构造函数的依赖注入作为解决方法。Function

定义函数以接受额外的参数,您可以在全局范围内调用并存在于全局范围内的任何位置提供该参数。$A$A

此示例构造一个新函数并将其指定为其参数之一,然后调用该函数,将引用传递给可访问的 .fn$A$A

var fn = new Function("$A", "a", "b");
fn(window.$A, 1, 2);

$A在 LWC 中被阻止

该变量在 Lightning Web 组件中不受支持,因为它特定于 Aura 框架。$A

被 LWS 阻止的分析库

Analytics 库需要不受限制地访问整个页面。此要求与 LWS 要求冲突,即不允许属于一个命名空间的代码访问来自另一个命名空间的数据。

LWS 可防止分析脚本访问在 JavaScript 沙箱外部的浏览器中运行的代码。只允许在该领域中运行平台代码。LWS 还会阻止分析脚本访问 JavaScript 沙箱中的全局对象,因为这些脚本不属于为沙箱保留的命名空间。

Experience Cloud 站点可以使用解决方法使分析库能够与 Lightning Locker 配合使用。如果在组织中启用了 LWS 而不是 Lightning Locker,则此方法也有效。此解决方法仅适用于 Experience Cloud 站点,因为 Experience Builder 允许管理员访问站点的标记。其他 Salesforce 应用程序和组件不允许此类访问。有关更多信息,请参阅 Experience Cloud 开发人员指南。

在 LWS 中指定 Blob 对象的 MIME 类型

Lightning Web Security 要求您在创建 Blob 对象时设置 MIME 类型。LWS 允许某些 MIME 类型,清理某些 MIME 类型,并阻止其余类型。

创建 Blob 对象时显式设置 MIME 类型。

const blob = new Blob([result], { type: "text/plain" });

如果未设置类型,则不会使用预期内容创建对象。

LWS 允许这些 MIME 类型,与 Lightning Locker 相同。

  • application/octet-stream— 二进制文件的默认值
  • application/json— JSON 格式
  • application/pdf— 可移植文档格式 (.pdf)
  • video/— 所有哑剧类型video/*
  • audio/— 所有哑剧类型audio/*
  • image/— 所有哑剧类型image/*
  • font/— 所有哑剧类型font/*
  • text/plain— 文本 (.txt)
  • text/markdown— 降价 (.md)
  • application/zip— 压缩存档 (.zip)
  • application/x-bzip— Bzip 存档 (.bz)
  • application/x-rar-compressed— RAR 存档 (.rar)
  • application/x-tar— 磁带存档 (.tar)

LWS 清理 、 和 MIME 类型。这些类型是允许的,但 LWS 会删除潜在的恶意代码。text/htmlimage/svg+xmltext/xml

任何其他类型都会被阻止,并显示错误消息。Unsupported MIME type

若要发送未明确允许的二进制文件,请将 MIME 类型指定为 .application/octet-stream

在 LWS 中访问 iframe 内容

启用 LWS 后,当内容来自同一来源时,Lightning Web 组件可以访问元素中的内容。Lightning Locker 可防止访问来自同一来源的内容。iframeiframe

Web 浏览器会阻止访问来自不同来源的内容。 浏览器遵循同源策略来阻止元素中的跨域内容。某些属性(例如)是允许的。有关跨域可访问窗口属性的详细信息,请参阅 HTML 规范中的 CrossOriginProperties。iframeiframe.contentWindow.postMessage

LWS 跨沙箱边界维护标识,以便检查事件源的标识正常工作。iframepostMessage

LWS 不允许 Blob 对象中的元素。iframe

LWS 将元素的属性限制为使用 和 方案的值。不允许使用类似的 URL 方案。srciframehttp://https://javascript://

允许的 URL 协议方案

LWS 将 HTMLObjectElement.data 属性和元素的属性限制为使用 和 方案的值。不允许使用类似的 URL 方案。srciframehttp://https://javascript://

LWS 消毒

LWS 会清理 Lightning 组件中使用的 HTML 和 SVG 元素中的内容字符串。LWS 会检查字符串并删除存在潜在安全风险的内容。此清理过程可防止跨站点脚本 (XSS) 攻击。

LWS 使用元素和属性的允许列表,这些元素和属性在清理后可以保留在 DOM 树中。

LWS 对属性(如 和 )执行字符串清理等失真。LWS 可以对某些元素应用多种失真,以防止不安全行为并删除不安全内容。innerHTMLsetHTML

LWS Sanitizer 的 HTML 允许列表

以下是允许的元素列表,这些元素在清理后保留在 DOM 中。LWS 会删除不在此列表中的 HTML 元素。

  • a, abbr, acronym, address, area, article, aside, audio
  • b, bdi, bdo, big, blockquote, body, br, button
  • caption, canvas, center, cite, code, col, colgroup, command
  • datalist, dd, del, details, dfn, dir, div, dl, dt
  • em
  • fieldset, figure, figcaption, footer, form
  • h1, h2, h3, h4, h5, h6, head, header, hgroup, hr
  • i, iframe, img, input, ins
  • keygen, kbd
  • label, legend, li
  • map, mark, menu, meter
  • nav
  • ol, optgroup, option, output
  • p, pre, progress
  • q
  • rp, rt, ruby
  • s, samp, section, select, small, source, span, strike, strong, style, sub, summary, sup
  • table, tbody, td, textarea, tfoot, th, thead, time, tr, track, tt
  • u, ul
  • var, video
  • wbr

例如,此代码包含不允许的标记。<script>

<div>
  <script>
    alert(1);
  </script>
</div>

消毒后,它变成:

<div></div>

LWS 的 SVG 允许列表

SVG 可能成为跨站点脚本 (XSS) 攻击的目标,因为它对加载和执行外部资源的宽松方法。LWS 在遇到潜在的恶意内容时会应用 SVG 清理规则来防止 XSS 攻击。

SVG 语言包含可在标签中使用的大量元素列表。<svg>

以下是 LWS 认为安全并允许在消毒后保留在元素中的元素列表。LWS 会删除不在此列表中的元素。svg

  • a, altglyph, altglyphdef, altglyphitem, animatecolor, animatemotion, animatetransform, audio
  • canvas, circle, clippath
  • defs, desc
  • ellipse
  • filter, font
  • g, glyph, glyphref
  • hkern
  • image
  • line, lineargradient
  • marker, mask, mpath
  • path, pattern, polygon, polyline
  • radialgradient, rect
  • stop, switch, symbol
  • text, textpath, title, tref, tspan
  • use
  • video, view, vkern

例如,此元素包含一个允许的元素和一个不允许的元素。svggscript

<svg>
  <g></g>
  <script></script>
</svg>

清理后,此代码变为:

<svg><g></g></svg>

SVG 元素中的静态资源

LWS 会清理使用标签和支持属性的其他 SVG 元素加载的资源。<use>href

例如,此标记中的 URL 已清理。<use>

<use href="foo.svg#circle"></use>

LWS 将该值替换为清理值。清理后的 URL 类似于:href

<use href="httplocalhostfoo_svg#circle"></use>

清理后的 URL 在沙盒之外是未知的。

尽管对 DOM 进行了更改,但内容仍按预期运行。

内容安全策略概述

Lightning 组件框架使用内容安全策略 (CSP) 对内容施加限制。主要目标是帮助防止跨站点脚本 (XSS) 和其他代码注入攻击。

CSP 是一种 W3C 标准,它定义了用于控制可在页面上加载的内容源的规则。所有 CSP 规则都在页面级别工作,并应用于所有组件和库。Web 浏览器遵循网页标题中指定的 CSP 规则来阻止向未知服务器请求资源,包括脚本、图像和其他数据。CSP 指令也适用于客户端 JavaScript,例如通过限制 HTML 中的内联 JavaScript。

该框架支持以下特定的 CSP 规则:JavaScript 库只能从您的组织引用

所有外部 JavaScript 库都必须作为静态资源上传到您的组织。该指令要求从同一源调用脚本源。有关详细信息,请参阅使用第三方 JavaScript 库。script-src 'self'默认情况下,资源必须位于您的组织中

、 、 、 和 指令设置为 。因此,默认情况下,字体、图像、视频、框架内容、CSS 和脚本等资源必须位于组织中。font-srcimg-srcmedia-srcframe-srcstyle-srcconnect-src'self'

可以通过添加受信任的 URL(以前称为 CSP 受信任的站点)来更改 CSP 指令以允许访问第三方资源。有关详细信息,请参阅管理受信任的 URL。

但是,您无法将这些资源的协议从 HTTPS 更改为 HTTP。资源的 HTTPS 连接

所有对外部字体、图像、框架和 CSS 的引用都必须使用 HTTPS URL。无论资源位于您的组织中还是通过受信任的 URL 访问,此要求都适用。不允许内联 JavaScript

脚本标记不能用于加载 JavaScript,事件处理程序不能使用内联 JavaScript。不允许使用指令的源。例如,阻止使用事件处理程序运行内联脚本的这种尝试:unsafe-inlinescript-src

<button onclick="doSomething()"></button>

浏览器支持

并非所有浏览器都强制实施 CSP。有关强制实施 CSP 的浏览器列表,请参阅 caniuse.com

注意

IE11 不支持 CSP,因此我们建议使用其他受支持的浏览器来增强安全性。对 IE11 的支持将于 2022 年 12 月 31 日结束。

查找 CSP 冲突

CSP 策略冲突记录在浏览器的开发人员控制台中。如果应用的功能不受影响,则可以忽略 CSP 冲突。

此消息是示例冲突。

Refused to load the script 'https://externaljs.docsample.com/externalLib.js'
because it violates the following Content Security Policy directive: ...

更严格的 CSP 限制

Lightning 组件框架使用内容安全策略 (CSP)(这是 W3C 标准)来控制可在页面上加载的内容源。CSP 规则在页面级别工作,并应用于所有组件和库,无论是否启用了 Lightning Locker 或 Lightning Web Security。“启用更严格的内容安全策略”组织设置可进一步降低跨站点脚本攻击的风险。默认情况下,此设置处于启用状态。

更严格的 CSP 有什么作用?

“启用更严格的内容安全策略”设置禁止指令的源。启用此设置后,标记不能用于加载 JavaScript,事件处理程序也不能使用内联 JavaScript。unsafe-inlinescript-srcscript

必须确保所有代码(包括第三方库)都遵守所有 CSP 限制。

重要

Lightning Web Security (LWS) 依赖于更严格的 CSP。禁用“启用更严格的内容安全策略”设置会降低 LWS 提供的安全级别。我们强烈建议您在启用 LWS 时保持启用更严格的内容安全策略处于打开状态。

更严格的 CSP 会影响哪些体验?

更严格的 CSP 会影响:

  • 闪电体验
  • Salesforce 应用程序
  • 您创建的独立应用(例如,myApp.app)

更严格的 CSP 不会影响:

  • Salesforce 经典
  • 适用于 Salesforce Classic 的任何应用程序,例如 Salesforce Classic 中的 Salesforce 控制台
  • Experience Builder 站点,具有自己的 CSP 设置
  • Lightning Out,允许您在 Lightning 应用程序之外的容器中运行 Lightning 组件,例如 Visualforce 和 Salesforce 选项卡 + Visualforce 站点中的 Lightning 组件。容器定义 CSP 规则。

注意

Experience Builder 站点中的 CSP 通过每个站点的设置单独控制。

开发安全代码

Lightning Locker 架构层通过将单个 Lightning 组件命名空间隔离在自己的容器中并强制执行编码最佳实践来增强安全性。Lightning Web Security (LWS) 旨在使您的组件更容易使用安全编码实践,旨在取代 Lightning Locker。Lightning Locker 一直是 Lightning 组件的默认安全架构。LWS 最初在 22 年春季开始为 Lightning Web 组件替换 Lightning Locker。

Lightning 组件框架使用 JavaScript 严格模式在浏览器中打开本机安全功能。它使用内容安全策略 (CSP) 规则来控制可在页面上加载的内容源。

Lightning Locker 的安全性

Lightning Locker 提供组件隔离和安全性,允许来自多个来源的代码使用安全、标准的 API 和事件机制执行和交互。Lightning Locker 一直是 Lightning 组件的默认安全架构。Lightning Web Security (LWS) 旨在使您的组件更容易使用安全编码实践,旨在取代 Lightning Locker。LWS 最初在 22 年春季开始为 Lightning Web 组件替换 Lightning Locker。

JavaScript 严格模式强制执行

在 JavaScript 中,严格模式在模块中强制执行,但在常规程序中是可选的。但是,Lightning Locker 和 Lightning Web Security 隐式启用了 JavaScript 严格模式。无需在代码中指定。JavaScript 严格模式使代码更安全、更可靠、更受支持。"use strict"

当启用严格模式并采取不安全的操作时,JavaScript 会抛出错误,否则这些错误会被禁止。不安全操作的示例包括将值分配给不可写的属性以及使用尚未声明的变量。报告这些操作可以捕获变量名称键入错误的情况。

使用严格模式时的一些常见绊脚石是:

  • 您必须声明所有变量。使用库或模块中的任何 、 或声明。varletconst
  • 若要在组件之间共享代码,必须从模块中导出变量和函数,并将它们导入到其他模块中。请参阅共享 JavaScript 代码。
  • 组件使用的库也必须在严格模式下工作,但不需要在自己的代码中指定。请参阅使用第三方 JavaScript 库。"use strict"
  • 如果使用显式指定的第三方库,则可能会在 LWS 下出现错误。请参见使用 LWS 的第三方库严格模式。"use strict"

有关 JavaScript 严格模式的更多信息,请参阅 Mozilla 开发人员网络。

DOM 访问遏制

组件只能遍历 DOM 并访问它创建的元素。此行为可防止反模式进入其他组件拥有的 DOM 元素。

Lightning Web 组件不能使用 或 全局属性来查询 DOM 元素。例如,使用 而不是 .windowdocumentthis.template.querySelector()document.querySelector()

注意

使用 Lightning Web Security (LWS),组件对 DOM 的访问由浏览器通过 Shadow DOM 控制,Shadow DOM 是在 Web 浏览器中实现的 Web 标准。LWS 通过要求 to be 来强制关闭,这提供了额外的封装限制。参见 MDN 中的 ShadowRoot.mode。shadowRootmodeclosed

安全包装纸

为了安全起见,Lightning Locker 通过隐藏对象或将其包装在对象的安全版本中来限制全局对象的使用。例如,的安全版本是 。Locker 会截获对 的调用并改用。windowSecureWindowwindowSecureWindow

若要查看 、 和包装器支持哪些 API,请使用 Locker API 查看器工具。SecureWindowSecureDocumentSecureElementSecureWindow

可用:Lightning Web 组件和 Aura 组件

对象的安全包装器,它表示包含 DOM 文档的窗口。window

如果 Lightning Web 组件和 Aura 组件属于同一命名空间,则它们共享同一实例。SecureWindowSecureDocument

可用:Lightning Web 组件和 Aura 组件

对象的安全包装器,表示 HTML 文档或页面的根节点。对象是页面内容的入口点,即 DOM 树。documentdocument

如果 Lightning Web 组件和 Aura 组件属于同一命名空间,则它们共享同一实例。SecureDocumentSecureObject

可用:Lightning Web 组件和 Aura 组件

由 Lightning Locker 包装的对象的安全包装器。当您看到 时,这通常意味着您无权访问基础对象,并且其属性不可用。SecureObjectSecureElement

可用:Lightning Web 组件和 Aura 组件

对象的安全包装器,表示各种 HTML 元素。element

如果 Lightning Web 组件和 Aura 组件属于同一命名空间,则它们共享同一实例。SecureElement

Lightning Web 组件还使用包装器。SecureLightningElementSecureLightningElement

可用:Lightning Web 组件

基类的安全包装器。Lightning Web 组件扩展了基类,并在运行时 Locker 使用 .创建 Lightning Web 组件时,请勿直接扩展。LightningElementLightningElementSecureLightningElementSecureLightningElement

不支持这些 API。

  • Element.attachShadow()
  • Element.shadowRoot – 改用this.template
  • Element.slot

SecureTemplate

可用:Lightning Web 组件

对象的安全包装器,表示节点。templateshadowRoot

不支持这些 API。

  • Node.localName
  • Node.namespaceURI
  • Node.nodeValue
  • Node.nextSibling
  • Node.previousSibling
  • Node.parentElement
  • Node.parentNode
  • Node.prefix
  • NonDocumentTypeChildNode.nextElementSibling
  • NonDocumentTypeChildNode.previousElementSibling
  • DocumentOrShadowRoot.getSelection()
  • DocumentOrShadowRoot.styleSheets
  • DocumentOrShadowRoot.pointerLockElement
  • DocumentFragment.getElementById()
  • ParentNode.append
  • ParentNode.prepend
  • constructor
  • EventTarget.dispatchEvent
  • pictureInPictureElement

注意

Lightning Web Security 不使用包装器。它使用 JavaScript 沙盒中的 API 失真来有选择地修改启用非安全行为的 API。

eval() 函数受 Lightning Locker 限制

在 Lightning Locker 中,支持使用该函数来启用动态评估代码的第三方库。但是,它仅限于在命名空间的全局范围内工作。该函数无法访问调用它的范围内的局部变量。eval()eval()

通常,有两种执行模式。直接调用时,它在本地范围内工作。当您通过引用调用它时,它会在全局范围内工作。Lightning Locker仅支持后者。eval()eval()

例如,假设您执行以下代码:

window.foo = 1;
function bar() {
  var foo = 2;
  return eval("foo");
}

在本地范围内执行评估时,调用 2 时返回 2,在全局范围内执行评估时返回 1。如果必须使用本地范围中的变量,请重构代码。使用 ,将局部变量声明为参数,将它们作为参数传递,并添加 return 语句:bar()Function()

window.foo = 1;
function bar() {
  var foo = 2;
  return Function("foo", "return foo")(foo);
}

提示

Lightning Web Security 不限于全局范围,因为代码在沙盒中执行。但是,仍然不建议直接在本地范围内调用。eval()eval()

允许的 MIME 类型

Lightning Locker 分析 Blob 对象中使用的 MIME 类型。Locker 允许某些 MIME 类型,清理某些 MIME 类型,并阻止其余类型。

Lightning Locker 允许这些 MIME 类型。

  • application/octet-stream— 二进制文件的默认值
  • application/json— JSON 格式
  • application/pdf— 可移植文档格式 (.pdf)
  • video/— 所有哑剧类型video/*
  • audio/— 所有哑剧类型audio/*
  • image/— 所有哑剧类型image/*
  • font/— 所有哑剧类型font/*
  • text/plain— 文本 (.txt)
  • text/markdown— 降价 (.md)
  • application/zip— 压缩存档 (.zip)
  • application/x-bzip— Bzip 存档 (.bz)
  • application/x-rar-compressed— RAR 存档 (.rar)
  • application/x-tar— 磁带存档 (.tar)

Locker 清理 、 和 MIME 类型。这些类型是允许的,但 Locker 会删除潜在的恶意代码。text/htmlimage/svg+xmltext/xml

任何其他类型都会被阻止,并显示错误消息。Unsupported MIME type

若要发送未明确允许的二进制文件,请将 MIME 类型指定为 .application/octet-stream

提示

Lightning Web Security (LWS) 要求您指定 MIME 类型。请参阅在 LWS 中指定 Blob 对象的 MIME 类型。

带有 Lightning Locker 的第三方 Web 组件

Lightning Locker 阻止使用第三方 Web 组件,以防止 Salesforce 平台上的安全风险。

Web 组件是自定义元素。若要定义自定义元素,必须使用 API。但是,此 API 允许您按标记名称全局注册组件。全局注册标记名称存在安全风险,因为攻击者可以创建任何已注册自定义元素的实例,并可能获得对敏感信息的访问权限。Lightning Locker 的包装器阻止了创建自定义 Web 组件的方法。customElements.defineSecureWindowcustomElements

Locker API 查看器工具中的包装器列表显示不受支持。SecureWindowcustomElements

对 Salesforce 全局变量的访问受限

Lightning Locker 阻止访问其他 Salesforce 功能可用的某些全局 JavaScript 对象。

Lightning Web 组件无权访问

  • $A
  • Aura
  • Sfdc
  • sforce

Locker 限制了 Lightning Web 组件对这些对象的访问,因为它们要么已被弃用,要么会导致跨框架依赖。

Lightning Locker为不受支持的浏览器禁用

Lightning Locker 依赖于浏览器中的一些 JavaScript 功能:支持严格模式、对象和对象。如果浏览器不符合要求,Lightning Locker 将无法强制执行其所有安全功能,因此会被禁用。WeakMapProxy

Lightning Locker 在 Lightning Experience 支持的浏览器中启用。如果您的应用在不受支持的浏览器中运行,您可能会遇到无法修复的问题。通过要求应用在受支持的浏览器中运行,使你的生活更轻松,用户的浏览体验更安全。

注意

IE11 禁用了 Lightning Locker。对 IE11 的支持将于 2022 年 12 月 31 日结束。

传递给子组件时代理的数组

当您将数组传递给子组件时,Lightning Locker 会将该数组包装在代理中。

JavaScript 用于传递给嵌套组件的数组。如果将数组从父组件传递到嵌套组件的多个层,则会多次代理该数组。当数组由多个对象组成时,多个代理在运行函数(例如在数组上)时会对性能造成负面影响。如果页面使用的组件通过子组件层传递大量对象,则该组件可能会变得无响应。ProxyJSON.stringify

若要限制处理大型数组对性能的负面影响,请使用以下方法之一重构代码。

  • 避免将大型对象数组向下传递多个级别。例如,将数组从父级传递到子级,但不要进一步传递。
  • 在父组件中将数据划分为多个较小的数组。

Lightning Web Security (LWS) 没有此限制。

使用 Lightning Locker 运行的 querySelector API

启用 Lightning Locker 后,使用或 API 返回元素的组件可能会遇到大型 DOM 树的内存泄漏和性能问题。从 DOM 中删除组件时,内存有时不会释放。即使在 DevTools 中手动调用垃圾回收,垃圾回收也不会清除内存。浏览器可能会使用不断增加的内存量,并可能因内存不足错误而崩溃。this.template.querySelectorthis.template.querySelectorAll

此问题的唯一解决方法是在运行组件的组织中启用 Lightning Web Security。垃圾回收在 LWS 下正常工作。

或者,考虑使用 refs 而不是 .querySelector

Lightning Locker工具

Lightning Locker 工具可帮助您开发与 Lightning Locker 兼容并高效运行的安全代码。Lightning Locker API 查看器

使用 Lightning Locker API 查看器查看 Lightning Locker 支持哪些标准 DOM API 用于最复杂的包装器:、 和 。SecureDocumentSecureElementSecureWindow

储物柜控制台

使用 Locker 控制台检查 JavaScript 代码与 Lightning Locker 的兼容性,并比较它在启用和禁用 Lightning Locker 时的运行方式。

评估 JavaScript 代码是否存在问题或基准测试代码,而无需创建应用。

要评估或基准测试您的 JavaScript 代码,请将其粘贴或键入到控制台中 (1)。启用或禁用 Locker 和 Strict CSP (2)。若要运行控制台中显示的代码,请单击“评估”(3)。若要使用和不使用 Locker 运行代码并查看相关性能指标,请单击“基准测试”。要清除显示的结果,请单击“清除结果”。

选择组织的 Locker API 版本

选择 Lightning Locker 在整个组织中使用的 API 版本。默认值为当前 API 版本,其中包括最新的 Locker 安全增强功能。当自定义组件仅符合旧版本中的 Locker 时,请选择较早的 API 版本。当组件符合当前安全增强功能时,您可以将设置更改为当前 API 版本。

在您的沙盒组织更新到新的 Salesforce 版本后,自定义组件可能会与 Lightning Locker 安全增强功能发生冲突。检查 Web 控制台中是否有消息。

我们建议更新自定义组件以符合最新的 Locker API 版本,但我们知道更新可能需要一些时间。您的组织还可能依赖于包含第三方开发人员必须更新的自定义组件的托管包。将您的组织设置为使用较旧的 Locker API 版本,以便开发人员有时间更新自定义 Lightning 组件并遵守 Locker 的最新安全增强功能。

在沙盒组织中验证自定义组件是否在将 Locker API 版本设置为最新版本的情况下正确执行。然后,可以将 Locker API 版本更改为生产组织中的最新版本,以利用最新的安全增强功能。

注意

Locker API 版本设置在 Winter ’20 版本中首次提供。您可以选择的最早 Locker API 版本是 46.0,它启用了 Summer ’19 版本的 Locker 功能。

使用更衣室 API 版本的位置

组织的 Locker API 版本会影响 Lightning Locker 影响什么?中列出的区域中使用的所有 Lightning 组件。

在组织中启用 Lightning Web Security 时,组织的 Locker API 版本对 Lightning Web 组件没有影响。

注意

每个 Lightning Web 组件都有一个配置文件,其中包含 .组件和 Locker API 版本使用相同的版本号策略来与 Salesforce 版本保持一致。但是,Locker API 版本的组织设置与组件的 .组织中设置的 Locker API 版本适用于所有 Lightning 组件,无论其设置如何。apiVersionapiVersionapiVersionapiVersion

Locker API 版本更改

查看 API 版本中的安全更改,以帮助确定自定义组件的兼容性。

Locker API 版本安全更改描述
53.0 及更高版本没有此版本中的 Lightning Locker 更改不会影响自定义组件。
52.0防止多个潜在的基于突变的跨站点脚本 (mXSS) 向量。Lightning Locker 加强了标记的清理以提高安全性。此更改适用于所有 API 版本。无法通过选择较早的 API 版本来回滚此更改。
51.0没有此版本中的 Lightning Locker 更改不会影响自定义组件。
50.0没有此版本中的 Lightning Locker 更改不会影响自定义组件。
49.0限制 API$A.getCallback()Lightning Locker 包装该功能。包装的 JavaScript 必须遵守 Locker 的安全限制。请参阅 Locker API 查看器,了解 Lightning Locker 中 JavaScript API 的支持状态。$A.getCallback()$A.getCallback()
48.0清理插入的 HTMLexecCommandLightning Locker 会清理插入的 HTML,用于删除潜在的恶意可执行脚本内容。document.execCommand(insertHTML)
47.0拒绝表达式import()Lightning Locker 不允许该表达式,因为导入第三方代码存在潜在的安全风险。import()
 限制 HTML 元素的 和 属性nameidLightning Locker 不允许将 or 属性设置为为 DOM 保留的属性名称。nameid
46.0所有 Locker 安全功能支持自 Lightning Locker 推出以来的所有功能,当时它被称为 LockerService。这包括版本 37.0(16 年春季)到版本 46.0(19 年夏季)版本中的所有功能。

更改组织的 Locker API 版本

  1. 在“设置”中,输入“快速查找”框,然后选择“会话设置”。Session
  2. 在“Locker API 版本”部分中,对于“在 API 版本中使用安全增强功能”字段,选择 API 版本。
  3. 点击保存

使用 RefreshView API 刷新组件数据

无论是用户驱动还是应用调用,在不重新加载整个页面的情况下同步数据的能力都是一项关键的用户体验要求。RefreshView API 和模块提供了一种在 LWC 和 Aura 中刷新组件数据的标准方法。lightning/refresh

RefreshView API 对刷新范围的详细控制使你能够创建精细的用户体验,同时保持向后兼容性。

RefreshView API 为您提供了更新组件层次结构(称为视图)的选项,而无需重新加载整个页面。此刷新可确保与订阅该视图中刷新事件的组件外部源的数据完全同步。RefreshView API 支持由最终用户或 Web 组件触发的刷新。RefreshView API 为 LWC 组件中的数据刷新体验提供了标准机制,允许灵活控制刷新范围。

RefreshView API 可以刷新 Salesforce 平台容器以及自定义 LWC 和 Aura 组件的数据。

Lightning 数据服务支持 RefreshView API。

使用 RefreshView API 的限制

RefreshView API 可以在已启用 Lightning Web Security 或 Lightning Locker 的组织中使用。每个安全体系结构的注册容器和处理程序的协议都不同。

基本 Lightning Aura 组件目前不支持 RefreshView API。

RefreshView API 用户体验

可靠的内容刷新是一项基本的 Web UX 要求,可以由用户驱动,也可以由应用调用。

用户触发的刷新的典型流程为:

  1. Lightning Web 组件显示一个按钮(或其他用户界面控件),用于启动一个进程,该进程将组件中显示的数据与其源同步。
  2. 点击按钮时,按钮组件将调度一个事件。RefreshEvent
  3. 向 RefreshView API 注册的最近级别的容器组件将收到 ,停止其传播。RefreshEvent
  4. 组件的刷新处理程序在相应的组件上启动刷新过程。它们可以显示微调器、执行检测以及执行其他操作来准备要刷新的 UI。
  5. 处理程序的后代组件通过公开的 API 挂钩参与刷新过程。他们可以从 Salesforce 组织获取数据或执行其他任务以将显示的数据与外部数据源同步。
  6. 当所有数据在屏幕上同步和更新时,组件层次结构的刷新完成。

对于应用触发的刷新:

  1. 应用程序确定视图需要刷新以确保数据同步。例如,在从过期会话重新进行身份验证后,或者在移动 UI 上执行下拉刷新操作后,需要刷新。
  2. 从这一点来看,刷新流类似于上面描述的用户触发刷新。

使用 RefreshView API

若要使用 RefreshView API,请导入模块。lightning/refresh

该模块还取代了 Aura 并暴露了:lightning/refreshforce:refreshView

  • 模块可以触发以发出刷新信号的事件。RefreshEvent
  • 允许组件注册以接收调度的刷新事件并开始刷新过程的方法。
  • 允许组件注册在刷新过程中调用的回调方法的方法。
  • 状态定义,如 、 和 。RefreshCompleteRefreshCompleteWithErrorRefreshError

在 LWC 中注册刷新处理程序方法

您必须注册属于启用刷新的视图的组件,以包含在刷新过程中。

若要参与视图刷新,组件需要注册一个处理程序方法。使用组件中的方法注册处理程序方法。registerRefreshHandler()connectedCallback()

重要

当组件在未启用 LWS 且仍在使用 Lightning Locker 的组织中运行时,方法中传递的参数需要不同的格式。registerRefreshHandler()

在容器收到 .已注册的容器由已注册的刷新处理程序组成“刷新树”,其顺序模拟 DOM。然后,容器调用参与者组件的回调刷新方法,这些组件已注册刷新处理程序。RefreshEvent

启用了 LWS 的组织示例

import { LightningElement } from "lwc";
import { registerRefreshHandler, unregisterRefreshHandler } from "lightning/refresh";
export default class RefreshHandler extends LightningElement {
  refreshHandlerID;
  connectedCallback() {
    this.refreshHandlerID = registerRefreshHandler(this, this.refreshHandler);
  }
  disconnectedCallback() {
    unregisterRefreshHandler(this.refreshHandlerID);
  }
  refreshHandler() {
    // example usage case for refresh participant
    // fetch some data and report status once complete
    let endPoint = "https://api.<your company>.com";
    return new Promise((resolve) => {
      fetch(endPoint, {
        method: "GET",
      });
      resolve(true);
    });
  }
}

启用了 Lightning Locker 的组织示例

import { LightningElement } from "lwc";
import { registerRefreshHandler, unregisterRefreshHandler } from "lightning/refresh";
export default class RefreshHandler extends LightningElement {
  refreshHandlerID;
  connectedCallback() {
    this.refreshHandlerID = registerRefreshHandler(
      this.template.host,
      this.refreshHandler.bind(this),
    );
  }
  disconnectedCallback() {
    unregisterRefreshHandler(this.refreshHandlerID);
  }
  refreshHandler() {
    // example usage case for refresh participant
    // fetch some data and report status once complete
    let endPoint = "https://api.<your company>.com";
    return new Promise((resolve) => {
      fetch(endPoint, {
        method: "GET",
      });
      resolve(true);
    });
  }
}

刷新树中已注册的刷新方法按广度优先的顺序从已注册容器的节点调用。此方法可确保在调用较低级别(子)处理程序之前解析较高级别(父级)处理程序。

已注册的刷新处理程序回调必须:

  • 返回解析为 a 的 a:PromiseBoolean
    • true如果组件已完成刷新操作,并且刷新过程可以从此节点继续沿刷新树向下
    • false防止刷新过程继续到此节点的子元素
  • 根据当前状态执行必要的视图更新操作。
  • 如有必要,请确保重新同步数据和状态。
  • 在此过程中显示相应的 UI,例如微调器或 Toast。

您还必须注销组件的处理程序方法,如上例所示。disconnectedCallback()

使用 RefreshEvent 发出刷新信号

要启动视图刷新,请触发 中定义的事件。此事件向容器发出请求以开始刷新过程。可以从任何组件触发此事件。RefreshEventlightning/refresh

import { LightningElement } from "lwc";
import { RefreshEvent } from "lightning/refresh";

export default class RefreshButton extends LightningElement {
  // signal a refresh programmatically
  // or via a button click
  beginRefresh() {
    this.dispatchEvent(new RefreshEvent());
  }
}

注册以接收 RefreshEvent

若要在用户视图上开始刷新过程,每个容器中的应用程序控制器必须注册才能接收 .使用容器中的方法注册以接收 .RefreshEventregisterRefreshContainer()connectedCallback()RefreshEvent

重要

当组件在未启用 LWS 且仍在使用 Lightning Locker 的组织中运行时,方法中传递的参数需要不同的格式。registerRefreshContainer()

注意

如果要将组件添加到活动页面,则无需创建容器即可接收 .仅当想要确定刷新范围时,才添加容器。RefreshEvent

启用了 LWS 的组织示例

import { LightningElement } from "lwc";
import {
  registerRefreshContainer,
  unregisterRefreshContainer,
  REFRESH_ERROR,
  REFRESH_COMPLETE,
  REFRESH_COMPLETE_WITH_ERRORS,
} from "lightning/refresh";

export default class RefreshContainer extends LightningElement {
  refreshContainerID;
  connectedCallback() {
    this.refreshContainerID = registerRefreshContainer(this, this.refreshContainer);
  }
  disconnectedCallback() {
    unregisterRefreshContainer(this.refreshContainerID);
  }
  refreshContainer(refreshPromise) {
    console.log("refreshing");
    return refreshPromise.then((status) => {
      if (status === REFRESH_COMPLETE) {
        console.log("Done!");
      } else if (status === REFRESH_COMPLETE_WITH_ERRORS) {
        console.warn("Done, with issues refreshing some components");
      } else if (status === REFRESH_ERROR) {
        console.error("Major error with refresh.");
      }
    });
  }
}

启用了 Lightning Locker 的组织示例

import { LightningElement } from "lwc";
import {
  registerRefreshContainer,
  unregisterRefreshContainer,
  REFRESH_ERROR,
  REFRESH_COMPLETE,
  REFRESH_COMPLETE_WITH_ERRORS,
} from "lightning/refresh";

export default class RefreshContainer extends LightningElement {
  refreshContainerID;
  connectedCallback() {
    this.refreshContainerID = registerRefreshContainer(
      this.template.host,
      this.refreshContainer.bind(this),
    );
  }
  disconnectedCallback() {
    unregisterRefreshContainer(this.refreshContainerID);
  }
  refreshContainer(refreshPromise) {
    console.log("refreshing");
    return refreshPromise.then((status) => {
      if (status === REFRESH_COMPLETE) {
        console.log("Done!");
      } else if (status === REFRESH_COMPLETE_WITH_ERRORS) {
        console.warn("Done, with issues refreshing some components");
      } else if (status === REFRESH_ERROR) {
        console.error("Major error with refresh.");
      }
    });
  }
}

传递 to 将事件侦听器绑定到 的元素。当已注册的刷新容器收到 时,刷新过程将在其刷新树上开始。thisregisterRefreshContainerRefreshEventRefreshEvent

注意

RefreshEvent遵循 DOM 事件冒泡规则,因此开始此过程的刷新容器是离信令组件最近的注册祖先。

在此示例中,当容器收到 .刷新过程开始时,回调接收 a 作为参数。刷新过程完成后,将使用一个值来解决此问题。refreshContainer()RefreshEventPromisePromiseRefreshStatus

若要管理与刷新相关的进程,请使用已注册容器的回调方法。例如:

  • 检测开始/结束
  • 显示微调器
  • 错误处理
  • 祝酒词

如示例中所示,在使用该方法时,还必须将视图控制器注销为刷新容器。RefreshEventdisconnectedCallback()unregisterRefreshContainer()

RefreshView API 示例

使用 RefreshView API 通常涉及一系列操作。

首先,页面上的某个位置必须有一个组件,可以在需要刷新时发送事件。在此示例中,组件可以发出刷新信号。RefreshEventrefreshButton

// refreshButton.js
import { LightningElement } from "lwc";
import { RefreshEvent } from "lightning/refresh";

export default class RefreshButton extends LightningElement {
  // signal a refresh programmatically
  // or via a button click
  beginRefresh() {
    this.dispatchEvent(new RefreshEvent());
  }
}
<!-- refreshButton.html -->
<template>
  <button label="Refresh Button" onclick={beginRefresh}>Refresh</button>
</template>

接下来,为了参与视图刷新,组件注册一个处理程序方法,以便在容器收到 .在此示例中,handler 方法在 中注册。RefreshEventrefreshHandler.js

//refreshHandler.js
import { LightningElement } from "lwc";
import { registerRefreshHandler, unregisterRefreshHandler } from "lightning/refresh";
export default class RefreshHandler extends LightningElement {
  refreshHandlerID;
  connectedCallback() {
    this.refreshHandlerID = registerRefreshHandler(this, this.refreshHandler);
    // if the component runs in an org with Lightning Locker instead of LWS, use
    // this.refreshHandlerID = registerRefreshHandler(this.template.host, this.refreshHandler.bind(this));
  }
  disconnectedCallback() {
    unregisterRefreshHandler(this.refreshHandlerID);
  }
  refreshHandler() {
    // example usage case for refresh participant
    // fetch some data and report status once complete
    let endPoint = "https://api.<your company>.com";
    return new Promise((resolve) => {
      fetch(endPoint, {
        method: "GET",
      });
      resolve(true);
    });
  }
}

最后,页面上的某个地方必须有一个组件可以注册接收。在此示例中,注册以接收刷新事件。当收到事件时,它会启动刷新过程。RefreshEventrefreshContainer

// refreshContainer.js
import { LightningElement } from "lwc";
import { registerRefreshContainer, unregisterRefreshContainer } from "lightning/refresh";

export default class RefreshContainer extends LightningElement {
  refreshContainerID;
  connectedCallback() {
    this.refreshContainerID = registerRefreshContainer(this, this.refreshContainer);
    // if the component runs in an org with Lightning Locker instead of LWS, use
    // this.refreshContainerID = registerRefreshContainer(this.template.host, this.refreshContainer.bind(this));
  }
  disconnectedCallback() {
    unregisterRefreshContainer(this.refreshContainerID);
  }
  refreshContainer(refreshPromise) {
    console.log("refreshing");
    return refreshPromise.then((status) => {
      if (status === REFRESH_COMPLETE) {
        console.log("Done!");
      } else if (status === REFRESH_COMPLETE_WITH_ERRORS) {
        console.warn("Done, with issues refreshing some components");
      } else if (status === REFRESH_ERROR) {
        console.error("Major error with refresh.");
      }
    });
  }
}

将 和 组件放在模板中,包括标记中的命名空间。 是此示例中的默认命名空间。refreshHandlerrefreshButtonrefreshContainerc

<!-- refreshContainer.html -->
<template>
  <!-- Ensure handler exists within structure of container -->
  <div>
    <c-refresh-handler></c-refresh-handler>
  </div>
  <div>
    <c-refresh-button></c-refresh-button>
  </div>
</template>

考虑

在开发使用 RefreshView API 的功能时,请记住以下几点。

  • 在处理来自第三方的 Aura 组件或数据时,请使用 RefreshView API。在这些情况下,参与 RefreshView API 的组件可以使用 notifyRecordUpdateAvailable(recordIds) 和/或 refreshApex 来刷新它们从 Lightning 数据服务接收的数据。
  • 仅为要参与刷新的组件注册回调。refresh()lightning/refresh
  • 组件不负责刷新其后代 — 后代必须注册自己的回调才能参与正常值传播之外的刷新。
  • 为了允许作用域内刷新树,容器的注册方式与想要自行刷新的组件类似。

从 Apex 调用 API

要从 Apex 调用 API,请使用命名凭据,该凭据指定标注终结点的 URL 及其所需的身份验证参数。

注意

根据安全策略,由 Lightning 组件创建的会话不会启用 API 访问。此限制甚至会阻止您的 Apex 代码对 Salesforce 进行 API 调用。通过对特定 API 调用使用命名凭据,您可以谨慎且有选择地绕过此安全限制。

对启用 API 的会话的限制并非偶然。请仔细检查使用命名凭据的任何代码,以确保不会造成漏洞。

在使用 Apex 进行 API 调用之前,请查看是否可以从 JavaScript 进行 API 调用。您可以在 JavaScript 中使用 Lightning Data Service (LDS) 来处理 Salesforce 记录的数据和元数据。Lightning Data Service 建立在公共用户界面 API 之上,但它仅支持 API 的一个子集。该子集涵盖了许多处理数据的典型用例。您无法从 JavaScript 代码调用除 LDS 之外的 Salesforce API。

如果 Lightning 数据服务不支持您要使用的实体,或者您想要使用其他 Salesforce API,请从 Apex 类调用该 API。

调用 Apex 方法

Lightning Web 组件可以从 Apex 类导入方法。导入的方法是组件可以通过或命令性调用的函数。@wire

在使用 Apex 方法之前,请确保没有更简单的方法来获取数据,例如,使用基本 Lightning 组件。请参阅数据指南。

重要

假设 Apex 方法提供不可变数据,即使今天可以改变数据。最佳做法是将组件接收的所有数据视为不可变数据。若要更改数据,请创建要更改的对象的浅拷贝。在处理数据时,了解这个概念非常重要。请参阅数据流。

lwc-recipes 存储库具有多个调用 Apex 方法的组件。查找名称以 开头的组件。apex

Apex限制

每次调用 Apex 方法时都会应用 Apex 限制。如果您的 JavaScript 代码通过或命令性方式调用多个 Apex 方法,则 Apex 限制将分别应用于每个方法调用。@wire

导入 Apex 方法

使用 JavaScript 中的默认导入语法通过作用域包导入 Apex 方法。@salesforce/apex

语法

import apexMethodName from "@salesforce/apex/namespace.classname.apexMethodReference";
  • apexMethodName– 标识 Apex 方法的符号。
  • apexMethodReference– 要导入的 Apex 方法的名称。
  • classname– Apex 类的名称。
  • namespace– 如果类与组件位于同一命名空间中,则不要指定命名空间。如果该类位于托管包中,请指定托管包的命名空间。

用法

当 Apex 类名称在 JavaScript 源文件之外发生更改时,该类名称会在 JavaScript 源文件中自动更新。JavaScript 源文件中不会更新方法和参数名称更改。

要调用 Apex 方法,Lightning Web 组件可以:

  • 将 Apex 方法连接到 Lightning Web 组件
  • 命令式调用 Apex 方法

向 Lightning Web 组件公开 Apex 方法

要向 Lightning Web 组件公开 Apex 方法,该方法必须是 and 或 。用 注释方法。staticglobalpublic@AuraEnabled

语法

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

输入和输出支持这些类型。

  • 基元 – Boolean、Date、DateTime、Decimal、Double、Integer、Long 和 String。
  • sObject – 支持标准和自定义 sObject。
  • Apex – Apex 类的实例。 (最常见的是自定义类。
  • 集合 – 任何其他类型的集合。

注意

  • 不支持将 Apex 内部类作为 Lightning Web 组件调用的 Apex 方法的参数或返回值。
  • 不支持从 Lightning Web 组件引用的 Apex 方法的 Apex 注释。从软件包安装的 Lightning Web 组件无法从另一个软件包中的 Apex 类调用 Apex 方法,即使两个软件包位于同一命名空间中也是如此。@NamespaceAccessible@AuraEnabled

用法

Lightning 组件框架不强制执行任何有关 Apex 类位置的规则。如果您使用的是 Salesforce DX,请将 Apex 类放在目录中。<app dir>/main/default/classes

构造 Apex 方法时,必须决定是将参数作为基元传入,还是作为复杂数据类型(如 Apex 类或 sObject)传入。

提示

lwc-recipes 存储库有一个 apexImperativeMethodWithParams 和 apexImperativeMethodWithComplexParams 组件示例,该示例调用具有基元或复杂参数的方法。

将 Apex 方法连接到 Lightning Web 组件

为了读取 Salesforce 数据,Lightning Web 组件使用反应式连线服务。在组件的 JavaScript 类中用于指定 Apex 方法。可以使用属性或函数来接收数据。要对返回的数据进行操作,需要函数。@wire@wire@wire

若要用于调用 Apex 方法,请使用 批注 Apex 方法。在发出网络调用以调用服务器上的 Apex 方法之前,将检查客户端 Lightning 数据服务缓存。要刷新过时的数据,请调用 ,因为 Lightning 数据服务不管理 Apex 预置的数据。@wire@AuraEnabled(cacheable=true)refreshApex()

使用此语法导入 Apex 方法并将其连接到组件。

import apexMethodName from '@salesforce/apex/namespace.classname.apexMethodReference';
@wire(apexMethodName, { apexMethodParams })
propertyOrFunction;
  • apexMethodName– 标识 Apex 方法的符号。
  • apexMethodReference– 要导入的 Apex 方法的名称。
  • classname– Apex 类的名称。
  • namespace– Salesforce 组织的命名空间。指定命名空间,除非组织使用默认命名空间 (),在这种情况下,请勿指定它。c
  • apexMethodParams– 具有与 的参数匹配的属性(如果需要)的对象。如果参数值为 ,则调用该方法。如果参数值为 ,则不调用该方法。如果 Apex 方法重载,则选择要调用的方法的选择是不确定的(实际上是随机的),并且传递的参数现在或将来可能会导致错误。不要重载 Apex 方法。apexMethodnullundefined@AuraEnabled重要apexMethodParams是一个对象。若要将参数值传递给 Apex 方法,请传递其属性与 Apex 方法的参数匹配的对象。例如,如果 Apex 方法采用字符串参数,则不要直接传递字符串。相反,请传递一个对象,该对象包含值为字符串的属性。命令式和有线 Apex 调用都不支持使用映射。请参阅将值传递给 Apex。
  • propertyOrFunction– 从线路服务接收数据流的私有属性或函数。如果属性被修饰为 ,则结果将返回到该属性的属性或属性。如果函数用 修饰,则结果将在具有属性或属性的对象中返回。@wiredataerror@wiredataerror注意属性和属性是 API 中的硬编码值。您必须使用这些值。dataerror

将 Apex 方法连接到属性

让我们看一下 lwc-recipes 存储库中的组件。此组件打印 Apex 方法返回的联系人列表。apexWireMethodToProperty

为了获取其数据,该组件连接了一个 Apex 方法。Apex 方法进行 SOQL 查询,该查询返回每个联系人都有图片的联系人列表。(此组件不呈现图片,但其他示例组件会呈现。正如我们所了解的,要返回一个简单的联系人列表,最好使用 getListUi。但是要使用 SOQL 查询来选择某些记录,我们必须使用 Apex 方法。

请记住,该方法必须是 、 和 或 。该方法必须用 修饰。staticglobalpublic@AuraEnabled(cacheable=true)

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该组件的 JavaScript 代码导入 Apex 方法,并通过 wire 服务调用它。电汇服务要么设置属性的联系人列表,要么向属性返回错误。contacts.datacontacts.error

// apexWireMethodToProperty.js
import { LightningElement, wire } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexWireMethodToProperty extends LightningElement {
  @wire(getContactList) contacts;
}

该模板使用 lwc:if 指令来检查属性是否为真值。如果是,它会循环访问它并呈现每个联系人的名称。如果为 truey,则组件呈现 .contacts.datacontacts.error<c-error-panel>

<!-- apexWireMethodToProperty.html -->
<template>
  <lightning-card title="ApexWireMethodToProperty" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts.data}>
        <template for:each={contacts.data} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={contacts.error}>
        <c-error-panel errors={contacts.error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

使用动态参数连接 Apex 方法

现在,让我们连接一个接受参数的 Apex 方法。此组件也在 lwc-recipes 存储库中。

Apex 方法采用一个名为 的字符串参数,并返回其名称包含该字符串的联系人列表。searchKey

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Name LIKE :key AND Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

组件的 JavaScript 在参数值前面加上 ,以指示它是动态的和反应式的。它引用组件实例的属性。如果其值发生更改,模板将重新呈现。searchKey$

重要

对于 Apex 方法的组件,第一个参数是一个字符串,它是 Apex 方法的名称,在本例中为 .第二个参数是一个对象,其中包含要传递给 Apex 方法的参数。因此,例如,即使采用字符串,我们也不会将字符串作为第二个参数传递。相反,我们传递一个对象,该对象包含一个属性,其值为字符串: 。(如果 Apex 方法采用另一个参数,我们将向对象添加另一个属性。@wirefindContacts@wirefindContacts@wire{ searchKey: '$searchKey' }

// apexWireMethodWithParams.js
import { LightningElement, wire } from "lwc";
import findContacts from "@salesforce/apex/ContactController.findContacts";

/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 300;

export default class ApexWireMethodWithParams extends LightningElement {
  searchKey = "";

  @wire(findContacts, { searchKey: "$searchKey" })
  contacts;

  handleKeyChange(event) {
    // Debouncing this method: Do not update the reactive property as long as this function is
    // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
    window.clearTimeout(this.delayTimeout);
    const searchKey = event.target.value;
    this.delayTimeout = setTimeout(() => {
      this.searchKey = searchKey;
    }, DELAY);
  }
}

模板用作字段。searchKeyvalue<lightning-input>

<!-- apexWireMethodWithParams.html -->
<template>
  <lightning-card title="ApexWireMethodWithParams" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <lightning-input
        type="search"
        onchange={handleKeyChange}
        class="slds-m-bottom_small"
        label="Search"
        value={searchKey}
      ></lightning-input>
      <template lwc:if={contacts.data}>
        <template for:each={contacts.data} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={contacts.error}>
        <c-error-panel errors={contacts.error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

提示

要观看此示例代码的演示,请参阅 Lightning Web 组件视频库中的将 Apex 方法连接到属性

连接具有复杂参数的 Apex 方法

此示例演示如何调用将对象作为参数的 Apex 方法。任何 Apex 方法的语法都是相同的,但此示例显示了用于生成和传递对象的模式。@wire

该组件有三个输入字段,这些字段采用一个字符串、一个数字和多个列表项,组件使用这些字段来生成列表。Apex 方法只是根据值连接并返回一个字符串。当输入值发生更改时,将调用 Apex 方法并预配新数据。@wire

<!-- apexWireMethodWithComplexParams.html -->
<template>
  <lightning-card title="ApexWireMethodWithComplexParams" icon-name="custom:custom63">
    <div class="slds-var-m-around_medium">
      <lightning-input
        label="String"
        type="string"
        value={stringValue}
        class="string-input"
        onchange={handleStringChange}
      ></lightning-input>
      <lightning-input
        label="Number"
        type="number"
        min="0"
        max="100"
        value={numberValue}
        class="number-input"
        onchange={handleNumberChange}
      ></lightning-input>
      <lightning-input
        label="List items"
        type="number"
        min="0"
        max="10"
        value={listItemValue}
        class="list-item-input"
        onchange={handleListItemChange}
      ></lightning-input>
      <br />
      <template lwc:if={apexResponse.data}>
        <p>{apexResponse.data}</p>
      </template>
    </div>
    <template lwc:elseif={apexResponse.error}>
      <c-error-panel errors={error}></c-error-panel>
    </template>
  </lightning-card>
</template>

Apex 方法采用一个对象 。CustomWrapper

public with sharing class ApexTypesController {
    @AuraEnabled(cacheable=true)
    public static String checkApexTypes(CustomWrapper wrapper) {
        // The values are based on the data that is defined in the
        // apexWireMethodWithComplexParams Lightning web component.
        String response =
            'You entered "' +
            wrapper.someString +
            '" as String, and "' +
            wrapper.someInteger +
            '" as Integer value. The list contained ' +
            wrapper.someList.size() +
            ' items.';
        return response;
    }
}
public with sharing class CustomWrapper {
    @TestVisible
    class InnerWrapper {
        @AuraEnabled
        public Integer someInnerInteger { get; set; }
        @AuraEnabled
        public String someInnerString { get; set; }
    }

    @AuraEnabled
    public Integer someInteger { get; set; }
    @AuraEnabled
    public String someString { get; set; }
    @AuraEnabled
    public List<InnerWrapper> someList { get; set; }
}

若要将参数值从组件传递到 Apex 方法,请使用对象。在此示例中,Apex 方法采用一个对象 ,因此该组件会生成一个匹配的对象以传入 .CustomWrapper@wire

// apexWireMethodWithComplexParams.js
import { LightningElement, wire } from "lwc";
import checkApexTypes from "@salesforce/apex/ApexTypesController.checkApexTypes";

export default class ApexWireMethodWithComplexParams extends LightningElement {
  listItemValue = 0;
  numberValue = 50;
  stringValue = "Some string";

  parameterObject = {
    someString: this.stringValue,
    someInteger: this.numberValue,
    someList: [],
  };

  @wire(checkApexTypes, { wrapper: "$parameterObject" })
  apexResponse;

  handleStringChange(event) {
    this.parameterObject = {
      ...this.parameterObject,
      someString: (this.stringValue = event.target.value),
    };
  }

  handleNumberChange(event) {
    this.parameterObject = {
      ...this.parameterObject,
      someInteger: (this.numberValue = parseInt(event.target.value, 10)),
    };
  }

  handleListItemChange(event) {
    const someList = [];
    for (let i = 0; i < event.target.value; i++) {
      someList.push({
        someInnerString: this.stringValue,
        someInnerInteger: this.numberValue,
      });
    }
    this.parameterObject = {
      ...this.parameterObject,
      someList,
    };
  }
}

将 Apex 方法连接到函数

现在让我们看一下 lwc-recipes 存储库中的组件。此组件将 Apex 方法调用连接到函数。由于结果被预配到函数,因此 JavaScript 可以对结果进行操作。此外,模板访问数据的方式与将结果预配到属性时略有不同。apexWireMethodToFunction

渲染的组件与(标头除外)相同。apexWireMethodToProperty

此组件调用与 相同的 Apex 方法。该方法必须是 、 和 或 。该方法必须用 修饰。apexWireMethodToPropertystaticglobalpublic@AuraEnabled(cacheable=true)

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该组件的 JavaScript 代码导入 Apex 方法,并通过 wire 服务调用它。线路服务通过具有 or 属性的对象将结果提供给函数。如果电汇服务提供 ,则将其分配给 ,该模板将用于。如果它提供 ,则将其分配给 ,该模板中也会使用。如果这些属性的值发生更改,模板将重新呈现。wiredContacts()errordatadatathis.contactserrorthis.error

// apexWireMethodToFunction.js
import { LightningElement, wire } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexWireMethodToFunction extends LightningElement {
  contacts;
  error;

  @wire(getContactList)
  wiredContacts({ error, data }) {
    if (data) {
      this.contacts = data;
      this.error = undefined;
    } else if (error) {
      this.error = error;
      this.contacts = undefined;
    }
  }
}

该模板使用该指令来检查 JavaScript 属性。如果它存在,它会循环访问它并呈现每个联系人的名称。如果该属性存在,则组件将呈现 .lwc:ifcontactserror<c-error-panel>

<!-- apexWireMethodToFunction.html -->
<template>
  <lightning-card title="ApexWireMethodToFunction" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts}>
        <template for:each={contacts} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={error}>
        <c-error-panel errors={error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

命令式调用 Apex 方法

若要控制方法调用的发生时间(例如,响应单击按钮),请以命令方式调用该方法。当您以命令方式调用某个方法时,您只收到一个响应。将此行为与 进行比较,后者将控制权委托给框架,并导致预配值流。@wire

在以下情况下,必须以命令方式调用 Apex 方法,而不是使用 .@wire

  • 调用未注释的方法,其中包括插入、更新或删除数据的任何方法。cacheable=true
  • 控制调用的发生时间。
  • 使用用户界面 API 不支持的对象,如 Task 和 Event。
  • 从不扩展的 ES6 模块调用方法LightningElement

如果 Apex 方法标记为 ,则在发出网络调用以调用服务器上的 Apex 方法之前,将检查客户端 Lightning 数据服务缓存。但是,Lightning 数据服务不管理 Apex 预置的数据。因此,要刷新过时的数据,请调用 Apex 方法,然后调用 notifyRecordUpdateAvailable(recordIds) 来更新 Lightning 数据服务缓存。@AuraEnabled(cacheable=true)

调用 Apex方法

让我们看一下 lwc-recipes 存储库中的一个示例组件,该组件使用与前面示例相同的类。当用户单击按钮时,组件不会连接它,而是调用 .getContactListgetContactList()

导入的函数返回一个 promise。此代码在给定一组参数的情况下提供一次性分辨率,而 @wire(apexMethod) 提供值流并支持动态参数。

// apexImperativeMethod.js
import { LightningElement, track } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexImperativeMethod extends LightningElement {
  @track contacts;
  @track error;

  handleLoad() {
    getContactList()
      .then((result) => {
        this.contacts = result;
      })
      .catch((error) => {
        this.error = error;
      });
  }
}
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != NULL
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该模板用于呈现联系人列表或错误面板。它还用于遍历联系人。lwc:iffor:each

<!-- apexImperativeMethod.html -->
<template>
  <lightning-card title="ApexImperativeMethod" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <p class="slds-m-bottom_small">
        <lightning-button label="Load Contacts" onclick={handleLoad}></lightning-button>
      </p>
      <template lwc:if={contacts}>
        <template for:each={contacts} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={error}>
        <c-error-panel errors={error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

使用参数调用 Apex 方法

将参数值传递给其属性与 Apex 方法的参数匹配的对象中的 Apex 方法。例如,如果 Apex 方法采用字符串参数,则不要直接传递字符串。相反,请传递一个对象,该对象包含值为字符串的属性。

// apexImperativeMethodWithParams.js

import { LightningElement } from "lwc";
import findContacts from "@salesforce/apex/ContactController.findContacts";

export default class ApexImperativeMethodWithParams extends LightningElement {
  searchKey = "";
  contacts;
  error;

  handleKeyChange(event) {
    this.searchKey = event.target.value;
  }

  handleSearch() {
    findContacts({ searchKey: this.searchKey })
      .then((result) => {
        this.contacts = result;
        this.error = undefined;
      })
      .catch((error) => {
        this.error = error;
        this.contacts = undefined;
      });
  }
}
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Name LIKE :key AND Picture__c != NULL
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

提示

lwc-recipes 存储库也有一个组件,用于调用带有参数的方法。若要调用具有对象参数的方法,请签出组件。apexImperativeMethodWithParamsapexImperativeMethodWithComplexParams

将值传递给 Apex

将记录数据等值从 LWC 传递到 Apex 时,请使用 JavaScript 对象或数组。映射值在传递给 Apex 方法时不会序列化。

命令式和有线 Apex 调用都不支持使用映射。映射(例如 with )的不当使用允许在禁用 LWS 的情况下传递数据。但是,启用 LWS 后,此用法不再起作用。此外,不支持将值传递给 Apex。map[key] = valmap.set(key, val)

//Don’t do this
Map map = new Map();
map.set(key, val);
apexMethod({map});

您可以使用这样的 JavaScript 对象。

import { LightningElement, wire } from "lwc";
import apexMethod from "@salesforce/apex/ReadValues.apexMethod";

export default class ApexValueExample extends LightningElement {
  objVal = {};

  val;

  async connectedCallback() {
    this.objVal["one"] = "two";
    this.val = await apexMethod({ theValues: this.objVal });
  }

  @wire(apexMethod, { theValues: "$objVal" })
  propertyOrFunction;
}

Apex 操作的批处理

框架在将 Apex 操作发送到服务器之前将其排队。在编写代码时,此机制在很大程度上对您是透明的,但它使框架能够通过将多个操作批处理到一个请求 (XHR) 中来最大程度地减少网络流量。

动作的批处理也称为棚车,类似于将棚车耦合在一起的火车。

该框架使用堆栈来跟踪要发送到服务器的 Apex 操作。当浏览器在客户端上处理完事件和 JavaScript 时,堆栈上的排队操作将批量发送到服务器。

Boxcar 请求中的操作限制

如果 boxcar 请求中的操作超过 2,500 个,则框架将返回 413 HTTP 响应状态代码。如果用户看到此罕见错误,请考虑重新设计自定义组件,以遵循最佳实践并减少请求中的操作数。

Apex 方法结果的客户端缓存

若要提高运行时性能,请使用 批注 Apex 方法,这会在客户端上缓存方法结果。要设置 ,方法只能获取数据,不能改变(更改)数据。@AuraEnabled(cacheable=true)cacheable=true

将方法标记为可缓存可快速显示客户端存储中的缓存数据,而无需等待服务器访问,从而提高了组件的性能。如果缓存的数据已过时,框架将从服务器检索最新数据。缓存对于连接延迟高、速度慢或不可靠的用户特别有益。可缓存方法的性能更好,因此请尽可能使用它们。

要用于调用 Apex 方法,必须设置 .@wirecacheable=true

要以命令方式调用 Apex 方法,您可以选择将 .cacheable=true

缓存刷新时间是在存储中刷新条目之前的持续时间(以秒为单位)。刷新时间在 Lightning Experience 和 Salesforce 移动应用程序中自动配置。

默认缓存持续时间可能会随着平台的优化而更改。在设计 Lightning Web 组件时,不要假设缓存持续时间。如果组件知道缓存的值无效(例如,当基础数据发生更改时),它可以向服务器查询更新的数据,并使用 from 刷新缓存。refreshApex()@salesforce/apex

提示

lwc-recipes 存储库中的组件调用 .ldsDeleteRecordrefreshApex()

强制调用方法时刷新缓存

要刷新过时的 Apex 数据,请调用 Apex 方法,然后调用 notifyRecordUpdateAvailable(recordIds) 以更新 Lightning 数据服务 (LDS) 缓存。Lightning 数据服务不管理由命令性 Apex 调用预置的数据。调用 Apex 方法后,调用以向 Lightning 数据服务发出某些记录已过时的信号,并刷新缓存中的这些记录。notifyRecordUpdateAvailable(recordIds)

import { LightningElement, wire } from 'lwc';
import { getRecord, notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';
import apexUpdateRecord from '@salesforce/apex/Controller.apexUpdateRecord';

export default class Example extends LightningElement {
    @api recordId;

    // Wire a record
    @wire(getRecord, { recordId: '$recordId', fields: ... })
    record;

    async handler() {
      // Do something before the record is updated
      showSpinner();

      // Update the record via Apex
      await apexUpdateRecord(this.recordId);

      // Notify LDS that you've changed the record outside its mechanisms
      // Await the Promise object returned by notifyRecordUpdateAvailable()
      await notifyRecordUpdateAvailable([{recordId: this.recordId}]);
      hideSpinner();
    }
}

使用@wire时刷新缓存

要刷新通过 Apex 置备的 Apex 数据,请调用 。该函数使用绑定到 的配置预配数据并更新缓存。@wirerefreshApex()@wire

注意

您刷新时使用的参数必须是以前由 Apex 发出的对象。refreshApex()@wire

有时,您知道缓存已过时。如果缓存已过时,则组件需要新数据。若要在服务器中查询更新的数据并刷新缓存,请导入并调用该函数。仅在必要时调用,因为它会导致查询服务器的网络行程。refreshApex()refreshApex()

该函数返回一个 Promise。解析 Promise 后,线路中的数据是最新的。Apex 方法的返回值仅在 上可用。当 Promise 解析时,有新数据,但 Promise 解析到的实际值是没有意义的。使用块处理刷新的数据,例如在页面上设置属性。refreshApex()@wire@wirethen()

import { refreshApex } from "@salesforce/apex";
refreshApex(valueProvisionedByApexWireService);
  • 其中 是用 Apex 注释的属性,或者如果注释了函数,则为有线函数接收的参数。valueProvisionedByApexWireService@wire// Example of refreshing data for a wired property // after you update data via an LDS-managed module (lightning/uiRecordApi). import { updateRecord } from 'lightning/uiRecordApi'; import { refreshApex } from '@salesforce/apex'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record using updateRecord(recordInput) // Refresh Apex data that the wire service provisioned handler() { updateRecord(recordInput).then(() => { refreshApex(this.opptiesOverAmount); }); }// Example of refreshing data for a wired property // after you update data via imperative Apex. import { refreshApex } from '@salesforce/apex'; import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record in Apex, such as via a button click // Refresh Apex data that the wire service provisioned handler() { updateRecordApexMethod() .then(() => { refreshApex(this.opptiesOverAmount); notifyRecordUpdateAvailable(recordIds); // Refresh the Lightning Data Service cache }); }//Example of refreshing data for a wired function import { refreshApex } from '@salesforce/apex'; import getActivityHistory from '@salesforce/apex/ActivityController.getActivityHistory; @wire(getActivityHistory, { accountId: '$recordId', max: '500' }) wiredGetActivityHistory(value) { // Hold on to the provisioned value so we can refresh it later. this.wiredActivities = value; // Destructure the provisioned value const { data, error } = value; if (data) { ... } else if (error) { ... } ... } handler() { refreshApex(this.wiredActivities); }

示例:刷新有线属性的缓存

让我们看一个例子。此组件显示超过指定数量的商机列表。用户可以单击以将所有商机标记为“已赢得”。当机会更新时,缓存数据将变得过时。因此,在代码更新商机后,它会调用以查询服务器以获取更新的数据并刷新缓存。refreshApex()

<!-- opportunitiesOverAmount.html -->
<template>
  <!-- Display a list of opportunities -->
  <p>
    List of opportunities over
    <lightning-formatted-number
      value={amount}
      format-style="currency"
    ></lightning-formatted-number>
  </p>
  <template lwc:if={opptiesOverAmount.data}>
    <template for:each={opptiesOverAmount.data} for:item="oppty" for:index="idx">
      <div key={oppty.Id} class="slds-m-bottom_medium">
        <p>{idx}. {oppty.Name}</p>
        <p>
          <lightning-formatted-number
            value={oppty.Amount}
            format-style="currency"
          ></lightning-formatted-number>
        </p>
        <p>
          <lightning-formatted-date-time value={oppty.CloseDate}></lightning-formatted-date-time>
        </p>
        <p><lightning-badge label={oppty.StageName}></lightning-badge></p>
      </div>
    </template>
    <!-- Click the button to change the opportunities. Requires the data to be refetched
             and rerendered -->
    <lightning-button label="Mark all as Closed Won" onclick={handleClick}></lightning-button>
  </template>
</template>

让我们看一下组件的 JavaScript 代码。首先,它从 导入 Apex 方法。<c-opportunities-over-amount>refreshApex@salesforce/apex

然后,它使用动态值 调用 Apex 方法。每当发生更改时,都会重新请求数据。系统从客户端缓存或服务器提供数据。getOpptyOverAmountamountthis.amount

// opportunitiesOverAmount.js
import { LightningElement, api, wire } from "lwc";
import { refreshApex } from "@salesforce/apex";
import getOpptyOverAmount from "@salesforce/apex/OpptiesOverAmountApex.getOpptyOverAmount";
import updateOpptyStage from "@salesforce/apex/OpptiesOverAmountApex.updateOpptyStage";

export default class OpportunitiesOverAmount extends LightningElement {
  @api amount = 500000;

  @wire(getOpptyOverAmount, { amount: "$amount" })
  opptiesOverAmount;

  handleClick(e) {
    updateOpptyStage({
      amount: this.amount,
      stage: "Closed Won",
    })
      .then(() => {
        refreshApex(this.opptiesOverAmount).then(() => {
          // do something with the refreshed data in this.opptiesOverAmount
        });
      })
      .catch((error) => {
        this.message =
          "Error received: code" + error.errorCode + ", " + "message " + error.body.message;
      });
  }
}

代码在方法中调用。将 Closed Won 更改为 Closed Won 会影响字段,这意味着数据已过时。当您将来有关闭日期时,“关闭日期”字段将更改为今天的日期。refreshApex()handleClickstageCloseDate@wire

要告知系统数据已过时,请调用 。有线适配器将其缓存标记为过时,重新查询服务器以获取更新的数据,更新其缓存,然后通知其订阅者。发生这种情况时,组件的属性会更新,从而触发使用新数据重新呈现。收盘日期现在是(今天的日期),机会阶段现在是“Closed Won”。refreshApex()this.opptiesOverAmountJuly 18, 2019

该类包含该方法,该方法检索超过指定数量的机会列表。它还包含该方法,该方法使用 DML 操作将超过指定金额的商机阶段更新为“Closed Won”。OpptiesOverAmountApexgetOpptyOverAmountupdateOpptyStageupdate

public with sharing class OpptiesOverAmountApex {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpptyOverAmount(Decimal amount) {
        return [SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount];
    }

    @AuraEnabled
    public static void updateOpptyStage(Decimal amount, String stage) {
        for (List<Opportunity> oppts:
            [SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount]) {
                for(Opportunity o : oppts) {
                    o.StageName = stage;
                }
                update oppts;
            }
            return;
    }
}

示例:刷新有线函数的缓存

要刷新有线函数,请将有线函数接收的参数(即有线值)传递给 。在此示例代码中,有线函数为 。保留线路服务提供的值并将其传递给 。refreshApex()wiredGetActivityHistory(value)refreshApex()

使用新函数进行命令式调用。要跟踪已置备的值,请定义一个新属性,并在 中使用它。解构预置的值以轻松提取 和 对象。refreshApex()wiredActivitieswiredGetActivityHistory(value)dataerror

import { LightningElement, api, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getActivityHistory from '@salesforce/apex/GalleryApexController.getActivityHistory';

export default class GalleryApexMaster extends LightningElement {
    @api recordId;
    wiredActivities;

    @wire(getActivityHistory, { accountId: '$recordId', max: '500' })
    wiredGetActivityHistory(value) {
        // Hold on to the provisioned value so we can refresh it later.
        this.wiredActivities = value; // track the provisioned value
        const { data, error } = value; // destructure the provisioned value
        if (data) { ... }
        else if (error) { ... }
        ...
    }

    handleLogACall() {
        // Use the value to refresh wiredGetActivityHistory().
        return refreshApex(this.wiredActivities);
    }
}

从中导入对象和字段@salesforce/schema

如果您导入对对象和字段的引用,Salesforce 会验证对象和字段是否存在(这也会捕获拼写错误),防止删除对象和字段,并将任何重命名的对象和字段级联到组件的源代码中。它还确保依赖对象和字段包含在更改集和包中。导入对对象和字段的引用可确保代码正常工作,即使对象和字段名称发生更改也是如此。

如果组件通过 Apex 类获取对象并从 导入对对象的引用,则调用以从对象中获取字段值。@salesforce/schemagetSObjectValue()

getSObjectValue(sobject, fieldApiName);
  • {sobject}– Apex 方法返回的对象。
  • {fieldApiName}– 字段的 API 名称。该值可以是字符串,也可以是对从 导入的字段的引用。您最多可以指定 5 个级别的生成字段。例如,返回 4 级生成字段。@salesforce/schemaOpportunity.Account.CreatedBy.LastModifiedById

让我们看一下 lwc-recipes 中的组件。该组件从返回单个联系人的 Apex 方法获取其数据。apexStaticSchema

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static Contact getSingleContact(){
        return [SELECT Id, Name, Title, Phone, Email, Picture__c FROM Contact LIMIT 1];
    }

}

JavaScript 从 导入函数和方法。它从 导入对联系人字段的引用。getSObjectValuegetSingleContact@salesforce/apex@salesforce/schema

Apex 方法已连接到属性。模板中的 、 和 属性具有 getter,这些 getter 调用以从指定字段中提取值。contactnametitleemailgetSObjectValue()

// apexStaticSchema.js
import { LightningElement, wire } from "lwc";
import { getSObjectValue } from "@salesforce/apex";
import getSingleContact from "@salesforce/apex/ContactController.getSingleContact";

import NAME_FIELD from "@salesforce/schema/Contact.Name";
import TITLE_FIELD from "@salesforce/schema/Contact.Title";
import EMAIL_FIELD from "@salesforce/schema/Contact.Email";

export default class ApexStaticSchema extends LightningElement {
  @wire(getSingleContact) contact;

  get name() {
    return this.contact.data ? getSObjectValue(this.contact.data, NAME_FIELD) : "";
  }
  get title() {
    return this.contact.data ? getSObjectValue(this.contact.data, TITLE_FIELD) : "";
  }
  get email() {
    return this.contact.data ? getSObjectValue(this.contact.data, EMAIL_FIELD) : "";
  }
}
<!-- apexStaticSchema.html -->
<template>
  <lightning-card title="ApexStaticSchema" icon-name="custom:custom63">
    <template lwc:if={contact.data}>
      <div class="slds-m-around_medium">
        <p>{name}</p>
        <p>{title}</p>
        <p><lightning-formatted-email value={email}></lightning-formatted-email></p>
      </div>
    </template>
    <template lwc:elseif={contact.error}>
      <c-error-panel errors={contact.error}></c-error-panel>
    </template>
  </lightning-card>
</template>

使用延续进行长时间的标注

使用 Apex 中的类向外部 Web 服务发出长时间运行的请求。在回调方法中处理响应。延续是管理宣传信息的首选方式,因为它们可以大幅改善用户体验。Continuation

使用延续有一些优点,包括能够并行进行标注。

框架在将操作发送到服务器之前对操作进行排队。在编写代码时,此机制在很大程度上对您是透明的,但它使框架能够通过将多个操作批处理到一个请求 (XHR) 中来最大程度地减少网络流量。动作的批处理也称为棚车,类似于将棚车耦合在一起的火车。由于延续可以是长时间运行的请求,因此该框架实质上将延续视为后台操作。延续不会与其他请求绑定,因此它们在运行时不会阻止其他操作。

使用延续发出的异步标注不计入持续时间超过 5 秒的同步请求的 Apex 限制。自 20 年冬季以来,所有标注都被排除在长期运行的请求限制之外,因此与常规标注相比,延续不再为使用限制提供优势。但是,由于改善了用户体验,我们建议使用延续来管理宣传信息。

在 Apex 类中使用延续

若要使用 Apex 类中的延续,请使用 Apex 对象。Continuation

先决条件

在调用外部服务之前,您必须将远程站点添加到 Salesforce 用户界面中的授权远程站点列表中。在“设置”的“快速查找”框中,输入 。选择远程站点设置”,然后单击“新建远程站点”。添加与下面的 Apex 类延续示例中对应的标注 URL。Remote Site SettingsLONG_RUNNING_SERVICE_URL

注意

如果标注将命名凭据指定为终结点,则无需配置远程站点设置。命名凭据在一个定义中指定标注终结点的 URL 及其所需的身份验证参数。在代码中,指定命名凭据 URL,而不是长时间运行的服务 URL。

使用 Continuation 对象

  1. 若要进行长时间运行的标注,请定义一个返回对象的 Apex 方法。(先不用担心注解的属性。我们很快就会解释。Continuation@AuraEnabled@AuraEnabled(continuation=true cacheable=true) public static Object startRequest() { // Create continuation. Argument is timeout in seconds. Continuation con = new Continuation(40); // more to come here return con; }
  2. 在对象的属性中设置标注完成后要调用的 Apex 回调方法。在此示例中,回调方法为 。回调方法必须位于同一 Apex 类中。continuationMethodContinuationprocessResponsecon.continuationMethod='processResponse';
  3. 通过向标注对象添加对象来设置标注的端点。单个对象最多可以包含三个注解。每个标注都必须在安装程序中定义一个远程站点或命名凭据。HttpRequestContinuationContinuationHttpRequest req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(LONG_RUNNING_SERVICE_URL); con.addHttpRequest(req);
  4. 在对象的属性中设置要传递给回调方法的数据。该属性具有类型,因此可以传入 Apex 中支持的任何数据类型。stateContinuationstateObjectcon.state='Hello, World!';
  5. 在 Apex 回调中编写逻辑代码。当对象中设置的所有标注都完成后,将调用 Apex 回调方法 。回调方法有两个可以访问的参数。ContinuationprocessResponsepublic static Object processResponse(List<String> labels, Object state)
    1. labels– 标签列表,对应于延续中的每个请求。这些标签是自动创建的。
    2. state– 在对象的属性中设置的状态。stateContinuation
  6. 获取延续中每个请求的响应。例如:HttpResponse response = Continuation.getResponse(labels[0]);
  7. 将结果返回到组件的 JavaScript 文件。

示例:具有延续的 Apex 类

下面是一个完整的 Apex 类,它将所有前面的步骤联系在一起。

public with sharing class SampleContinuationClass {
    // Callout endpoint as a named credential URL
    // or, as shown here, as the long-running service URL
    private static final String LONG_RUNNING_SERVICE_URL =
        '<insert your callout URL here>';

    // Action method
    @AuraEnabled(continuation=true cacheable=true)
    public static Object startRequest() {
      // Create continuation. Argument is timeout in seconds.
      Continuation con = new Continuation(40);
      // Set callback method
      con.continuationMethod='processResponse';
      // Set state
      con.state='Hello, World!';
      // Create callout request
      HttpRequest req = new HttpRequest();
      req.setMethod('GET');
      req.setEndpoint(LONG_RUNNING_SERVICE_URL);
      // Add callout request to continuation
      con.addHttpRequest(req);
      // Return the continuation
      return con;
    }

    // Callback method
    @AuraEnabled(cacheable=true)
    public static Object processResponse(List<String> labels, Object state) {
      // Get the response by using the unique label
      HttpResponse response = Continuation.getResponse(labels[0]);
      // Set the result variable
      String result = response.getBody();
      return result;
    }
}

@AuraEnabled延续的注释

延续使用 Apex 代码的注释。以下是使用规则。@AuraEnabled

  • @AuraEnabled(continuation=true)返回延续的 Apex 控制器方法必须用 注释。@AuraEnabled(continuation=true)
  • @AuraEnabled(continuation=true cacheable=true)若要缓存延续操作的结果,请在 Apex 回调方法的注释上设置。cacheable=true注意之间有一个空格,而不是逗号continuation=true cacheable=true

缓存注意事项

最佳做法是设置延续链中涉及的所有方法,包括返回对象的方法。cacheable=trueContinuation

在此示例中,返回延续 的 Apex 方法和回调 ,都包含在其批注中。startRequest()processResponse()cacheable=true@AuraEnabled

// Action method
@AuraEnabled(continuation=true cacheable=true)
public static Object startRequest() { }

// Callback method
@AuraEnabled(cacheable=true)
public static Object processResponse(List<String> labels,
  Object state) { }

可缓存设置行为

下表总结了 中属性的不同设置的行为。cacheable@AuraEnabled

返回 Continuation 对象的方法 注释cacheable=true回调方法注解cacheable=true有效?可以使用 ?@wire操作响应是否缓存在客户端上?
是的是的是的是的是的
是的否(引发异常)不适用不适用
是的是的否(所有方法都必须具有cacheable=true)
是的

延续示例

下面是一个组件的标记,该组件在单击按钮后调用线路服务的延续和命令。

<!-- continuationCmp.html -->
<template>
  <lightning-button label="Call Continuation" onclick={callContinuation}> </lightning-button>
  <div>@wire(startRequest) result: {formattedWireResult}</div>
  <div>Imperative result: {formattedImperativeResult}</div>
</template>

HTML 包含一个按钮,用于触发对延续的命令性调用。当我们接下来查看 JavaScript 文件时,我们会看到更多。

下面是组件的 JavaScript 文件。

import { LightningElement, track, wire } from "lwc";
import startRequest from "@salesforce/apexContinuation/SampleContinuationClass.startRequest";
export default class ContinuationComponent extends LightningElement {
  @track imperativeContinuation = {};

  // Using wire service
  @wire(startRequest)
  wiredContinuation;

  get formattedWireResult() {
    return JSON.stringify(this.wiredContinuation);
  }

  // Imperative Call
  callContinuation() {
    startRequest()
      .then((result) => {
        this.imperativeContinuation = result;
      })
      .catch((error) => {
        this.imperativeContinuation = error;
      });
  }

  get formattedImperativeResult() {
    return JSON.stringify(this.imperativeContinuation);
  }
}

此代码使用新的导入语法。

import startRequest from "@salesforce/apexContinuation/SampleContinuationClass.startRequest";

此 import 语句告诉框架,我们正在调用一个可以返回延续的 Apex 方法。如果 Apex 方法返回一个对象,则必须使用 批注该方法。startRequestContinuation@AuraEnabled(continuation=true)

该组件使用线路服务调用 Apex 方法。线路服务将控制流委托给 Lightning Web 组件引擎。在执行延续后,当电缆适配器中的数据变得可用时,模板将显示结果。您可以使用线路服务,因为在 Apex 类中进行了注释。startRequeststartRequest()@AuraEnabled(cacheable=true)

由于延续会发出长时间运行的请求,因此您可能更愿意通过发出命令性调用来控制延续执行的开始时间,而不是使用线路服务。此组件使用有线服务和命令性调用。有线服务调用延续并缓存结果。当您单击该按钮对同一 Apex 方法进行命令性调用时,缓存的结果将返回并显示在模板中。

延续限制

由于延续可能导致多个长时间运行的操作,因此它们的使用存在一些限制。

《Apex 开发人员指南》中列出了在 Apex 中使用延续的限制。

以下是一些特定于 Lightning Web 组件使用的限制。每个延续最多 3 个标注

单个对象最多可以包含三个注解。Continuation连续操作的串行处理

该框架从客户端串行处理包含延续的操作。在进行下一次延续调用之前,必须完成上一次延续调用。在任何时候,客户端上只能有一个正在进行的延续。DML 操作限制

返回对象的 Apex 方法不能执行任何数据操作语言 (DML) 操作。DML 语句在 Salesforce 中插入、更新、合并、删除和恢复数据。如果在延续方法中执行了任何 DML,则延续执行不会继续,事务将回滚,并返回错误。Continuation

您可以在 Apex 回调方法中执行 DML 操作以延续。

保护 Apex 类

默认情况下,Apex 在系统模式下运行,这意味着它以大幅提升的权限运行,就像用户拥有大多数权限以及授予的所有字段和对象级访问权限一样。由于这些安全层不像在 Salesforce UI 中那样强制执行,因此您必须编写代码来强制执行它们。否则,您的组件可能会无意中暴露敏感数据,而这些数据通常会在 Salesforce UI 中对用户隐藏。

注意

要使用 Salesforce 记录,我们建议使用 Lightning 数据服务,它为您处理共享规则、CRUD 和字段级安全性。请参阅数据指南。

授予用户对 Apex 类的访问权限

仅当用户的配置文件或分配的权限集允许访问 Apex 类时,经过身份验证的用户或来宾用户才能访问 Apex 方法。@AuraEnabled

有关配置用户配置文件或权限集对 Apex 类的访问的详细信息,请参阅《Apex 开发人员指南》中的类安全性。

强制执行共享规则

声明类时,最佳做法是指定在组件使用 Apex 控制器时强制执行共享规则。with sharing

public with sharing class SharingClass {
    // Code here
}

未显式设置 或 定义的 Apex 类,或者使用 定义的 Apex 类使用默认值或隐式值。但是,Apex 类不会显式设置或继承其运行上下文中的值。因此,当设置其中一个关键字的类调用没有显式共享行为的类时,它将与调用类的共享行为一起操作。要确保您的班级强制执行共享规则,请将 .@AuraEnabledwith sharingwithout sharinginherited sharingwith sharingwith sharingwithout sharingwith sharing

关键字强制执行记录级安全性。它不强制实施对象级和字段级安全性。您必须在 Apex 类中分别手动强制实施对象级和字段级安全性。with sharing

强制执行对象和字段权限(CRUD 和 FLS)

有几种替代方法可以在 Apex 代码中强制执行对象级和字段级权限。最简单的强制执行方式WITH SECURITY_ENFORCED

若要强制执行对象级和字段级权限,请在 Apex 代码中将该子句用于查询,包括子查询和跨对象关系。WITH SECURITY_ENFORCEDSOQL SELECT

如果你在开发安全代码方面经验最少,并且对于不需要对权限错误进行正常降级的应用程序,则该子句是理想的选择。WITH SECURITY_ENFORCED

此示例使用不安全的方法 查询自定义支出对象上的字段。不要使用这个类!get_UNSAFE_Expenses()

// This class is an anti-pattern.
public with sharing class UnsafeExpenseController {
    // ns refers to namespace; leave out ns__ if not needed
    // This method is vulnerable because it doesn't enforce FLS.
    @AuraEnabled
    public static List<ns__Expense__c> get_UNSAFE_Expenses() {
        return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
    }
}

下一个示例使用安全方法 ,该方法使用子句强制执行对象级和字段级权限。使用此类代替 .getExpenses()WITH SECURITY_ENFORCEDUnsafeExpenseController

public with sharing class ExpenseController {
    // This method is recommended because it enforces FLS.
    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
    // Query the object safely
    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate
             FROM ns__Expense__c WITH SECURITY_ENFORCED];
    }
}

有关详细信息,请参阅 Apex 开发人员指南。优雅降解stripInaccessible()

若要更优雅地降级权限错误,请使用该方法强制实施字段级和对象级数据保护。此方法从用户无法访问的查询和子查询结果中删除字段和关系字段。如果需要,您可以查看是否有任何字段被剥离,并抛出带有自定义错误消息的字段。stripInaccessible()AuraHandledException

您还可以使用该方法在 DML 操作之前删除无法访问的 sObject 字段,以避免异常并清理已从不受信任的源反序列化的 sObject。

此示例将更新为使用 SOQL 子句。结果是相同的,但让您有机会在使用 时正常降级,而不是因访问冲突而失败。ExpenseControllerstripInaccessible()WITH SECURITY_ENFORCEDstripInaccessible()WITH SECURITY_ENFORCED

public with sharing class ExpenseControllerStripped {

    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
        // Query the object but don't use WITH SECURITY_ENFORCED
        List<ns__Expense__c> expenses =
            [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
                ns__Reimbursed__c, CreatedDate
                 FROM ns__Expense__c];

         // Strip fields that are not readable
         SObjectAccessDecision decision = Security.stripInaccessible(
               AccessType.READABLE,
               expenses);

         // Throw an exception if any data was stripped
         if (!decision.getModifiedIndexes().isEmpty()) {
            throw new AuraHandledException('Data was stripped');
         }

         return expenses;
    }
}

有关更多详细信息和示例,请参阅 Apex 开发人员指南。遗留代码 using 和 methodsDescribeSObjectResultDescribeFieldResult

在子句和方法可用之前,强制执行对象和字段权限的唯一方法是通过调用 and 方法来检查当前用户的访问权限级别。然后,如果用户具有必要的权限,则执行特定的 DML 操作或查询。WITH SECURITY_ENFORCEDstripInaccessible()Schema.DescribeSObjectResultSchema.DescribeFieldResult

例如,您可以调用 、 或 方法分别验证当前用户是否具有对 的读取、创建或更新访问权限。同样,公开访问控制方法,您可以调用这些方法来检查当前用户对字段的读取、创建或更新访问权限。isAccessibleisCreateableisUpdateableSchema.DescribeSObjectResultsObjectSchema.DescribeFieldResult

此示例使用 describe result 方法。此方法需要更多的样板代码行,因此我们建议改用子句或方法。WITH SECURITY_ENFORCEDstripInaccessible()

public with sharing class ExpenseControllerLegacy {
    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
        String [] expenseAccessFields = new String [] {'Id',
                                                       'Name',
                                                       'ns__Amount__c',
                                                       'ns__Client__c',
                                                       'ns__Date__c',
                                                       'ns__Reimbursed__c',
                                                       'CreatedDate'
                                                       };


    // Obtain the field name/token map for the Expense object
    Map<String,Schema.SObjectField> m = Schema.SObjectType.ns__Expense__c.fields.getMap();

    for (String fieldToCheck : expenseAccessFields) {

        // Call getDescribe to check if the user has access to view field
        if (!m.get(fieldToCheck).getDescribe().isAccessible()) {

            // Pass error to client
            throw new System.NoAccessException();
        }
    }

    // Query the object safely
    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
    }
}

使用Wire服务获取数据

为了读取 Salesforce 数据,Lightning Web 组件使用反应式连线服务。在组件的 JavaScript 类中用于指定 Lightning 数据服务线路适配器。线路适配器定义线路服务在不可变流中预配的数据形状。@wire

LDS 电线适配器基于用户界面 API 资源构建,并位于模块中。每个线路适配器都提供不同的 Salesforce 数据和元数据,从单个记录和记录列表到对象和布局架构。lightning/ui*Api

注意

在使用电线适配器之前,请确保没有更简单的方法来获取数据。要使用基于表单的界面查看、编辑或创建记录,请考虑 lightning-record*form 组件。

为了提高性能,请始终使用返回用例所需的最少数据量的线路适配器。例如,返回记录数据,同时返回对象及其字段的元数据。要查看电线适配器返回的数据,请参阅 lightning/ui*Api 电线适配器和功能中每个电线适配器的“返回”部分。getRecordgetObjectInfo

了解Wire服务

线路服务为组件提供不可变的数据流。流中的每个值都是其前面值的较新版本。

重要

传递给组件的对象是只读的。若要更改数据,组件应创建要更改的对象的浅拷贝。在处理数据时,了解这个概念非常重要。请参阅数据流。

我们称 wire service 为响应式,部分原因是它支持以 .如果反应变量发生更改,则线路服务会预配新数据。我们说“provisions”而不是“requests”或“fetches”,因为如果数据存在于客户端缓存中,则可能不涉及网络请求。$

提示

线路服务将控制流委托给 Lightning Web 组件引擎。委派控制非常适合读取操作,但不适用于创建、更新和删除操作。作为开发人员,您希望完全控制更改数据的操作。因此,您使用 JavaScript API 而不是 Wire 服务执行创建、更新和删除操作。

线路服务语法

使用命名导入语法导入电线适配器。用并指定电线适配器来装饰属性或函数。每个电线适配器都定义一个数据类型。@wire

有线服务提供不可变的数据流。即使 adapterConfig 对象内容相同,也不能保证数据相同。

// Syntax
import { adapterId } from 'adapterModule';
@wire(adapterId, adapterConfig)
propertyOrFunction;
  • adapterId(标识符)— 电线适配器的标识符。
  • adapterModule(String) – 包含有线适配器功能的模块的标识符,格式为 。看看格式!要在 JavaScript 中导入模块,请使用 代替 .namespace/moduleNamelightning/ui*Apilightning-ui-*-api
  • adapterConfig(对象) – 特定于电线适配器的配置对象。配置对象属性值可以是字符串,也可以是对从 导入的对象和字段的引用。对象中的属性不能未定义。如果属性未定义,则线路服务不会预配数据。不要更新电线适配器配置对象属性,因为它可能会导致无限循环。@salesforce/schema{adapterConfig}renderedCallback()
  • propertyOrFunction– 从线路服务接收数据流的私有属性或函数。如果属性被修饰为 ,则结果将返回到该属性的属性或属性。如果函数用 修饰,则结果将在具有属性和属性的对象中返回。@wiredataerror@wiredataerror注意属性和属性是 API 中的硬编码值。您必须使用这些值。dataerror

导入对 Salesforce 对象和字段的引用

当您在 lightning/ui*Api 模块中使用电线适配器时,我们强烈建议导入对对象和字段的引用。Salesforce 会验证对象和字段是否存在,防止删除对象和字段,并将任何重命名的对象和字段级联到组件的源代码中。它还确保依赖对象和字段包含在更改集和包中。导入对对象和字段的引用可确保代码正常工作,即使对象和字段名称发生更改也是如此。

提示

若要遵循一些代码示例,请参阅 lwc-recipes 存储库。查找名称以 开头的组件。wire

如果组件不知道它正在使用哪个对象,请使用字符串而不是导入的引用。使用 getObjectInfo 返回对象的字段。lightning/ui*Api 模块中的所有电线适配器都遵循对象 CRUD 规则、字段级安全性和共享。如果用户无权访问某个字段,则该字段不会包含在响应中。

注意

目前,名称更改在大约两个小时内不会完全级联到源代码中。对于 App Builder 中使用的组件,如果组件的 meta.xml 文件用于约束对象主目录或记录主目录,则此计时也很重要。<objects>

要访问对象和字段 API 名称,请使用语句。所有对象和字段导入都来自作用域包。import@salesforce/schema

若要导入对对象的引用,请使用以下语法。

import objectName from "@salesforce/schema/object";
import objectName from "@salesforce/schema/namespace__object";

import POSITION_OBJECT from "@salesforce/schema/Position__c";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";

若要导入对字段的引用,请使用以下语法。

import FIELD_NAME from "@salesforce/schema/object.field";

import POSITION_LEVEL_FIELD from "@salesforce/schema/Position__c.Level__c";
import ACCOUNT_NAME_FIELD from "@salesforce/schema/Account.Name";

若要通过关系导入对字段的引用,请使用以下语法。您可以使用关系字段遍历到父对象和字段。您最多可以指定三个关系字段,这将导致四个对象和被引用的字段。例如,返回 4 级生成字段。Opportunity.Account.CreatedBy.LastModifiedById

import SPANNING_FIELD_NAME from "@salesforce/schema/OBJECT.RELATIONSHIP.FIELD";

import POSITION_HIRINGMANAGER_NAME_FIELD__c from "@salesforce/schema/Position__c.HiringManager__r.Name__c";
import ACCOUNT_OWNER_NAME_FIELD from "@salesforce/schema/Account.Owner.Name";

提示

对于对象,我们使用 命名约定 。对于字段,我们使用 命名约定 。我们使用这些命名约定来使代码更易于理解。它们是指导方针,而不是规则。OBJECTNAME_OBJECTFIELDNAME_FIELD

此代码导入字段,并在电线适配器的配置对象中使用它。Account.Name

import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";
import ACCOUNT_NAME_FIELD from "@salesforce/schema/Account.Name";

export default class Record extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields: [ACCOUNT_NAME_FIELD] })
  record;
}

此代码几乎完全相同,但它使用字符串来标识字段。此代码无法获得导入对字段的引用所获得的好处。Account.Name

import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

export default class Record extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields: ["Account.Name"] })
  record;
}

导入复合字段的引用

名称字段、地址字段和地理位置字段是复合字段。复合字段可作为单个结构化字段或单个组成字段进行访问。复合字段中包含的值和构成字段中的值都映射到 Salesforce 中存储的相同数据。

在读取操作中,可以导入对复合字段或其构成字段的引用。例如,在读取操作中,可以使用 ,这是一个复合字段。Contact.Name

import NAME_FIELD from "@salesforce/schema/Contact.Name";

对于使用 updateRecord(recordInput, clientOptions) 等函数对复合字段进行创建和更新操作,必须导入构成字段。例如,import 和 而不是 .请记住包括所有必需的组成字段。例如,要创建联系人,该字段是必填字段。lightning/ui*ApiContact.FirstNameContact.LastNameContact.NameLastName

import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";
import LASTNAME_FIELD from "@salesforce/schema/Contact.LastName";

复合地址字段通过其组成字段得到支持。若要访问地址字段,请将其构成字段与字符串语法一起使用。

const STREET_FIELD = "Contact.MailingStreet";
const CITY_FIELD = "Contact.MailingCity";

复合地理位置字段通过其组成字段得到支持。要访问地理位置字段,请将其构成字段与字符串语法一起使用。

// User.Place_of_birth__c is a compound custom field of type Geolocation
const PLACE_OF_BIRTH_LAT_FIELD = "User.Place_of_birth__Latitude__s";
const PLACE_OF_BIRTH_LON_FIELD = "User.Place_of_birth__Longitude__s";

提示

使用 Lightning Web Components Trailhead 构建熊跟踪应用程序项目使用复合地理位置字段,并将纬度和经度数据转换为地图标记。

导入限制

Salesforce 支持许多对象或字段后缀来表示不同类型的数据。Lightning Web 组件支持导入对标准对象的引用,以及仅支持导入对自定义对象的引用 ()。例如:__c

import POSITION_OBJECT from "@salesforce/schema/Position__c";

请考虑以下解决方法,以导入对其他对象或字段后缀的引用。个人帐户

字段表示个人帐户,该帐户通过将某些客户和联系人字段组合到单个记录中来存储有关个人的信息。在 Contact 对象中创建的每个自定义字段都可用于使用后缀的 Account 对象中的个人帐户,但此语法在语句中不起作用。__pc__pcimport

不要从 Account 对象导入字段,而是从 Contact 对象导入自定义字段,以获得引用完整性的好处。__pc

import PA_CONTACT_CUSTOM_FIELD from "@salesforce/schema/Contact.myCustomField__c";

稍后在 JavaScript 文件中,您可以直接访问引用 .例如:Account.myCustomField__pcContact.myCustomField__c

paField = "Account.myCustomField__pc";

外部对象

后缀表示外部自定义对象,该对象类似于自定义对象,不同之处在于它映射到存储在 Salesforce 组织外部的数据。__x

若要从外部自定义对象检索数据,请调用 Apex 类中的方法,该方法使用 SOQL 查询获取数据。

将配置对象属性标记为动态和反应式

在电线适配器的配置对象中,在值前面加上前缀以引用组件实例的属性。前缀告诉线路服务将其视为类的属性,并将其计算为 。该属性是反应式的。如果属性的值发生更改,则会预配新数据并重新呈现组件。$$this.propertyName

在配置对象中使用顶级值的前缀。将前缀(例如嵌套在数组中)使其成为文本字符串,它不是动态的或响应式的。$$['$accountIds']

在此示例中,是动态的和反应式的。$recordId

import { LightningElement, api, wire } from "lwc";
import { getRecord, getFieldValue } from "lightning/uiRecordApi";

import REVENUE_FIELD from "@salesforce/schema/Account.AnnualRevenue";

const fields = [REVENUE_FIELD];

export default class WireGetValue extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields })
  account;

  get revenue() {
    return getFieldValue(this.account.data, REVENUE_FIELD);
  }
}

不带 的值(如上一示例中的属性值)是静态值 。$fields[AccountNameField]

您可以将这些类型的配置对象属性指定为动态属性和反应式属性。

  • 私有财产
  • 由 getter-setter 对定义的属性
  • 装饰有@api

用@wire装饰房产

当您想要按原样使用数据或错误时,连接属性非常有用。

如果修饰的属性用作模板中的属性,并且其值发生更改,则线路服务将预配数据并触发组件重新呈现。该物业是私人的,但具有反应性。@wire

此代码适用于该物业。@wirerecord

import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";
import ACCOUNT_NAME_FIELD from "@salesforce/schema/Account.Name";

export default class Record extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields: [ACCOUNT_NAME_FIELD] })
  record;
}

在组件构造之后和任何其他生命周期事件之前,将为该属性分配默认值。

缺省值是 和 属性为 的对象。dataerrorundefined

{ data: undefined, error: undefined }

因此,可以在任何函数中访问属性的值,包括模板使用的函数或用作组件生命周期一部分的函数。

提供给属性的对象(在此示例中为 )具有此形状。record

  • data(任何类型)- 适配器提供的值。
  • error(错误)- 如果适配器无法提供请求的数据或找不到适配器,则会出现错误。否则,此属性为 。undefined

当数据从电线适配器中可用时,它会在属性 ( remains ) 中设置。当较新版本的数据可用时,将进行更新。dataerrorundefineddata

如果适配器中发生错误,例如在检索数据时,则会填充错误对象(设置为 )。errordataundefined

您可以将一个输出用作另一个输入。例如,您可以用作另一个电线适配器的输入。@wire@wire$record.data.fieldName

用@wire装饰函数

连接函数对于在提供新数据或发生错误时执行逻辑非常有用。有线服务为函数提供具有 和 属性的对象,就像有线属性一样。errordata

每当值可用时,都会调用该函数,该值可以在连接或呈现组件之前或之后。

// wireFunction.js
import { LightningElement, api, track, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

export default class WireFunction extends LightningElement {
  @api recordId;
  @track record;
  @track error;

  @wire(getRecord, { recordId: "$recordId", fields: ["Account.Name"] })
  wiredAccount({ error, data }) {
    if (data) {
      this.record = data;
      this.error = undefined;
    } else if (error) {
      this.error = error;
      this.record = undefined;
    }
  }
  get name() {
    return this.record.fields.Name.value;
  }
}
<!-- wireFunction.html -->
<template>
  <lightning-card title="Wire Function" icon-name="standard:contact">
    <template lwc:if={record}>
      <div class="slds-m-around_medium">
        <p>{name}</p>
      </div>
    </template>
    <template lwc:if={error}>
      <c-error-panel errors={error}></c-error-panel>
    </template>
  </lightning-card>
</template>

获取记录数据

让我们使用线路服务来获取记录数据并显示一些字段名称。

提示

此示例使用 github.com/trailheadapps/lwc-recipes 存储库中的组件。此组件使用动态架构,它不会导入对字段的引用。有关使用静态架构的示例,请参阅 lwc-recipes 存储库。wireGetRecordDynamicContactwireGetRecordStaticContact

此组件为联系人呈现自定义 UI。为了获取数据,该组件使用 getRecord 线路适配器。

如果线路服务预配数据,模板将呈现姓名、职务、电话和电子邮件。如果线路服务预配错误,模板将呈现组件。c-error-panel

<!-- wireGetRecordDynamicContact.html -->
<template>
  <lightning-card title="My Contact Record" icon-name="standard:contact">
    <template lwc:if={contact.data}>
      <div class="slds-m-around_medium">
        <p>{name}</p>
        <p>{title}</p>
        <p><lightning-formatted-phone value={phone}></lightning-formatted-phone></p>
        <p><lightning-formatted-email value={email}></lightning-formatted-email></p>
      </div>
    </template>
    <template lwc:if={contact.error}>
      <c-error-panel errors={contact.error}></c-error-panel>
    </template>
  </lightning-card>
</template>

现在让我们看一下组件的 JavaScript。

首先,代码从基于 Lightning 数据服务构建的模块导入电线适配器。然后,它定义要传递给电线适配器的字段。getRecordlightning/uiRecordApi

该组件用于定义公共属性。如果组件嵌套在闪电记录页面中(我们的组件就是该页面),则闪电网络页面将设置值 。@apirecordIdrecordId

装饰器告诉 getRecord 获取具有指定 .表示该值是动态传递的。当值更改时,线路服务将预配数据,组件将重新呈现。@wire$recordId$

数据被提供给用 修饰的属性的 和对象。在此示例中,这是属性。dataerror@wirecontact

注意

由于该属性返回的默认值是 ,因此此示例依赖于指令来检查是否已填充联系人记录。JavaScript 代码中的 getter 在填充了联系人记录时被调用。或者,使用 getFieldValue(record, field)。dataundefinedlwc:ifcontact.datacontact.data

// wireGetRecordDynamicContact.js
import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

const FIELDS = ["Contact.Name", "Contact.Title", "Contact.Phone", "Contact.Email"];

export default class WireGetRecordDynamicContact extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: "$recordId", fields: FIELDS })
  contact;

  get name() {
    return this.contact.data.fields.Name.value;
  }

  get title() {
    return this.contact.data.fields.Title.value;
  }

  get phone() {
    return this.contact.data.fields.Phone.value;
  }

  get email() {
    return this.contact.data.fields.Email.value;
  }
}

回头看看 HTML 模板。、 、 和 字段使用以下语法绑定到 JavaScript 类。在 JavaScript 中,每个属性都有一个 getter 函数,该函数使用对象访问其同名字段的值。nametitlephoneemail{}contact.data

返回数据的结构与构建的用户界面 API 返回的结构相同。在本例中,响应为 Record。getRecord

注意

此示例演示如何通过返回的 JSON 对象进行数据访问。要简化数据访问,请使用 getFieldValue(record, field)。

处理 Lightning 数据服务中的错误

当服务器上无法访问资源(如记录或对象)时,Lightning 数据服务将返回错误。

例如,如果将无效的输入传递到线路适配器,例如无效的记录 ID 或缺少必填字段,则会发生错误。如果记录不在缓存中且服务器处于脱机状态,也会返回错误。此外,当删除资源或更新其共享或可见性设置时,该资源可能会在服务器上变得不可访问。

错误值

错误对象是根据 Fetch API 的 Response 对象建模的。它包含响应的正文以及状态代码和消息。FetchResponse

  • body(对象或数组)- 响应的正文,由底层 API 定义。
  • ok(Boolean) – 指定响应是否成功。对于错误,始终为 ,并且包含 400–599 范围内的状态。okfalse
  • status(数字)- 包含响应的状态代码,例如,如果找不到资源,则为 404,如果内部服务器错误为 500。
  • statusText(String) – 包含与状态代码对应的状态消息,例如,状态代码为 404。NOT_FOUND

使用 error 对象处理电线适配器返回的错误。getRecord

<template>
  <lightning-card title="Handle Error" icon-name="standard:contact">
    <template lwc:if={error}>
      <p>{error}</p>
    </template>
  </lightning-card>
</template>

JavaScript 代码检查错误正文是数组还是对象,并返回错误消息。

import { LightningElement, api, wire, track } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

const fields = [
  // This invalid field causes @wire(getRecord) to return an error
  "Contact.invalidField",
];

export default class WireGetRecordDynamicContact extends LightningElement {
  @api recordId;

  @track error;

  @wire(getRecord, { recordId: "$recordId", fields })
  wiredRecord({ error, data }) {
    if (error) {
      this.error = "Unknown error";
      if (Array.isArray(error.body)) {
        this.error = error.body.map((e) => e.message).join(", ");
      } else if (typeof error.body.message === "string") {
        this.error = error.body.message;
      }
      this.record = undefined;
    } else if (data) {
      // Process record data
    }
  }
}

车身有效载荷

错误响应的正文取决于返回它的 API。

  • UI API 读取操作(如线路适配器)以对象数组的形式返回。getRecorderror.body
  • UI API 写入操作(如线路适配器)作为对象返回,通常带有对象级和字段级错误。createRecorderror.body
  • Apex 读取和写入操作作为对象返回。error.body
  • 网络错误(如脱机错误)作为对象返回。error.body

将 Wire Service 与基本组件配合使用

为了简化记录数据的显示和操作并加快开发速度,请使用基本组件。

注意

让用户查看、编辑和创建 Salesforce 记录的最简单方法是使用组件。请参见比较基本组件。lightning-record*form

使用基本组件获取用户输入

若要创建自定义用户界面,或者如果不符合要求,请考虑将线路服务与其他基本组件一起使用。例如,使用 和 组件来构建自定义表单或显示记录数据。lightning-record*formlightning-inputlightning-formatted-*

有关这些组件的更多信息,请参阅组件参考。lightning-checkbox-group

显示两个或多个复选框,用于选择单个或多个选项。lightning-combobox

显示可选选项的下拉列表(选择列表)。lightning-input

根据用户输入的指定类型显示字段。lightning-input-address

显示地址输入的复合字段。

有关与 Wire Service 一起使用的示例,请参阅 github.com/trailheadapps/lwc-recipes 存储库中的组件。lightning-inputldsCreateRecordlightning-input-location

显示地理位置输入的复合字段。lightning-input-name

显示名称输入的复合字段。lightning-input-rich-text

显示带有用于设置内容格式的工具栏的富文本编辑器。lightning-radio-group

显示两个或多个复选框,用于选择单个或多个选项。lightning-textarea

显示多行文本输入字段。

尽管支持许多字段类型,但在使用不同的字段类型时,请考虑其他基本组件。lightning-input

假设您希望在用户界面中包含带有名字和姓氏字段的称呼选择列表。您可以添加 for the salutation 选择列表或使用 .下一个示例用于显示称呼、名字和姓氏字段及其当前值。要确定哪个基本组件最适合您的用例,请参阅组件参考。lightning-comboboxlightning-input-namelightning-input-name

示例:创建自定义表单

本示例创建一个自定义窗体,以显示和编辑联系人的称呼、名字和姓氏的值。这些字段显示联系人记录中的当前值。

注意

您可以使用这些组件以更少的代码行获得类似的结果。lightning-record*form

<template lwc:if={contact.data}>
    <div class="slds-m-around_medium">
        <lightning-input-name
                label="Contact Name"
                first-name={firstname}
                last-name={lastname}
                salutation={salutation}
                options={salutations}
                class="slds-m-bottom_x-small"
                required>
        </lightning-input-name>
    </div>
</template>

若要在字段中显示初始值,请使用电线适配器。getRecord

要填充 的选择列表选项,请使用电线适配器。此示例使用默认记录类型 Id。要显示当前选定的称呼值,请导入字段引用。lightning-input-namegetPicklistValuesContact.Salutation

import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";
import { getPicklistValues } from "lightning/uiObjectInfoApi";

import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";
import LASTNAME_FIELD from "@salesforce/schema/Contact.LastName";
import SALUTATION_FIELD from "@salesforce/schema/Contact.Salutation";

const namefields = [FIRSTNAME_FIELD, LASTNAME_FIELD, SALUTATION_FIELD];

export default class GetContactName extends LightningElement {
  @api recordId; // provided by the contact record page

  @wire(getPicklistValues, { recordTypeId: "012000000000000AAA", fieldApiName: SALUTATION_FIELD })
  salutationValues;

  @wire(getRecord, { recordId: "$recordId", fields: namefields })
  contact;

  get firstname() {
    return this.contact.data.fields.FirstName.value;
  }

  get lastname() {
    return this.contact.data.fields.LastName.value;
  }

  get salutation() {
    return this.contact.data.fields.Salutation.value;
  }

  // creates the options array for lightning-input-name
  get salutations() {
    let salutationOptions = [];
    Object.entries(this.salutationValues.data.values).forEach((val) => {
      let values = val[1];
      salutationOptions.push({ label: values.label, value: values.value });
    });
    return salutationOptions;
  }
}

使用基本组件显示记录数据

显示记录数据的最简单方法是使用 或 。若要在自定义用户界面中显示记录数据,请考虑使用以下基本组件。有关这些组件的更多信息,请参阅组件参考。lightning-record-formlightning-record-view-formlightning-formatted-address

对于地址复合字段。 显示一个地址,其中包含指向 Google 地图上给定位置的链接。该链接将在新选项卡中打开。静态地图可以与地址一起显示,以获得更好的上下文。lightning-formatted-addresslightning-formatted-date-time

用于日期或日期/时间字段。 显示日期和时间。lightning-formatted-date-timelightning-formatted-email

对于电子邮件字段。 将电子邮件显示为带有 URL 方案的超链接。lightning-formatted-emailmailto:lightning-formatted-location

对于地理位置字段。 使用格式显示以十进制度表示的地理位置。lightning-formatted-locationlatitude, longitudelightning-formatted-name

对于名称字段。 显示可以包含问候语和后缀的名称。lightning-formatted-namelightning-formatted-number

对于货币、数字和百分比字段。 以指定格式显示数字。lightning-formatted-numberlightning-formatted-phone

对于电话字段。 将电话号码显示为带有 URL 方案的超链接。lightning-formatted-phonetel:lightning-formatted-rich-text

对于富文本字段。 显示使用列入许可名单的标记和属性设置格式的富文本。lightning-formatted-rich-textlightning-formatted-text

对于文本字段。 显示文本,用换行符替换换行符,并添加链接。lightning-formatted-textlightning-formatted-time

对于时间字段。 以用户的区域设置格式显示时间。lightning-formatted-timelightning-formatted-url

对于 url 字段。 将 URL 显示为超链接。lightning-formatted-url

示例:使用地图显示自定义记录

本示例在带有静态地图的联系人记录页上显示地址。点击地址或地图即可在 Google 地图上打开该位置。由于 和 组件不提供静态映射,因此该示例使用该组件。lightning-record-formlightning-record-view-formlightning-formatted-address

<template>
  <lightning-card title="Display Contact Address" icon-name="standard:contact">
    <template lwc:if={contact.data}>
      <div class="slds-m-around_medium">
        <lightning-formatted-address
          street={street}
          city={city}
          country={country}
          province={state}
          postal-code={postal}
          show-static-map
        ></lightning-formatted-address>
      </div>
    </template>
  </lightning-card>
</template>

若要显示地址复合字段,请使用电线适配器。getRecord

import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

import STREET_FIELD from "@salesforce/schema/Contact.MailingStreet";
import CITY_FIELD from "@salesforce/schema/Contact.MailingCity";
import STATE_FIELD from "@salesforce/schema/Contact.MailingState";
import COUNTRY_FIELD from "@salesforce/schema/Contact.MailingCountry";
import POSTAL_FIELD from "@salesforce/schema/Contact.MailingPostalCode";

const FIELDS = [STREET_FIELD, CITY_FIELD, STATE_FIELD, COUNTRY_FIELD, POSTAL_FIELD];

export default class GetContactAddress extends LightningElement {
  @api recordId; // provided by the contact record page

  @wire(getRecord, { recordId: "$recordId", fields: FIELDS })
  contact;

  get street() {
    return this.contact.data.fields.MailingStreet.value;
  }

  get city() {
    return this.contact.data.fields.MailingCity.value;
  }

  get state() {
    return this.contact.data.fields.MailingState.value;
  }

  get country() {
    return this.contact.data.fields.MailingCountry.value;
  }

  get postal() {
    return this.contact.data.fields.MailingPostalCode.value;
  }
}

在表中显示记录数据

若要显示记录数据的行和列,请使用该组件。若要在具有结构层次结构的表中显示数据,例如客户记录和关联的联系人,请使用该组件。lightning-datatablelightning-tree-grid

比较和用于数据显示lightning-datatable lightning-tree-grid

这些组件类似,但有一个 V 形按钮,用于展开和折叠具有子记录的行。lightning-tree-grid

lightning-datatable使用 Salesforce Lightning Design System (SLDS) 中的数据表蓝图,并提供一种在桌面上显示记录数据的简单方法。

lightning-tree-grid使用 SLDS 中的树网格蓝图,使您能够显示分层数据,将记录分组到父子关系中。

提示

Lightning Design System 文档提供了每个组件处于不同状态的示例。

以下是 和 支持的功能列表。lightning-datatablelightning-tree-grid

特征lightning-datatablelightning-tree-grid
按数据类型设置列格式是的是的
标头操作是的是的
行操作是的是的
调整列大小是的是的
选择行是的是的
换行或剪裁列内容是的是的
行号是的是的
列排序是的
内联编辑是的
无限滚动是的
可扩展行是的

注意

lightning-datatable并且不受移动设备支持。lightning-tree-grid

使用内联编辑在表格中显示数据

要在表中显示 Salesforce 数据,请使用该组件。该组件支持内联编辑,使用户无需导航到记录即可更新字段值。lightning-datatable

若要加载记录列表,请使用 getListUi(已弃用)线路适配器或在 Apex 方法中使用 SOQL。请参阅数据指南。

此示例在 Apex 方法中使用 SOQL 在客户记录中加载联系人。

public with sharing class ContactController {

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts(String accId) {
        return [
            SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
            FROM Contact
            WHERE AccountId = :accId
            WITH SECURITY_ENFORCED
        ];
    }
}

注释将方法公开给 Lightning 组件,并在客户端上缓存联系人列表。该参数筛选给定客户记录的联系人。@AuraEnabled(cacheable=true)accId

如果 Apex 方法返回数据,则 HTML 模板将显示具有可编辑名字和姓氏字段 (1) 的组件。编辑字段时,将显示“保存”按钮,您可以单击该按钮以更新联系人记录。成功更新联系人记录还会更新相关列表 (2) 中的记录数据,以及页面上由 Lightning 数据服务管理的其他组件。lightning-datatable

实现内联编辑

提示

此示例基于 lwc-recipes 存储库中的组件。此版本略有不同,以便能够在记录页面上使用数据表。datatableInlineEditWithUiApi

若要启用内联编辑,请按以下模板所示配置组件。lightning-datatableapexContactsForAccount

<!-- apexContactsForAccount.html -->
<template>
  <lightning-card title="Datatable Example" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts.data}>
        <lightning-datatable
          key-field="Id"
          data={contacts.data}
          columns={columns}
          onsave={handleSave}
          draft-values={draftValues}
        >
        </lightning-datatable>
      </template>
      <template lwc:if={contacts.error}>
        <!-- Handle Apex error -->
      </template>
    </div>
  </lightning-card>
</template>

required 属性将每一行与联系人记录相关联。该属性保存通过线路服务从 Apex 方法或线路适配器检索的数据。该属性为每列分配一个记录字段,并自定义列的行为。当用户编辑单元格时,更新后的值将存储在 中。key-fielddatacolumnsdraft-values

单击“保存”按钮将触发该事件。使用事件处理程序将更改保留在数据表中。在此示例中,事件处理程序调用 updateRecord(recordInput, clientOptions) 来保存记录更改。saveonsaveonsave

注意

有关属性和支持的功能的列表,请参阅 lightning-datatable

在列中启用内联编辑

一行数据对应于一条记录,每列显示该记录的其中一个字段的值。若要启用内联编辑,请通过在列定义中设置来指定可编辑的字段。在此示例中,“名字”和“姓氏”字段是可编辑的。由于联系人的“姓名”字段是复合字段,因此必须分别使用“名字”和“姓氏”字段。editable: true

// apexContactsForAccount.js

import { LightningElement, wire, api } from "lwc";
import getContacts from "@salesforce/apex/ContactController.getContactList";
import { refreshApex } from "@salesforce/apex";
import { updateRecord } from "lightning/uiRecordApi";

import { ShowToastEvent } from "lightning/platformShowToastEvent";
import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";
import LASTNAME_FIELD from "@salesforce/schema/Contact.LastName";
import TITLE_FIELD from "@salesforce/schema/Contact.Title";
import PHONE_FIELD from "@salesforce/schema/Contact.Phone";
import EMAIL_FIELD from "@salesforce/schema/Contact.Email";
import ID_FIELD from "@salesforce/schema/Contact.Id";

const COLS = [
  {
    label: "First Name",
    fieldName: FIRSTNAME_FIELD.fieldApiName,
    editable: true,
  },
  {
    label: "Last Name",
    fieldName: LASTNAME_FIELD.fieldApiName,
    editable: true,
  },
  { label: "Title", fieldName: TITLE_FIELD.fieldApiName, editable: true },
  {
    label: "Phone",
    fieldName: PHONE_FIELD.fieldApiName,
    type: "phone",
    editable: true,
  },
  {
    label: "Email",
    fieldName: EMAIL_FIELD.fieldApiName,
    type: "email",
    editable: true,
  },
];
export default class DatatableInlineEditWithUiApi extends LightningElement {
  @api recordId;
  columns = COLS;
  draftValues = [];

  @wire(getContacts, { accId: "$recordId" })
  contacts;

  async handleSave(event) {
    // Convert datatable draft values into record objects
    const records = event.detail.draftValues.slice().map((draftValue) => {
      const fields = Object.assign({}, draftValue);
      return { fields };
    });

    // Clear all datatable draft values
    this.draftValues = [];

    try {
      // Update all records in parallel thanks to the UI API
      const recordUpdatePromises = records.map((record) => updateRecord(record));
      await Promise.all(recordUpdatePromises);

      // Report success with a toast
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Success",
          message: "Contacts updated",
          variant: "success",
        }),
      );

      // Display fresh data in the datatable
      await refreshApex(this.contacts);
    } catch (error) {
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Error updating or reloading contacts",
          message: error.body.message,
          variant: "error",
        }),
      );
    }
  }
}

@wire(getContacts)将数据提供给 ,并将记录 ID 提供给 。contacts.dataaccId

编辑字段时,将编辑的字段值和记录存储为对象数组。event.detail.draftValuesId

[
  {
    FirstName: "Sean",
    Id: "003R0000002J2wHIAS",
  },
  {
    FirstName: "Jack",
    Id: "003R0000002J2wIIAS",
  },
];

在进行更改后按 Tab 键或单击单元格外部时,将显示数据表页脚,其中包含“取消”和“保存”按钮。若要隐藏数据表页脚,请清除该属性。将值复制到记录对象中后,事件处理程序将清除。draftValuesonsavedraftValues

调用 updateRecord(recordInput, clientOptions) 将新字段值保存到记录中。

updateRecord()只需要一条记录。为要更新其字段的每条记录传入一个对象。在此示例中,该参数包含一个对象,该对象具有多个记录的更新字段。若要同时更新多条记录,请将参数传递给 进行处理。recordInputrecordupdateRecord()

成功保存后,事件处理程序将调度事件以显示成功 Toast 消息。onsaveShowToastEvent

更新记录后,刷新 Apex 方法中的联系人列表,以便数据表始终显示最新数据。refreshApex(this.contacts)

注意

对于单个事务中的批量记录更新,我们建议使用 Apex。请参阅使用 Apex 启用内联编辑部分。

使用 Apex 启用内联编辑

对于批量记录更新,请将记录更改传递给调用数据操作语言 (DML) 操作的 Apex 控制器。在此示例中,编辑后的字段值将传递到 Apex 控制器。由于记录是由 Apex 更新的,因此您必须使用 notifyRecordUpdateAvailable(recordIds) 函数通知 Lightning 数据服务 (LDS),以便刷新 Lightning 数据服务缓存和线路。updateupdateContacts

注意

此示例基于 github.com/trailheadapps/lwc-recipes 存储库中的组件。此版本略有不同,使用不带 LDS 的 Apex。datatableInlineEditWithApex

在上一示例中创建的文件中,导入 Apex 控制器和函数。apexContactsForAccount.jsnotifyRecordUpdateAvailable

import updateContacts from "@salesforce/apex/ContactController.updateContacts";
import { notifyRecordUpdateAvailable } from "lightning/uiRecordApi";

更新函数以处理编辑的值。若要确保在通过 Apex 更新记录后调用该记录,请使用 / 模式或 Promise 链。此示例使用 /。handleSave()notifyRecordUpdateAvailable()asyncawaitasyncawait

async handleSave(event) {
    const updatedFields = event.detail.draftValues;

    // Prepare the record IDs for notifyRecordUpdateAvailable()
    const notifyChangeIds = updatedFields.map(row => { return { "recordId": row.Id } });

    try {
        // Pass edited fields to the updateContacts Apex controller
        const result = await updateContacts({data: updatedFields});
        console.log(JSON.stringify("Apex update result: "+ result));
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Success',
                message: 'Contact updated',
                variant: 'success'
            })
        );

        // Refresh LDS cache and wires
        notifyRecordUpdateAvailable(notifyChangeIds);

        // Display fresh data in the datatable
        await refreshApex(this.contacts);
            // Clear all draft values in the datatable
            this.draftValues = [];
        }
   } catch(error) {
           this.dispatchEvent(
               new ShowToastEvent({
                   title: 'Error updating or refreshing records',
                   message: error.body.message,
                   variant: 'error'
               })
         );
    };
}

Apex 控制器将包含更新字段的 JSON 字符串反序列化为 Contact 对象。它使用 DML 操作更新已更改的记录。updateContactsupdate

@AuraEnabled
public static string updateContacts(Object data) {
    List<Contact> contactsForUpdate = (List<Contact>) JSON.deserialize(
         JSON.serialize(data),
         List<Contact>.class
    );
    try {
        update contactsForUpdate;
        return 'Success: contacts updated successfully';
    }
    catch (Exception e) {
        return 'The following exception has occurred: ' + e.getMessage();
    }
}

创建自定义数据类型
组件根据您为列指定的类型设置数据格式。lightning-datatable

在创建自己的数据类型之前,请检查标准数据类型,看看是否满足您的要求。可以使用类型属性自定义多种类型的输出。标准数据类型包括:

action
boolean
button
button-icon
currency
date
date-local
email
location
number
percent
phone
text(默认)
url
有关标准数据类型及其类型属性的更多信息,请参阅 lightning-datatable 参考文档。

此外,在创建自己的数据类型之前,请考虑 SLDS 样式挂钩。SLDS 样式钩子可帮助您在影子 DOM 中自定义支持的数据类型。例如,可以使用 SLDS 样式挂钩在数据类型上应用不同的颜色。button

注意

自定义数据类型不支持调度自定义事件。

通过扩展 LightningDatatable 定义自定义类型

创建自己的数据类型来实现自定义单元格,例如删除行按钮或图像,甚至是自定义文本或数字显示。还可以为自定义数据类型上的每一行应用自定义类。

若要定义和使用自定义数据类型,请在新组件中扩展组件的类。LightningDatatablelightning-datatable

注意

您可以从 only 扩展到创建具有自定义数据类型的数据表。在所有其他情况下,不支持扩展除创建 Lightning Web 组件之外的任何类。LightningDatatableLightningElement

创建一个 Lightning Web 组件,并在组件文件夹的 HTML 模板中定义您的类型。该模板可以包含不需要 JavaScript 的简单数据类型的完整 UI。该模板还可以嵌入您在另一个文件夹中定义的组件。例如,如果要包含确定要显示的内容的逻辑,请使用单独的组件。

让我们看一下组件的文件夹结构,它定义了两种自定义类型。myCustomTypeDatatable

myCustomTypeDatatable
   ├──customName.html
   ├──customNumber.html
   ├──myCustomTypeDatatable.js
   └──myCustomTypeDatatable.js-meta.xml
在 JavaScript 文件中,扩展类并指定类型的名称和模板文件。此示例使用 和 模板创建自定义名称类型和自定义数字类型。myCustomTypeDatatable.jsLightningDatatablecustomName.htmlcustomNumber.html

注意

类型和模板的名称不必匹配。该示例同时使用类型名称和模板文件名。我们建议您导入具有不同名称的模板,以便明确指定类型名称和模板名称的位置。customName

//myCustomTypeDatatable.js
import LightningDatatable from "lightning/datatable";
import customNameTemplate from "./customName.html";
import customNumberTemplate from "./customNumber.html";

export default class MyCustomTypeDatatable extends LightningDatatable {
  static customTypes = {
    customName: {
      template: customNameTemplate,
      standardCellLayout: true,
      typeAttributes: ["accountName"],
    },
    customNumber: {
      template: customNumberTemplate,
      standardCellLayout: false,
      typeAttributes: ["status"],
    },
    // Other types here
  };
}
注意

您可以从 only 扩展到创建自定义数据类型。LightningDatatable

将以下属性传递给对象。customTypes

自定义类型属性	类型	描述
template	字符串	键入的导入 HTML 模板的名称。
typeAttributes	数组	要传递给自定义数据模板的属性的逗号分隔列表。使用语法访问数据。typeAttributes.attributeName
standardCellLayout	布尔	指定是否使用标准布局。默认值为 false。所有标准数据类型都使用标准布局。自定义数据类型的默认布局是裸布局。请参阅自定义数据类型布局和样式。您可以使用自定义数据类型的单元格样式,使其看起来与标准数据类型相似。还支持辅助功能和键盘导航。standardCellLayoutstandardCellLayout
创建自定义数据模板

在自定义数据模板中,添加数据类型的标记。此示例创建一个自定义类型,该类型使用组件呈现文本标签。customName.htmllightning-badge

<!--customName.html-->
<template>
  <lightning-badge label={typeAttributes.accountName} icon-name="standard:account">
  </lightning-badge>
</template>
该模板使用默认的裸布局,因为 设置为 .您可以添加自己的样式。以下示例在单元格周围添加一个小填充,并将数字浮动到右侧。如果数字与数据定义中指定的条件匹配,则会显示一个图标。customNumber.htmlmyCustomTypeDatatable.jsstandardCellLayout: falsecustomNumber

<!--customNumber.html-->
<template>
  <div class="slds-p-around_x-small">
    <lightning-formatted-number
      value={value}
      class="slds-float_right"
    ></lightning-formatted-number>
    <lightning-icon
      icon-name={typeAttributes.status}
      alternative-text="Employer Status"
    ></lightning-icon>
  </div>
</template>
提示

这些示例自定义数据类型很简单,只能用 HTML 表示。如果自定义类型更复杂,请使用 JavaScript、HTML 模板和 XML 配置文件创建单独的组件。使用自定义数据 HTML 模板作为组件的容器。自定义数据类型的编辑模板示例中使用了一个单独的组件。

使用自定义类型实现数据表

让我们实现一个使用自定义类型的数据表。第一列使用我们在上一节中创建的自定义类型显示帐户名称。该属性与帐户对象上的字段匹配。fieldNameName

使用自定义类型记录数据表中显示的数据

使用 Apex 控制器在数据表中显示 10 条帐户记录。

public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccountList() {
        return [SELECT Id, Name, Industry, NumberOfEmployees FROM Account WITH SECURITY_ENFORCED LIMIT 10];
    }
}
要实现自定义类型 datatable,请创建一个包装组件以包含扩展的 datatable 组件,定义数据表的列,然后获取数据。这里我们用作包装器。myDatatable

定义列时,可以使用该属性传入 SLDS 实用程序类或您自己的类。cellAttributes

/* myDatatable.js */
import { LightningElement, wire, track } from "lwc";
import getAccountList from "@salesforce/apex/AccountController.getAccountList";

const COLS = [
  {
    label: "Account Name",
    type: "customName",
    typeAttributes: {
      accountName: { fieldName: "Name" },
    },
  },
  {
    label: "Industry",
    fieldName: "Industry",
    cellAttributes: {
      class: { fieldName: "industryColor" },
    },
  },
  {
    label: "Employees",
    type: "customNumber",
    fieldName: "NumberOfEmployees",
    typeAttributes: {
      status: { fieldName: "status" },
    },
    cellAttributes: {
      class: "slds-theme_alert-texture",
    },
  },
];

export default class MyDatatable extends LightningElement {
  columns = COLS;
  @track accounts = [];

  @wire(getAccountList)
  wiredAccounts({ error, data }) {
    if (error) {
      // Handle error
    } else if (data) {
      // Process record data
      this.accounts = data.map((record) => {
        let industryColor = record.Industry === "Energy" ? "slds-text-color_success" : "";
        let status = record.NumberOfEmployees > 10000 ? "utility:ribbon" : "";
        return { ...record, industryColor: industryColor, status: status };
      });
    }
  }
}
myDatatable.js演示了在自定义单元格上应用样式的几种方法。仅当字段值匹配时,该列才使用该类。该列在该列的所有行上使用该类。此外,用于列的类型包括直接在标记中实现的布局和填充类。Industryslds-text-color_successEnergyNumberOfEmployeesslds-theme_alert-texturecustomNumberNumberOfEmployeescustomNumber.html

接下来,将 Apex 控制器返回的数据传递给自定义类型 datatable 组件中的属性。getAccountListdata

<!--myDatatable.html-->
<template>
  <c-my-custom-type-datatable
    key-field="Id"
    data={accounts}
    columns={columns}
    show-row-number-column
  >
  </c-my-custom-type-datatable>
</template>
提示

lwc-recipes 存储库中的组件为 创建自定义数据类型。datatableCustomDataTypelightning-datatable

自定义数据类型布局和样式

您可以自定义使用自定义数据类型的单元格的外观。lightning-datatable

使用自定义数据类型的布局

自定义单元格可以使用标准布局或裸布局。

标准布局:

  • 将内容均匀地分布在单元格中,第一个项目与左边框齐平,最后一个项目与右边框齐平
  • 将文本向左对齐,垂直向中对齐
  • 在内容的左侧和右侧添加一个小填充
  • 支持可编辑类型的辅助功能和键盘导航

裸布局,这是自定义单元格的默认布局:

  • 将文本向左对齐,垂直向中对齐
  • 删除内容左侧和右侧的填充
  • 不支持可编辑类型的辅助功能和键盘导航

所有支持的标准数据类型都使用标准布局。lightning-datatable

若要使用标准布局,请指定定义自定义类型的时间。standardCellLayout: true

static customTypes = { customName: { template: customNameTemplate, standardCellLayout: true,
typeAttributes: ['accountName'], }, // Other types here }

请参阅创建自定义数据类型。

创建自定义数据类型并使用默认裸布局时,可以应用自己的样式、对齐列中的内容、配置文本换行和剪辑。

在列上应用样式

若要将样式应用于列中的所有行,请在列配置中将 CSS 类传递给。cellAttributes

const COLS = [
  {
    label: "Employees",
    type: "number",
    fieldName: "NumberOfEmployees",
    cellAttributes: {
      class: "slds-theme_shade slds-theme_alert-texture",
    },
  },
];

若要将样式应用于特定行,请使用该属性将 CSS 类连接到数据定义。此示例使用该属性将蓝色文本应用于“能源”行业的行。fieldNamefieldNameindustryClass

/* myDatatable.js */
import { LightningElement, wire, track } from 'lwc';
import getAccountList from '@salesforce/apex/AccountController.getAccountList';

const COLS = [{
    label: 'Industry', fieldName: 'Industry',
    cellAttributes: {
        class: { fieldName: 'industryClass' },
    }
}, // Other columns here
];

export default class MyDatatable extends LightningElement {
    columns = COLS;
    @track accounts = [];

    @wire(getAccountList)
    wiredAccounts({error, data}) {
        if (error) {
            // Handle error
        } else if (data) {
            // Process record data
            this.accounts = data.map((record) => {
                let industryClass = record.Industry === 'Energy' ? 'blueText': 'defaultText';
                return {...record, 'industryClass: industryClass}
            });

        }
    }

对齐自定义单元格中的内容

您可以对齐使用自定义数据类型标准布局的列中的内容。在定义中,传入属性。cellAttributesalignment

const COLS = [
  {
    label: "Account Name",
    type: "customName",
    fieldName: "Name",
    cellAttributes: {
      alignment: "right",
    },
  },
];

支持的值为 、 和 。alignmentleftcenterright

默认情况下,标准数字类型向右对齐。标准数字类型包括 、 和 数据类型。currencynumberpercent

文本换行和剪裁

创建自定义数据类型时,请使用 SLDS 实用程序类和 实现文本换行和剪裁。这些类使自定义数据单元格能够在用户更改数据表列标题中的自动换行选择时正确显示。文本换行会垂直展开行以显示更多内容。文本剪裁会将内容截断为列中的一行。slds-hyphenateslds-truncate

假设您有一个显示帐户名称的自定义数据类型模板。默认情况下,该列显示的文本被剪裁,这会截断带有尾随省略号的内容。

<template>
  <template lwc:if={wrapText}>
    <div class="slds-hyphenate">{typeAttributes.accountName}</div>
  </template>
  <template lwc:else>
    <div class="slds-truncate">{typeAttributes.accountName}</div>
  </template>
</template>

您还可以创建一个 getter,该 getter 根据该值返回 SLDS 实用程序类。wrapText

若要显示换行文本,请从列标题的下拉菜单中选择换行文本

或者,若要在加载数据表时在列上显示换行文本,请传入列定义。wrapText: true

const COLS = [
  {
    label: "Account Name",
    type: "customText",
    wrapText: true,
    typeAttributes: {
      accountName: { fieldName: "Name" },
    },
  },
];

您可以控制换行列内容时显示的行数。在扩展数据表组件中设置,以在隐藏其余行之前显示多行。数据表应用行位以允许多行文本截断。wrap-text-max-lines

<template>
  <c-my-custom-type-datatable
    key-field="id"
    data={accounts}
    columns={columns}
    wrap-text-max-lines="2"
  >
  </c-my-custom-type-datatable>
</template>

注意

wrap-text-max-linesInternet Explorer 11 不支持。如果为 true,则显示列中的整个文本。wrapText

使自定义数据类型可编辑

若要使自定义数据类型可在 中编辑,请创建一个附加模板来实现用于内联编辑数据类型的 UI。lightning-datatable

这些高级步骤增加了内联编辑功能。

  1. 创建编辑模板。
  2. 将编辑模板添加到自定义类型定义中。
  3. 使自定义类型在列定义中可编辑。

让我们通过扩展 LightningDatatable 定义自定义类型中的示例中的类型实现内联编辑。customNumber

创建编辑模板

在定义自定义类型模板的同一文件夹中,创建用于编辑自定义类型的模板。customNumberEdit.htmlcustomNumber.html

myCustomTypeDatatable
   ├──customName.html
   ├──customNumber.html
   ├──customNumberEdit.html
   ├──myCustomTypeDatatable.js
   └──myCustomTypeDatatable.js-meta.xml

自定义类型的编辑模板使用与自定义类型模板中的输出组件匹配的输入组件。建议使用该组件。在示例中,输出组件在模板中使用,因此我们在其编辑模板中使用。该组件使用户能够更改自定义类型单元格中的数字。lightning-inputlightning-formatted-numbercustomNumberlightning-input type="number"lightning-input type="number"

提示

有关可在编辑模板中使用的组件的信息,请参阅本页后面的自定义类型的内联编辑注意事项

<!--customNumberEdit.html-->
<template>
  <div class="slds-p-around_x-small">
    <lightning-input
      type="number"
      value={editedValue}
      required={required}
      label={columnLabel}
      min={typeAttributes.min}
      data-inputable="true"
      class="slds-float_right"
    >
    </lightning-input>
  </div>
</template>

使用这些属性在输入组件和扩展数据表组件之间传递值。

属性类型描述
editedValue字符串正在编辑的自定义单元格的当前值。
columnLabel字符串从列定义中检索到的值。label
required布尔从列定义中检索到的必需值。默认值为 false。如果列定义设置为 true,则当用户与字段交互且未输入值时,该字段将显示验证错误。required
typeAttributes对象包含单元格的 type 属性值的对象。使用语法访问值。typeAttributes.attributeName

该示例设置该属性,以确保用户在内联编辑自定义数字时输入非负数。在下一节中,将添加到自定义类型定义中。minmin

该属性对于标准单元格布局中的辅助功能支持是必需的。data-inputable="true"

将编辑模板添加到自定义类型定义

导入 中的编辑模板,并在对象的定义中添加属性。该属性指定用于自定义类型的内联编辑的模板。myCustomTypeDatatable.jseditTemplate: customNumberEditTemplatecustomNumbercustomTypeseditTemplate

//myCustomTypeDatatable.js
import LightningDatatable from "lightning/datatable";
import customNameTemplate from "./customName.html";
import customNumberTemplate from "./customNumber.html";
import customNumberEditTemplate from "./customNumberEdit.html";

export default class MyCustomTypeDatatable extends LightningDatatable {
  static customTypes = {
    customName: {
      template: customNameTemplate,
      standardCellLayout: true,
      typeAttributes: ["accountName"],
    },
    customNumber: {
      template: customNumberTemplate,
      editTemplate: customNumberEditTemplate,
      standardCellLayout: true,
      typeAttributes: ["status", "min"],
    },
    // Other types here
  };
}

将值更改为 true 以支持辅助功能和键盘导航。有关详细信息,请参阅本页后面的可编辑自定义类型的辅助功能standardCellLayout

在类型定义中添加属性。mintypeAttributes

使自定义类型在列定义中可编辑

在数据表的列定义中,将属性添加到自定义类型。editable: true

在这里,我们展示了创建自定义数据类型中的使用自定义类型实现数据表的示例,该数据类型将属性应用于 Employees 列,该列使用 type .myDatatable.jseditable: truecustomNumber

/* myDatatable.js */
import { LightningElement, wire, track } from 'lwc';
import getAccountList from '@salesforce/apex/AccountController.getAccountList';

const COLS = [
    { label: 'Account Name', type: 'customName',
     typeAttributes: {
        accountName: { fieldName: 'Name' }
    },
    },
    { label: 'Industry', fieldName: 'Industry',
     cellAttributes: {
        class: {fieldName: 'industryColor'},
    }
    },
    { label: 'Employees', type: 'customNumber', fieldName: 'NumberOfEmployees', editable: true,
     typeAttributes: {
            status: {fieldName: 'status'},
            min: 0
    },
    cellAttributes: {
        class: 'slds-theme_alert-texture'
    }
}];

如果在编辑模板中向输入组件添加属性,请将它们也添加到列定义中。在这里,我们为 Employees 列添加了属性。mintypeAttributes

处理自定义类型的更改值

与标准数据类型一样,自定义类型的编辑字段以黄色突出显示显示。单元格的更改值临时存储在属性中。editedValue

使用 action 和 attribute 保留数据表中更改的值,就像对标准类型所做的那样。请参阅 lightning-datatable 参考文档中的使用内联编辑在表中显示数据和使用内联编辑部分。onsavedraft-values

可编辑自定义类型的辅助功能

要支持辅助功能,例如可编辑自定义数据类型的键盘导航,请执行以下操作:

  • 将属性添加到编辑模板中的输入组件。data-inputable="true"
  • 为 中的自定义类型定义设置。standardCellLayout: truecustomTypes

这些属性使数据表的操作模式和导航模式能够处理您的自定义数据类型。通过在显示可编辑的自定义数据类型的单元格上按 Enter 键或空格键进入操作模式。在导航模式下,当您在数据表中按 Tab 键时,您可以导航到显示可编辑自定义数据类型的单元格,因为它们是可操作的。

该属性使用该属性的值应用于输入组件。aria-labellabel

自定义类型的已编辑单元格的验证和错误处理

该组件为您处理基本的客户端输入验证。例如,如果为数字输入指定 、 或属性,则组件会根据这些属性值验证输入,如果输入无效,则返回错误。还可以对自定义类型执行更复杂的客户端验证。lightning-inputstepminmax

若要在客户端上进一步验证自定义类型的用户输入,请在自定义编辑模板中使用子组件。

在输入组件上设置,并在其 JS 中包含 getter,以公开输入组件的有效性 API。data-inputable="true"validity()

@api
get validity() {
    return this.customInput.validity;
}

在编辑模板中公开输入组件中的方法,以便自定义数据类型可以在更改的数据无效时显示自定义消息。showHelpMessageifInvalid()

@api
showHelpMessageIfInvalid() {
    this.customInput.showHelpMessageIfInvalid()
}

有关详细信息,请参阅 lightning-input 的参考文档。

自定义类型的内联编辑注意事项

  • 在自定义编辑模板中,仅支持将具有单个输入字段的 Lightning 基本组件作为输入组件。还支持具有多个输入的组件,其 API 计算结果为单个输入。lightning-input type="datetime"
  • 该组件仅设计用于数据表,不支持在数据表中使用。请改用。lightning-input-fieldlightning-record-edit-formlightning-input
  • 自定义数据类型尚不支持服务器端验证规则。

自定义数据类型的编辑模板示例

此示例显示了一个自定义数据类型,其模板包含组件而不是简单的标记。

注意

lwc-recipes 存储库具有多个组件,用于演示使用 Apex 和模块进行内联编辑。查找名称以 开头的组件。lightning/uiRecordApidatatableInline

此示例创建一个具有 Amount 自定义数据类型的数据表。

这是初始加载时的数据表。

该组件包含定义自定义类型的扩展数据表组件。myDatatableWrapper

/* myDatatableWrapper.html */

<template>
  <c-my-custom-type-datatable columns={COLS} key-field="id" data={data}>
  </c-my-custom-type-datatable>
</template>
// myDatatableWrapper.js

import { LightningElement } from 'lwc';
export default class MyDatatableWrapper extends LightningElement {
    COLS = [
        { label: 'Account', type: 'text', fieldName: 'account', editable: false, displayReadOnlyIcon: true},
        { label: 'Stage', type: 'text', fieldName: 'stage', editable: false, displayReadOnlyIcon: true},
        { label: 'Amount', type: 'customNumber', fieldName: 'amount', editable: true,
            typeAttributes: {
                status: {fieldName: 'status'},
                step: {fieldName: 'step'}
            }
        },
        { label: 'Due Date', type: 'date', fieldName: 'date', editable: false, displayReadOnlyIcon: true}
    ];

    data = [
        {account: 'Acme Corp.', amount: '200.00', status: 'action:user',
            step: '0.01', stage:'Value Proposition', date: '2022-02-10T00:00:00Z'},
        {account: 'Norwood Inc.', amount: '500000.00', status: 'action:user',
            step: '0.01', stage:'Closed Won', date: '2022-01-20T00:00:00Z'},
        {account: 'Edge Communications', amount: '800000.00', status: 'action:user',
            step: '0.01', stage:'Closed Won', date: '2022-02-18T00:00:00Z'},
        {account: 'Pyramid Construction Inc.', amount: '50000.00', status: 'action:user',
            step: '0.01', stage:'Closed ', date: '2022-03-10T00:00:00Z'},
        {account: 'GenePoint', amount: '30000', status: 'action:user',
            step: '0.01', stage:'Negotiation/Review	', date: '2022-05-28T00:00:00Z'},
        {account: 'Dickenson plc', amount: '1550000.00', status: 'action:user',
            step: '0.01', stage:'Closed Won', date: '2022-03-16T00:00:00Z'}
    ]

下面是具有可编辑自定义类型的扩展数据表的文件夹。

myCustomTypeDatatable
   ├──customNumber.html
   ├──customNumberEdit.html
   ├──myCustomTypeDatatable.js
   └──myCustomTypeDatatable.js-meta.xml

编辑模板包含另一个定义内联编辑 UI 的组件。

/*customNumber.html*/
<template>
  <c-my-fancy-number value={value}> </c-my-fancy-number>
</template>

子组件定义数据类型的显示,并包含用于确定应用于数据的样式的逻辑。

该组件评估数据以应用类、图标和图标样式。myFancyNumber

/* myFancyNumber.html */

<template>
  <div class={computedClass}>
    <lightning-formatted-number format-style="currency" value={value}>
    </lightning-formatted-number>
    <lightning-icon
      class="slds-p-horizontal_xx-small"
      icon-name={computedIcon}
      size="xx-small"
      variant={iconVariant}
    >
    </lightning-icon>
    ({range})
  </div>
</template>
// myFancyNumber.js

import { LightningElement, api, track, wire } from "lwc";

export default class MyFancyNumber extends LightningElement {
  @api value;
  get range() {
    return this.value > 50000 ? "High" : "Low";
  }
  get computedClass() {
    return this.value > 50000 ? "slds-text-color_success" : "slds-text-color_error";
  }

  get computedIcon() {
    return this.value > 50000 ? "utility:arrowup" : "utility:arrowdown";
  }

  get iconVariant() {
    return this.value > 50000 ? "success" : "error";
  }
}

该模板使用带有 的组件。customNumberEdit.htmllightning-inputtype="number"

/* customNumberEdit.html */
<template>
  <lightning-input
    type="number"
    value={editedValue}
    label={columnLabel}
    required={required}
    step={typeAttributes.step}
    data-inputable="true"
  >
  </lightning-input>
</template>

下面是在保存表之前编辑自定义数字类型字段后的数据表。编辑后的字段以黄色突出显示。

有关保存已更改数据的信息,请参阅使用内联编辑在表中显示数据。

创建记录数据的分层表

该组件显示可展开以显示子记录的数据行。lightning-tree-grid

该组件基于并支持其功能的子集。请参阅在表格中显示记录数据,以比较每个组件提供的功能。lightning-tree-gridlightning-datatable

显示记录和子记录

让我们创建一个表来显示具有关联案例的帐户。每个行级操作都允许您使用导航服务编辑客户或案例记录。

让我们使用 Apex 获取数据。在此示例中,包含一个 SOQL 语句,该语句返回帐户名称和状态为 的所有关联事例。CaseController.clsNew

//CaseController.cls
public with sharing class CaseController {
    @AuraEnabled(cacheable=true)
    public static list<Account> getNewCasesForAccounts(){
        return [SELECT Name, (
                SELECT Id, CaseNumber, Status FROM Cases WHERE toLabel(Status) = 'New')
                FROM Account WHERE Id IN (SELECT AccountId FROM Case)];
    }
}

在 JavaScript 文件中,用于调用 Apex 方法。使用键定义包含子项的行。包含子项的行显示带有 V 形按钮,用于切换子项。myTreeGrid.js@wire_children

//myTreeGrid.js
import { LightningElement, wire, track } from "lwc";
import { NavigationMixin } from "lightning/navigation";

import getNewCases from "@salesforce/apex/CaseController.getNewCasesForAccounts";

const actions = [{ label: "Edit", name: "edit_record" }];

const COLS = [
  {
    fieldName: "Name",
    label: "Account Name",
  },
  {
    fieldName: "CaseNumber",
    label: "New Cases",
    cellAttributes: {
      iconName: { fieldName: "iconName" },
    },
  },
  {
    type: "action",
    label: "Edit Record",
    typeAttributes: {
      rowActions: actions,
      menuAlignment: "right",
    },
  },
];

export default class MyTreeGrid extends NavigationMixin(LightningElement) {
  @track myData = [];
  columns = COLS;
  error;

  @wire(getNewCases)
  wiredCases({ error, data }) {
    if (error) {
      // Handle error
      this.error = error;
    } else if (data) {
      // Process record data
      var strData = JSON.parse(JSON.stringify(data));

      strData.map((row, index) => {
        if (row["Cases"]) {
          row._children = row["Cases"]; //define rows with children
          delete row.Cases;

          let iconKey = "iconName";
          row[iconKey] = "standard:case";
        }
      });
      this.myData = strData;
    }
  }

  handleRowAction(event) {
    const action = event.detail.action;
    const row = event.detail.row;
    switch (action.name) {
      case "edit_record":
        this[NavigationMixin.Navigate]({
          type: "standard__objectPage",
          attributes: {
            objectApiName: "Case",
            actionName: "edit",
            recordId: row.Id,
          },
        });
        break;
    }
  }
}

使用 创建表。lightning-tree-grid

<!--myTreeGrid.html-->
<template>
  <lightning-tree-grid
    columns={columns}
    data={myData}
    key-field="Id"
    onrowaction={handleRowAction}
  >
  </lightning-tree-grid>
</template>

更改表单显示密度

在 Lightning Experience 中,显示密度设置决定了显示内容的密度和字段标签的位置。在“设置”中为组织控制显示密度,用户还可以从其配置文件菜单中根据自己的喜好设置显示密度。

组织的舒适设置将标签放在字段的顶部,并在页面元素之间添加更多空间。相比之下,紧凑视图是更密集的视图,标签与字段位于同一行上,行与行之间的间距更小。舒适的环境类似于紧凑,但线条之间有更多的空间。

您可以将表单设计为遵循显示密度设置,或设置表单密度以覆盖显示密度设置。通过覆盖显示密度,您可以更好地控制标签位置,但不会影响间距。此外,您可以将表单中的各个字段设置为使用更改字段标签位置的变体。

在表单中使用组织的默认显示密度

lightning-record-edit-form、 ,并根据组织的显示密度进行调整,或者在您设置 时进行调整。lightning-record-view-formlightning-record-formdensity="auto"

<lightning-card icon-name="standard:contact" title="record-edit-form">
    <div class="slds-p-horizontal_small">
        <!-- Replace the record-id with your own -->
        <lightning-record-edit-form record-id="003RM0000066Y82YAE"
                                  object-api-name="Contact"
                                  density="auto">
            <lightning-messages></lightning-messages>
            <lightning-input-field field-name="FirstName"></lightning-input-field>
            <lightning-input-field field-name="LastName"></lightning-input-field>
            <lightning-input-field field-name="Email"></lightning-input-field>
            <lightning-input-field field-name="Phone"></lightning-input-field>
        </lightning-record-edit-form>
    </div>
</lightning-card>

覆盖组织的显示密度

要覆盖组织的显示密度,请指定 或 。该属性不支持该值。如果组织的显示密度设置为舒适,则默认情况下标签和字段位于同一行上。density="compact"density="comfy"cozydensity

下表列出了组织的显示密度设置,以及它们与 、 和 上的表单密度的关系。lightning-record-edit-formlightning-record-view-formlightning-record-form

组织显示密度形态密度字段标签对齐
舒适auto(默认)或comfy标签位于字段上方
 compact标签和字段在同一行上
舒适auto(默认)或compact标签和字段在同一行上
 comfy标签位于字段上方
紧凑的auto(默认)或compact标签和字段在同一行上
 comfy标签位于字段上方

减少标签和字段之间的空间

当表单密度为 时,标签和字段对于较大区域中的单个列表单来说可能显得太远。要在表单使用紧凑密度时减小标签和字段之间的间距,请使用 on 或 上的类。compactslds-form-element_1-collightning-input-fieldlightning-output-field

<lightning-card icon-name="standard:contact" title="record-edit-form">
    <div class="slds-p-horizontal_small">
        <!-- Replace the record-id with your own -->
        <lightning-record-edit-form record-id="003RM0000066Y82YAE"
                                  object-api-name="Contact"
                                  density="compact">
            <lightning-messages></lightning-messages>
            <lightning-input-field field-name="FirstName" class="slds-form-element_1-col"></lightning-input-field>
            <lightning-input-field field-name="LastName" class="slds-form-element_1-col"></lightning-input-field>
            <lightning-input-field field-name="Email" class="slds-form-element_1-col"></lightning-input-field>
            <lightning-input-field field-name="Phone" class="slds-form-element_1-col"></lightning-input-field>

        </lightning-record-edit-form>
    </div>
</lightning-card>

在表单域上设置标签变体

如果您希望特定字段的标签和字段对齐方式与表单使用的标签和字段对齐方式不同,则可以设置变体。变体将覆盖该字段的显示密度。lightning-input-field

lightning-input-field支持以下变体:(默认)、、 和 。standardlabel-hiddenlabel-inlinelabel-stacked

此示例显示两个带有内联标签的输入字段,而其余字段的标签则由于表单密度舒适而显示在字段顶部。

<lightning-card icon-name="standard:contact" title="record-edit-form">
    <div class="slds-p-horizontal_small">
        <!-- Replace the recordId with your own -->
        <lightning-record-edit-form record-id="003RM0000066Y82YAE"
                                  object-api-name="Contact"
                                  density="comfy">
            <lightning-messages></lightning-messages>
            <lightning-input-field field-name="FirstName" variant="label-inline"></lightning-input-field>
            <lightning-input-field field-name="LastName" variant="label-inline"></lightning-input-field>
            <lightning-input-field field-name="Email"></lightning-input-field>
            <lightning-input-field field-name="Phone"></lightning-input-field>
        </lightning-record-edit-form>
    </div>
</lightning-card>

lightning-output-field支持以下变体:(默认)和 .standardlabel-hidden

本示例在表单密度为 时显示不带标签的输出字段值。隐藏标签可用于辅助技术。comfy

<lightning-card icon-name="standard:contact" title="record-view-form">
    <div class="slds-p-horizontal_small">
        <!-- Replace the recordId with your own -->
        <lightning-record-view-form record-id="003RM0000066Y82YAE"
                                  object-api-name="Contact"
                                  density="comfy">
            <lightning-messages></lightning-messages>
            <lightning-output-field field-name="FirstName" variant="label-hidden"></lightning-output-field>
            <lightning-output-field field-name="LastName" variant="label-hidden"></lightning-output-field>
            <lightning-output-field field-name="Email" variant="label-hidden"></lightning-output-field>
            <lightning-output-field field-name="Phone" variant="label-hidden"></lightning-output-field>
        </lightning-record-view-form>
    </div>
</lightning-card>

此外,要在标签变体为 时减小标签和字段之间的间距,请使用 上的类。label-inlineslds-form-element_1-collightning-input-field

使用注意事项

管理员可以在“密度设置”设置页面上为组织设置默认显示密度。用户可以随时选择自己的显示密度。管理员无法覆盖用户的显示密度设置。组织的默认显示设置取决于 Salesforce 版本。密度更改不适用于 Salesforce Classic、Experience Cloud 站点或 Salesforce 移动应用程序。有关详细信息,请参阅配置用户界面设置。

使用记录的注意事项

使用基本组件处理记录后,您无需额外配置即可使用 Lightning 数据服务,但需要考虑一些自定义用例。

请考虑以下用例。

  • 若要显示基于记录类型的记录窗体(包括基于记录类型的选择列表值),请获取记录类型 ID。
  • 若要通知父组件记录提交成功,请调度和处理自定义事件。

获取记录类型 ID

选择列表字段根据您的记录类型显示值。使用 or 时,如果一个对象上有多个记录类型,并且没有默认记录类型,则必须提供记录类型 ID。否则,将使用默认记录类型 Id。lightning-record-formlightning-record-edit-form

若要检索记录类型信息,请使用 getObjectInfo 线路适配器。

示例:使用 record-type-id 属性

通过提供属性,根据记录类型显示记录创建表单。此示例显示可以放置在客户记录页上的窗体。表单显示字段,其中包括一个选择列表,其值基于给定的记录类型 ID。record-type-id

<template>
  <lightning-card title="Record Form with Record Type" icon-name="standard:account">
    <div lwc:if={objectInfo.data} class="slds-m-around_medium">
      <lightning-record-form
        object-api-name={objectApiName}
        record-type-id={recordTypeId}
        fields={fields}
      >
      </lightning-record-form>
    </div>
  </lightning-card>
</template>

导入模块和对帐户对象的引用。getter 返回与记录类型名称匹配的 Id。getObjectInforecordTypeIdSpecial Account

import { LightningElement, api, wire, track } from "lwc";
import { getObjectInfo } from "lightning/uiObjectInfoApi";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";
import PHONE_FIELD from "@salesforce/schema/Account.Phone";
import INDUSTRY_FIELD from "@salesforce/schema/Account.Industry";

export default class RecordFormWithRecordType extends LightningElement {
  // Flexipage provides recordId and objectApiName
  @api recordId;
  @api objectApiName;

  @track objectInfo;

  // Define fields to display in form
  // Industry field is a picklist
  fields = [NAME_FIELD, PHONE_FIELD, INDUSTRY_FIELD];

  @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
  objectInfo;

  get recordTypeId() {
    const rtis = this.objectInfo.data.recordTypeInfos;
    return Object.keys(rtis).find((rti) => rtis[rti].name === "Special Account");
  }
}

该属性返回组织中可用的记录类型 ID 的映射。recordTypeInfos

处理窗体上的自定义事件

、 和 组件通过 、 和 属性进行事件处理。要将表单数据传递到容器组件,请创建自定义事件。lightning-record-formlightning-record-edit-formlightning-record-view-formonsubmitonsuccessonerror

假设你有一个容器组件,并且你的表单在组件中。让我们将新记录的 Id 传递给容器组件。在 中,创建一个 的实例。c-wrapperc-account-creatorc-wrapperc-account-creator

<!-- wrapper.html -->
<template>
  <c-account-creator onnewrecord={handleNewRecord}></c-account-creator>
</template>

成功创建记录后,处理程序将调度该事件,然后该处理程序将调用具有记录 ID 的方法。newrecordonsuccesslightning-record-formhandleNewRecord

// wrapper.js
import { LightningElement } from "lwc";

export default class Wrapper extends LightningElement {
  recordId;

  /**
   * Handles the new record event.
   */
  handleNewRecord(evt) {
    const recordId = evt.detail.data.id;
    this.recordId = recordId;
  }
}

在 中,该属性显示一条消息,其中包含新创建的记录的 ID。c-account-creatorcreateStatus

注意

该 ID 在事件中不可用。使用该事件返回 Id。submitsuccess

<!-- c-account-creator -->
<template>
  <lightning-record-form
    object-api-name={accountObject}
    fields={accountFields}
    mode="edit"
    onsuccess={handleAccountCreated}
  >
  </lightning-record-form>
  <span class="slds-m-around_small status">{createStatus}</span>
</template>

该方法处理事件。handleAccountCreatedsuccess

import { LightningElement } from "lwc";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";
import WEBSITE_FIELD from "@salesforce/schema/Account.Website";

/**
 * Creates Account records.
 */
export default class AccountCreator extends LightningElement {
  /** Status message when creating an Account. */
  createStatus = "";

  accountObject = ACCOUNT_OBJECT;

  accountFields = [NAME_FIELD, WEBSITE_FIELD];

  /** Handles successful Account creation. */
  handleAccountCreated(evt) {
    this.createStatus = `Account record created. Id is ${evt.detail.id}.`;

    const event = new CustomEvent("newrecord", {
      detail: { data: evt.detail },
    });
    this.dispatchEvent(event);
  }
}

使用 Salesforce 数据

Salesforce 为 Lightning Web 组件提供了多种处理数据的方法。始终从最简单的解决方案开始,然后转向允许根据需要进行更多自定义的解决方案。

数据准则

处理 Salesforce 数据的最简单方法是使用基于 Lightning 数据服务 (LDS) 构建的基本 Lightning 组件。 如果您需要更大的灵活性,请直接使用 Lightning 数据服务线路适配器。每个电线适配器提供不同的数据和元数据。线路服务将数据和元数据预配到组件。

如果要在一个操作中发送多个查询,请从 GraphQL 线路适配器开始。最后,如果 GraphQL 和 LDS 电线适配器不够灵活,请使用 Apex。

重要

Lightning 数据服务支持所有自定义对象和用户界面 API 支持的所有标准对象。不支持外部对象、个人帐户和自定义元数据类型。Lightning 数据服务不会产生任何 API 使用调用,但它会受到一般限制的约束,例如返回的记录数。

使用基于 Lightning 数据服务构建的基本 Lightning 组件

Lightning 数据服务为您管理数据;对记录的更改反映在基于记录构建的所有技术中。

基于 Lightning 数据服务构建的基本 Lightning 组件是 、 和 。它们提供了一个用于查看、创建和编辑记录的 UI,类似于 Salesforce 中的记录详细信息页面。lightning-record-formlightning-record-edit-formlightning-record-view-form

使用这些组件可以:lightning-record*form

  • 创建元数据驱动的 UI 或基于表单的 UI,类似于 Salesforce 中的记录详细信息页面。
  • 根据字段元数据显示记录值。
  • 显示或隐藏本地化的字段标签。
  • 在自定义字段上显示帮助文本。
  • 执行客户端验证并强制执行验证规则。

使用组件时,请考虑以下准则。lightning-record*form

闪电记录形式

此组件是显示表单以创建、编辑或查看记录的最简单方法。当用户开始编辑字段时,表单会自动在查看模式和编辑模式之间切换。该组件使用对象的默认记录布局,并支持多列。它加载对象的紧凑布局或完整布局中的所有字段,或仅加载您指定的字段。

注意

若要提高性能,请尽可能指定字段而不是布局。仅当您希望管理员(而不是组件)控制所置备的字段时,才指定布局。该组件必须处理接收分配给上下文用户布局的每个字段。

lightning-record-edit-form 和 lightning-record-view-form

若要自定义表单显示或提供记录数据的自定义呈现,请使用 (添加或更新记录) 和 (查看记录)。 使您能够使用组件预填充字段值。lightning-record-edit-formlightning-record-view-formlightning-record-edit-formlightning-input-field

请参阅使用基本组件处理记录。

注意

若要创建自定义 UI,或者如果不需要元数据驱动的 UI,请使用线路适配器。

使用 Lightning Data Service 有线适配器和功能

和模块提供闪电数据服务 (LDS) 电线适配器和功能。lightning/uiGraphQLApilightning/ui*Api

GraphQL 有线适配器

GraphQL 线路适配器使用 LDS 管理您的数据。它提供了一个端点,允许您在没有 Apex 的情况下查询确切的字段和对象。与其他 LWC 电线适配器相比,它减少了请求有效负载,并最大限度地减少了为用户获取数据所需的请求数量。

如果您通过其他有线适配器替换多个请求,GraphQL 的性能可能会更高。在处理大量记录时,电线适配器的分页功能还可以帮助您轻松高效地呈现数据。

借助 Lightning Data Service 的共享缓存、字段级安全性以及用户和组织权限,您可以在以下场景中使用 GraphQL 线路适配器功能:

  • 在一次操作中发送多个查询
  • 请求多个对象的记录数据
  • 具有父子关系的查询记录
  • 按条件筛选并订购查询结果
  • 使用动态记录 ID

请参阅适用于 LWC 的 GraphQL API。

lightning/ui*Api 线适配器和功能

要访问原始记录数据,以便您可以执行业务逻辑或创建需要比 LDS 基本组件允许的更多自定义的表单,请使用 指定 getRecord Lightning 数据服务线路适配器。@wire

若要显示字段值列表(如联系人姓名列表),请使用 getListUi

您可以使用多个线路适配器为多个记录设置数据,但每个操作都是一个独立的事务。若要在单个事务中使用多个记录,请使用 Apex。

要创建、编辑或删除单个记录,请调用 lightning/ui*Api 模块中的 、 和 函数。与 LDS 电线适配器一样,要处理多条记录,可以使用多个功能,但每个操作都是一个独立的事务。若要在单个事务中使用多个记录,请使用 Apex。createRecordupdateRecorddeleteRecord

注意

要提高性能,请使用返回用例所需最少数据量的线路适配器。 例如,GraphQL 线路适配器仅返回您查询的数据。相反,除了指定的字段外,LDS 连线适配器还返回子关系和布局类型。getRecord

请参阅使用线路服务获取数据并创建记录。

使用 Apex

如果您无法使用基本组件,并且无法使用 Lightning 数据服务线路适配器或功能,请使用 Apex。

与 Lightning 数据服务数据不同,Apex 数据不受管理;您必须刷新数据。如果 Apex 数据是通过线路服务预配的,请通过调用 refreshApex() 来刷新它,该数据使用线路配置来获取数据并更新缓存。如果代码必须调用 Apex 方法,若要刷新数据,请调用该方法,然后调用 notifyRecordUpdateAvailable(recordIds) 来更新缓存。

注意

不推荐使用 to 刷新来自非 Apex 电线适配器的数据。若要刷新非 Apex 线路适配器返回的记录数据,请改用 notifyRecordUpdateAvailable(recordIds)。refreshApex

在以下场景中使用 Apex:

  • 使用用户界面 API 不支持的对象,如 Task 和 Event。
  • 使用用户界面 API 不支持的操作,例如按条件加载记录列表(例如,加载金额> $1M 的前 200 个帐户)。
  • 执行事务性操作。例如,创建一个客户并创建一个与新客户关联的商机。如果任一创建失败,则回滚整个事务。
  • 以命令方式调用方法,而不是通过有线服务调用方法。您可能希望以命令方式调用方法以响应单击按钮,或者延迟加载到关键路径之外。当您以命令方式调用 Apex 方法时,若要刷新数据,请再次调用 Apex 方法。

请参阅调用 Apex 方法。

重要

为了防止代码复杂性和不必要的副作用,数据应该沿一个方向流动,从父级流向子级。要触发突变,组件应向其父组件发送事件。传递给组件的对象是只读的。若要更改数据,组件应创建要更改的对象的浅拷贝。在处理数据时,了解这些概念非常重要。请参阅数据流。

Apex 和 Lightning 数据服务注意事项

根据您的使用案例,您可以结合使用 Apex 和 Lightning 数据服务 (LDS) 线路适配器。在处理 Apex 和 LDS 返回的数据时,请考虑这些准则。

  • Apex 不与 LDS 共享数据缓存或数据存储。在联机和脱机条件下,使用 Apex 获取的数据可能与使用 LDS 电线适配器获取的数据不一致。例如,如果您的组件使用 Apex 获取数据,然后使用 GraphQL 线路适配器对相同数据执行搜索查询,则它可能会出现意外行为。
  • 使用 SOQL 获取数据时,请在执行提取和搜索查询时使用 GraphQL 线路适配器。一致地使用 GraphQL 线路适配器进行提取和搜索可以防止出现问题,例如在 GraphQL 线路适配器上检索相应的 null 值时获取 Apex 数据。

闪电数据服务

Lightning 数据服务中加载的记录将被缓存并在组件之间共享。如果页面由显示相同记录的组件组成,则所有组件都显示相同版本的记录。访问同一记录的组件的性能显著提高,因为无论有多少组件使用记录,记录都会加载一次。

在 Lightning Web 组件中,使用基于 Lightning 数据服务构建的这些技术对数据执行操作并访问元数据。

  • 基本组件:、 和lightning-record-edit-formlightning-record-formlightning-record-view-form
  • lightning/ui*Api 模块中的电线适配器和功能
  • GraphQL API 线路适配器

重要

Lightning 数据服务支持所有自定义对象和用户界面 API 支持的所有标准对象。不支持外部对象、个人帐户和自定义元数据类型。Lightning 数据服务不会产生任何 API 使用调用,但它会受到一般限制的约束,例如返回的记录数。

Lightning 数据服务为您管理数据;对记录的更改反映在基于记录构建的所有技术中。相比之下,来自 Apex 的数据不受管理;您必须刷新数据。

如果 Lightning 数据服务检测到对记录或其支持的任何数据或元数据的更改,则使用相关适配器的所有组件都会接收新值。如果出现以下情况,将触发检测:@wire

  • 闪电网络组件会改变记录。
  • LDS 缓存条目过期,然后 Lightning Web 组件触发读取。对于同一用户,缓存条目和 Lightning Web 组件必须位于同一浏览器和应用程序(例如 Lightning Experience)中。@wire

Lightning 数据服务做了大量工作来使代码运行良好。

  • 逐步加载记录数据。
  • 在客户端上缓存结果。
  • 当依赖的 Salesforce 数据和元数据发生更改时,使缓存条目失效。
  • 通过批量处理和重复数据删除请求来优化服务器调用。

为了提高性能,Lightning 数据服务维护了通过线路适配器加载的记录数据的客户端缓存。从此缓存加载数据比从服务器请求数据更快。Lightning 数据服务从服务器重新请求记录数据,以满足缓存生命周期后发生的请求。但是,即使在缓存生存期结束之前,您也可能获得更新的记录数据。缓存生命周期可能会随着 Lightning 数据服务的持续改进而发生变化。

此外,Lightning 数据服务还为缓存在持久存储中的对象和布局元数据维护单独的超时。当您更改布局以在自定义对象上记录页面时,布局更改不会立即显示。当您重新加载页面时,记录页面会在超时后显示更新的布局。要立即查看布局更改,请注销并重新登录。持久缓存在具有安全浏览器缓存的组织中可用,默认情况下处于启用状态。

Lightning 数据服务建立在用户界面 API 之上。UI API 是 Salesforce 用于构建 Lightning Experience 和 Salesforce for iOS 和 Android 的公共 Salesforce。顾名思义,UI API 旨在使构建 Salesforce UI 变得容易。

UI API 在单个响应中提供数据和元数据。响应与 Salesforce 管理员对组织所做的布局更改相匹配。如果管理员从布局中删除字段,则该字段不会包含在对布局元数据请求的响应中。

UI API 响应还遵循 CRUD 访问、字段级安全设置和共享设置。这意味着该框架仅显示用户具有 CRUD 访问权限和 FLS 可见性的记录和字段。通过使用 Lightning 数据服务线路适配器 () 和基于它构建的组件,您可以获得所有这些功能。lightning/ui*Api

使用基本组件处理记录

要创建允许用户查看、编辑和创建 Salesforce 记录的表单,请使用 、 和 组件。lightning-record-formlightning-record-edit-formlightning-record-view-form

注意

这些组件是处理记录的最简单方法。它们提供用于查看、创建和编辑记录的 UI,类似于 Salesforce 中的记录详细信息页面。若要创建自定义 UI,或者如果不需要元数据驱动的 UI,请使用线路适配器。lightning-record*form

比较基本组件

比较组件以找到符合您用例的组件。lightning-record*form

这些组件提供窗体布局。字段标签使用基于组织中默认值的值。这些组件无需 Apex 代码即可处理记录创建、读取和更新更改。他们使用 Lightning 数据服务在组件之间缓存和共享记录更新。

  • lightning-record-edit-form – 显示可编辑的表单。
  • lightning-record-view-form – 显示只读表单。
  • lightning-record-form – 支持编辑、查看和只读模式。

注意

这些组件支持用户界面 API 支持的对象。不支持外部对象和个人帐户。请参阅支持的对象。

特征lightning-record-formlightning-record-view-formlightning-record-edit-form
创建记录是的 是的
编辑记录是的 是的
查看记录是的是的 
只读模式是的是的 
布局类型是的  
多列布局是的是的是的
字段的自定义布局 是的是的
记录数据的自定义呈现 是的是的

对于大多数用例,这提供了一个很好的起点。它组合并简化了 和 的功能。lightning-record-formlightning-record-view-formlightning-record-edit-form

指定布局类型或字段和模式,组件将负责布局、验证、CRUD 更改和错误处理。成功提交更改后,组件还会从编辑模式切换到查看模式。lightning-record-form

注意

若要提高性能,请尽可能指定字段而不是布局。仅当您希望管理员(而不是组件)控制所置备的字段时,才指定布局。该组件必须处理接收分配给上下文用户布局的每个字段。

对于需要自定义字段布局和自定义呈现记录数据的更高级用例,请使用 和 .lightning-record-view-formlightning-record-edit-form

如果需要比基本组件允许的更多自定义(例如自定义用户界面),请参阅生成自定义 UI 以创建和编辑记录并获取记录数据。

加载记录

加载记录可以完全在标记中使用和传入记录 ID 来完成。如果需要自定义布局,请使用 .如果您需要的自定义次数超出了基于表单的组件所允许的范围,请使用像 这样的电线适配器。lightning-record-formlightning-record-view-formgetRecord

使用 lightning-record-form 显示记录

显示记录的最简单方法是使用 .您可以在两种模式下显示记录。lightning-record-formview

使用启用了内联编辑的输出字段加载表单。可编辑字段具有编辑图标。如果用户单击编辑图标,则表单中的所有字段都将变为可编辑,并且表单将显示“提交”和“取消”按钮。read-only

只读模式仅加载包含输出字段的表单。表单不包括编辑图标或“提交”和“取消”按钮。

此代码在视图模式下显示具有指定字段的客户记录。字段标签和值根据显示密度设置进行显示。提供视图模式时为默认模式。record-id

<!-- myComponent.html -->
<template>
  <lightning-record-form record-id={recordId} object-api-name={objectApiName} fields={fields}>
  </lightning-record-form>
</template>

注意

若要提高性能,请尽可能指定字段而不是布局。仅当您希望管理员(而不是组件)控制所置备的字段时,才指定布局。该组件必须处理接收分配给上下文用户布局的每个字段。

使用您自己的记录 ID 或将示例放在客户记录页中以继承其记录 ID。

// myComponent.js
import { LightningElement, api } from "lwc";
export default class MyComponent extends LightningElement {
  @api recordId;
  @api objectApiName;
  fields = ["AccountId", "Name", "Title", "Phone", "Email"];
}

此示例使用 github.com/trailheadapps/lwc-recipes 存储库中的组件。recordFormDynamicContact

使用 lightning-record-view-form 显示具有自定义字段布局的记录

若要显示具有自定义字段布局的记录,请使用该组件。要编写表单字段,请使用组件。通过包含单个字段,您可以使用 Lightning Design System 实用程序类(如网格系统)设置自定义字段布局的样式。lightning-record-view-formlightning-output-field

<!-- recordViewCustomLayout.html -->
<template>
  <lightning-record-view-form record-id={recordId} object-api-name="Account">
    <div class="slds-grid">
      <div class="slds-col slds-size_1-of-2">
        <lightning-output-field field-name="Name"></lightning-output-field>
        <lightning-output-field field-name="Phone"></lightning-output-field>
      </div>
      <div class="slds-col slds-size_1-of-2">
        <lightning-output-field field-name="Industry"></lightning-output-field>
        <lightning-output-field field-name="AnnualRevenue"></lightning-output-field>
      </div>
    </div>
  </lightning-record-view-form>
</template>

使用您自己的记录 ID 或将示例放在客户记录页中以继承其记录 ID。

// recordViewCustomLayout.js
import { LightningElement, api } from "lwc";
export default class MyComponent extends LightningElement {
  // Expose a recordId property.
  @api recordId;
}

注意

您可以将表单设计为遵循组织的显示密度设置,或设置表单密度以覆盖显示密度设置。请参阅更改表单显示密度。

使用 getRecord 在自定义用户界面中显示记录数据

我们已经了解了记录字段如何使用 呈现。若要在自定义用户界面中呈现数据,请使用 getRecord 或 getRecords 线路适配器。lightning-output-field

此示例使用基本组件 来显示帐户名称。lightning-formatted-text

<!-- recordViewGetRecord.html -->
<template>
  <lightning-record-view-form record-id={recordId} object-api-name={accountObject}>
    <div class="slds-grid">
      <div class="slds-col slds-size_1-of-2">
        <!-- Other record data here -->
      </div>
      <div class="slds-col slds-size_1-of-2">
        <lightning-formatted-text value={nameValue} class="slds-text-heading_large">
        </lightning-formatted-text>
      </div>
    </div>
  </lightning-record-view-form>
</template>

在组件的 JavaScript 代码中,导入对对象和字段的引用。AccountAccount.Name

  • getRecord 连线适配器在组件中加载用于自定义渲染的字段,而不是 使用的标准。Namelightning-formatted-textlightning-output-fieldlightning-record-view-form
  • getFieldValue(record, field) 函数获取字段的值。Name
// recordViewGetRecord.js
import { LightningElement, api, wire } from "lwc";
/* Wire adapter to fetch record data */
import { getRecord, getFieldValue } from "lightning/uiRecordApi";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";

export default class AccountViewer extends LightningElement {
  /** Id of record to display. */
  @api recordId;

  /* Expose schema objects/fields to the template. */
  accountObject = ACCOUNT_OBJECT;

  /* Load Account.Name for custom rendering */
  @wire(getRecord, { recordId: "$recordId", fields: [NAME_FIELD] })
  record;

  /** Get the Account.Name value. */
  get nameValue() {
    return this.record.data ? getFieldValue(this.record.data, NAME_FIELD) : "";
  }
}

使用 getRecord 显示父记录中的数据

若要显示记录及其父记录中的数据,请使用 ,与上一个示例类似。使用表示法从父记录导入数据。lightning-record-view-formgetRecord{object}.{parentObject}.{field}

此示例使用基本组件 来显示客户记录所有者的电子邮件地址。lightning-formatted-email

<!-- recordViewGetRecordParent.html -->
<template>
  <lightning-record-view-form object-api-name={objectApiName} record-id={recordId}>
    <lightning-messages></lightning-messages>
    <lightning-output-field field-name={nameField}></lightning-output-field>

    <div class="slds-form-element slds-form-element_stacked">
      <span class="slds-form-element__label">Owner Email</span>
      <div class="slds-form-element__control">
        <lightning-formatted-email value={ownerField}> </lightning-formatted-email>
      </div>
    </div>
  </lightning-record-view-form>
</template>

组件的 JavaScript 代码导入对对象和字段的引用。AccountAccount.Owner.Email

  • getRecord 连线适配器在组件中加载用于自定义呈现的字段,而不是在 使用的标准中加载。Account.Owner.Emaillightning-formatted-emaillightning-output-fieldlightning-record-view-form
  • getFieldValue(record, field) 函数获取字段的值。Account.Owner.Email
// recordViewGetRecordParent.js
import { LightningElement, api, wire } from "lwc";

import { getRecord, getFieldValue } from "lightning/uiRecordApi";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";
import OWNER_EMAIL_FIELD from "@salesforce/schema/Account.Owner.Email";

export default class RecordViewFormParentData extends LightningElement {
  objectApiName = ACCOUNT_OBJECT;
  nameField = NAME_FIELD;

  @api recordId;
  @api objectApiName;

  /* Load Account.Owner.Email for custom rendering */
  @wire(getRecord, {
    recordId: "$recordId",
    fields: [OWNER_EMAIL_FIELD],
  })
  record;

  /* Get the Account.Owner.Email value. */
  get ownerField() {
    return this.record.data ? getFieldValue(this.record.data, OWNER_EMAIL_FIELD) : "";
  }
}

编辑记录

创建允许您编辑记录的表单的最简单方法是使用 .要自定义表单布局或预加载自定义值,请使用 .lightning-record-formlightning-record-edit-form

提示

如果您需要比这些组件提供的更大的灵活性,请参阅生成自定义 UI 以创建和编辑记录。

使用 lightning-record-form 编辑包含布局中的字段的记录

若要编辑记录,请使用 和 属性。当您提供记录 ID 时,组件默认使用视图模式,该模式显示带有编辑图标的输出字段。如果单击编辑图标,表单中所有可更新的字段都将变为可编辑。可编辑窗体显示一个用于更新记录的“保存”按钮,以及一个用于还原更改的“取消”按钮。record-idobject-api-name

该组件提供默认的提交和错误处理程序。错误会自动显示在表单顶部。

此示例创建一个窗体,该窗体允许用户从客户记录的紧凑布局中更新字段。它以两列布局显示字段。

<template>
  <lightning-record-form
    record-id={recordId}
    object-api-name={objectApiName}
    columns="2"
    mode="edit"
    layout-type="Compact"
  >
  </lightning-record-form>
</template>

注意

若要提高性能,请尽可能指定字段而不是布局。仅当您希望管理员(而不是组件)控制所置备的字段时,才指定布局。该组件必须处理接收分配给上下文用户布局的每个字段。

将此示例放在记录页上以继承其 和 属性。record-idobject-api-name

import { LightningElement, api } from "lwc";
export default class MyComponent extends LightningElement {
  @api recordId;
  @api objectApiName;
}

您可以使用自定义事件替代默认表单行为,例如在提交成功或失败时显示 Toast。有关更多信息,请参阅 lightning-record-form 参考文档。

使用 lightning-record-form 编辑具有特定字段的记录

您可以通过提供字段名称数组或导入对字段的引用来指定在可编辑表单上显示哪些字段。

注意

使用字符串传入对象和字段不提供编译时验证。在运行时之前,您不知道这些字段是否有效。建议从 导入对对象和字段的引用。@salesforce/schema

此示例使用 github.com/trailheadapps/lwc-recipes 存储库中的组件。它使用静态架构定义显示记录的可编辑表单。recordFormStaticContact

<template>
  <lightning-record-form
    object-api-name={objectApiName}
    record-id={recordId}
    fields={fields}
  ></lightning-record-form>
</template>

在 JavaScript 文件中导入字段引用。

import { LightningElement, api } from "lwc";

import ACCOUNT_FIELD from "@salesforce/schema/Contact.AccountId";
import NAME_FIELD from "@salesforce/schema/Contact.Name";
import TITLE_FIELD from "@salesforce/schema/Contact.Title";
import PHONE_FIELD from "@salesforce/schema/Contact.Phone";
import EMAIL_FIELD from "@salesforce/schema/Contact.Email";

export default class RecordFormStaticContact extends LightningElement {
  // Flexipage provides recordId and objectApiName
  @api recordId;
  @api objectApiName;

  fields = [ACCOUNT_FIELD, NAME_FIELD, TITLE_FIELD, PHONE_FIELD, EMAIL_FIELD];
}

使用 lightning-record-edit-form 编辑具有自定义布局的记录

要为表单字段提供自定义布局,请使用该组件。将字段传递给 ,这将显示基于记录字段类型的输入控件。本示例使用自定义布局显示多个字段。将此示例组件添加到联系人记录页以继承其 和 属性。lightning-record-edit-formlightning-input-fieldrecord-idobject-api-name

<template>
    <lightning-record-edit-form
        object-api-name={objectApiName}
        record-id={recordId}>
    <lightning-messages></lightning-messages>
    <div class="slds-grid">
        <div class="slds-col slds-size_1-of-2">
            <lightning-input-field field-name="Name"></lightning-input-field>
            <lightning-input-field field-name="Title"></lightning-input-field>
        </div>
        <div class="slds-col slds-size_1-of-2">
            <lightning-input-field field-name="Phone"></lightning-input-field>
            <lightning-input-field field-name="Email"></lightning-input-field>
        </div>
    </div>
    <div class="slds-m-top_medium">
        <lightning-button type="submit" variant="brand" label="Edit Contact"></lightning-button>
    </div>
</template>

lightning-record-edit-form自动处理表单提交和错误。要自动在表单字段的上方或下方显示错误消息,请在组件之前或之后添加。lightning-messageslightning-input-field

将表单重置为原始字段值

lightning-record-edit-form不像那样提供自己的“取消”和“保存”按钮。若要创建自己的“取消”按钮来还原字段值,请包含调用该方法的组件。将 替换为你自己的,或将此示例放在联系人记录页上以继承其属性。lightning-record-formlightning-buttonreset()record-idrecord-id

<template>
  <lightning-record-edit-form record-id={recordId} object-api-name="Contact">
    <lightning-messages></lightning-messages>
    <lightning-input-field field-name="FirstName"></lightning-input-field>
    <lightning-input-field field-name="LastName"></lightning-input-field>
    <lightning-input-field field-name="Email"></lightning-input-field>
    <lightning-input-field field-name="Phone"></lightning-input-field>
    <div class="slds-align_absolute-center slds-p-around_medium">
      <lightning-button
        class="slds-m-around_xx-small"
        label="Cancel"
        onclick={handleReset}
      ></lightning-button>
      <lightning-button
        class="slds-m-around_xx-small"
        label="Create Contact"
        type="submit"
        variant="brand"
      ></lightning-button>
    </div>
  </lightning-record-edit-form>
</template>

在组件上调用该方法。reset()lightning-input-field

import { LightningElement } from "lwc";

export default class FormResetExample extends LightningElement {
  @api recordId;

  handleReset(event) {
    const inputFields = this.template.querySelectorAll("lightning-input-field");
    if (inputFields) {
      inputFields.forEach((field) => {
        field.reset();
      });
    }
  }
}

使用自定义事件覆盖默认行为

lightning-record-edit-form自动处理表单提交和错误。要自动在表单字段的上方或下方显示错误消息,请在组件之前或之后添加。lightning-messageslightning-input-field

lightning-record-edit-form使您能够处理以下自定义事件。

  • error– 当表单返回服务器端错误时触发。
  • load– 在表单加载记录数据时触发。
  • submit– 提交表单时触发。
  • success– 在表单数据成功保存时触发。

此示例在表单提交时发生错误时显示 Toast。常见错误包括无法访问网络或表单中缺少必填字段。使用您自己的记录 ID 或将示例放在联系人记录页上以继承其记录 ID。

<template>
  <lightning-record-edit-form
    record-id={recordId}
    object-api-name="Contact"
    onerror={handleError}
  >
    <!--lightning-messages not needed here
                since we’re displaying a toast with the error message -->
    <!--<lightning-messages></lightning-messages>-->
    <lightning-input-field field-name="FirstName"></lightning-input-field>
    <lightning-input-field field-name="LastName"></lightning-input-field>
    <lightning-input-field field-name="Email"></lightning-input-field>
    <lightning-input-field field-name="Phone"></lightning-input-field>
    <lightning-button type="submit" variant="brand" label="Create Contact"></lightning-button>
  </lightning-record-edit-form>
</template>

若要显示 Toast,请导入模块。lightning/platformShowToastEvent

import { LightningElement } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class FormErrorExample extends LightningElement {
  @api recordId;

  handleError(event) {
    console.log(event.detail);
    this.dispatchEvent(
      new ShowToastEvent({
        title: "Error creating record",
        message: event.detail.message,
        variant: "error",
      }),
    );
  }
}

event.detail.message返回错误的一般说明。若要返回特定于字段的错误(如验证规则中的错误),请使用 ,它提供字段列表并记录异常错误。event.detail.output.fieldErrors

假设您在 、 和 字段中遇到验证规则错误,可能会返回如下内容。FirstNameLastNameEmailevent.detail.output.fieldErrors

{
  "Email": [
    {
      "constituentField": null,
      "duplicateRecordError": null,
      "errorCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
      "field": "Email",
      "fieldLabel": "Email",
      "message": "Enter a Salesforce email"
    }
  ],
  "Name": [
    {
      "constituentField": "FirstName",
      "duplicateRecordError": null,
      "errorCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
      "field": "Name",
      "fieldLabel": "First Name",
      "message": "Your first name should contain at least 2 characters"
    },
    {
      "constituentField": "LastName",
      "duplicateRecordError": null,
      "errorCode": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
      "field": "Name",
      "fieldLabel": "Last Name",
      "message": "Your last name should contain at least 2 characters"
    }
  ]
}

有关更多信息,请参阅 lightning-record-edit-form 参考文档。

使用 lightning-record-edit-form 编辑具有自定义验证的记录

我们建议您创建验证规则错误,以使用 nested in 强制执行字段验证,如使用自定义事件覆盖默认行为中所述。lightning-input-fieldlightning-record-edit-form

lightning-input-field不支持客户端自定义验证。如果要实现自己的客户端验证,请考虑改用嵌套。lightning-inputlightning-record-edit-form

将此示例放在客户记录页上。此示例将组件连接到“名称”字段进行编辑。lightning-input

<!-- recordEditFormStaticAccount.html -->
<template lwc:if={account.data}>
  <lightning-record-edit-form
    object-api-name={objectApiName}
    record-id={recordId}
    onsubmit={handleSubmit}
  >
    <lightning-input
      label="Name"
      value={name}
      onchange={handleChange}
      class="slds-m-bottom_x-small"
    ></lightning-input>

    <lightning-button class="slds-m-top_small" type="submit" label="Update Account Name">
    </lightning-button>
  </lightning-record-edit-form>
</template>

使用电线适配器将组件连接到 Salesforce 字段,并提供您自己的标签。使用 时会自动完成接线,但使用 时不会。使用 上的事件处理程序提交记录数据。在事件处理程序中,使用用户输入值执行验证检查并更新字段。lightning-inputgetRecordlightning-input-fieldlightning-inputonsubmitlightning-record-edit-formsetCustomValidity()

// recordEditFormStaticAccount.js
import { LightningElement, api, wire } from "lwc";
import { getRecord } from "lightning/uiRecordApi";

const FIELDS = ["Account.Name"];

export default class RecordEditFormStaticAccount extends LightningElement {
  @api recordId;
  @api objectApiName;
  inputVal = "";

  @wire(getRecord, { recordId: "$recordId", fields: FIELDS })
  account;

  get name() {
    return this.account.data.fields.Name.value;
  }

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

  handleSubmit(event) {
    event.preventDefault();
    const inputCmp = this.template.querySelector("lightning-input");
    const value = inputCmp.value;
    // perform validation check
    if (!value.includes("Burlington")) {
      inputCmp.setCustomValidity("The account name must include 'Burlington'");
    } else {
      // if there was a custom error before, reset it
      inputCmp.setCustomValidity("");
      const fields = event.detail.fields;
      fields.Name = this.inputVal;
      this.template.querySelector("lightning-record-edit-form").submit(fields);
    }
    // shows the error right away without user interaction
    inputCmp.reportValidity();
  }
}

lightning-input提供用于自定义验证的属性,如 、 和 。有关详细信息,请参阅 lightning-input 参考文档。minmaxpattern

注意

lightning-input-field是与 一起使用的首选组件。仅当验证规则错误不符合要求时才使用 with。lightning-record-edit-formlightning-inputlightning-record-edit-form

创建记录

创建允许用户创建记录的表单的最简单方法是使用 。要自定义表单布局或预加载自定义值,请使用 .lightning-record-formlightning-record-edit-form

提示

如果您需要比这些组件提供的更大的灵活性,请参阅生成自定义 UI 以创建和编辑记录。

使用 lightning-record-form 创建记录

若要使用 创建记录,请省略该属性。此示例使用帐户对象及其字段的导入引用创建记录。窗体显示一个用于更新记录的“保存”按钮,以及一个用于还原更改的“取消”按钮。lightning-record-formrecord-id

<template>
  <lightning-record-form
    object-api-name={accountObject}
    fields={myFields}
    onsuccess={handleAccountCreated}
  >
  </lightning-record-form>
</template>

字段导入将传递到数组中。导入引用时,将在编译时验证引用。将此示例放在记录页上以继承其 和 属性。myFieldsrecord-idobject-api-name

import { LightningElement } from "lwc";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";
import WEBSITE_FIELD from "@salesforce/schema/Account.Website";

/**
 * Creates Account records.
 */
export default class AccountCreator extends LightningElement {
  accountObject = ACCOUNT_OBJECT;
  myFields = [NAME_FIELD, WEBSITE_FIELD];

  handleAccountCreated() {
    // Run code when account is created.
  }
}

成功保存数据后,字段将显示铅笔图标,表示内联编辑可用。此视图将一直显示,直到您刷新或重新加载页面。

内联编辑

使用 lightning-record-edit-form 创建具有自定义字段布局的记录

用于自定义字段布局或数据呈现。lightning-record-edit-form

传入所需的字段,这将显示基于记录字段类型的输入控件。若要启用错误消息的自动显示,请包含该组件。lightning-input-fieldlightning-messages

<template>
  <lightning-record-edit-form object-api-name={accountObject} onsuccess={handleAccountCreated}>
    <lightning-messages></lightning-messages>
    <div class="slds-grid">
      <div class="slds-col slds-size_1-of-2">
        <lightning-input-field field-name={nameField}></lightning-input-field>
        <lightning-input-field field-name={websiteField}></lightning-input-field>
      </div>
      <div class="slds-col slds-size_1-of-2">
        <!-- More lightning-input-field components here -->
      </div>
    </div>
    <lightning-button type="submit" variant="brand" label="Create Account"></lightning-button>
  </lightning-record-edit-form>
</template>

要访问所需的字段,请使用字段 imports from 。导入对对象和字段的引用可确保代码正常工作,即使对象和字段名称发生更改也是如此。@salesforce/schema

import { LightningElement } from "lwc";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";
import WEBSITE_FIELD from "@salesforce/schema/Account.Website";

/**
 * Creates Account records.
 */
export default class AccountCreator extends LightningElement {
  accountObject = ACCOUNT_OBJECT;
  nameField = NAME_FIELD;
  websiteField = WEBSITE_FIELD;

  handleAccountCreated() {
    // Run code when account is created.
  }
}

使用 lightning-record-edit-form 预填充字段值

要在表单显示时提供字段值,请使用 上的属性。valuelightning-input-field

本示例显示一个窗体,其中包含帐户名称字段的自定义值。单击该按钮时,窗体将创建客户记录。

<template>
  <lightning-record-edit-form object-api-name="Account">
    <lightning-input-field field-name="Name" value="My Field Value"> </lightning-input-field>
    <lightning-button class="slds-m-top_small" type="submit" label="Create new"> </lightning-button>
  </lightning-record-edit-form>
</template>

若要在加载表单时以编程方式设置值,请在 JavaScript 中提供该值。此示例使用该属性设置值。以后可以通过编程方式设置此值,如调用该方法的事件处理程序所示。myValueonclickoverrideValue

<template>
  <lightning-record-edit-form object-api-name="Account">
    <lightning-input-field field-name="Name" value={myValue}></lightning-input-field>
    <lightning-button class="slds-m-top_small" type="submit" label="Create new"></lightning-button>
  </lightning-record-edit-form>

  <lightning-button label="Override Value" onclick={overrideValue}></lightning-button>
</template>
import { LightningElement, api } from "lwc";
export default class FieldValueCreateExample extends LightningElement {
  myValue = "My Account Name";
  overrideValue(event) {
    this.myValue = "My New Name";
  }
}

将表单重置为原始字段值

lightning-record-edit-form不像那样提供自己的“取消”和“保存”按钮。若要创建自己的“取消”按钮来还原字段值,请包含调用该方法的组件。替换为您自己的。有关示例,请参阅编辑记录。lightning-record-formlightning-buttonreset()record-id

使用自定义事件覆盖默认行为

lightning-record-edit-form自动处理表单提交和错误。要自动在表单字段的上方或下方显示错误消息,请在组件之前或之后添加。lightning-messageslightning-input-field

lightning-record-edit-form使您能够处理以下自定义事件。

  • error– 当表单返回服务器端错误时触发。
  • load– 在表单加载记录数据时触发。
  • submit– 在表单提交更改的记录数据时触发。
  • success– 在表单提交更改的记录数据时触发。

有关示例,请参阅编辑记录。

构建自定义 UI 以创建和编辑记录

如果组件无法为您的用例提供足够的灵活性,请使用 JavaScript API 构建用于创建和编辑记录的 UI。lightning-record*form

提示

在使用 JavaScript API 之前,请查看组件是否满足您的需求。请参见比较基本组件。lightning-record*form

本文档介绍了 lwc-recipes 存储库中的组件。该组件要求输入一个名称,并使用该名称创建一个帐户。创建帐户时,它会呈现帐户 ID。ldsCreateRecord

若要通过 JavaScript 创建记录,请调用该函数。此组件调用处理程序中的函数。createRecord()createAccount

<template>
  <lightning-card title="LdsCreateRecord" icon-name="standard:record">
    <div class="slds-m-around_medium">
      <lightning-input label="Id" disabled value={accountId}></lightning-input>
      <lightning-input
        label="Name"
        onchange={handleNameChange}
        class="slds-m-bottom_x-small"
      ></lightning-input>
      <lightning-button
        label="Create Account"
        variant="brand"
        onclick={createAccount}
      ></lightning-button>
    </div>
  </lightning-card>
</template>

组件的 JavaScript 从模块导入。要显示错误处理通知,请从 导入。然后,它导入对 Account 对象和 Account name 字段的引用。createRecordlightning/uiRecordApiShowToastEventlightning/platformShowToastEvent

将帐户名称传递给 ,这将根据您提供的名称创建帐户记录。createRecord(recordInput)

接下来,提供一个使用 创建客户记录的处理程序。在此示例中,当用户输入帐户名称时,该方法将处理组件 HTML 文件中的事件。createRecord(recordInput)handleNameChange(event)change

import { LightningElement } from "lwc";
import { createRecord } from "lightning/uiRecordApi";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import ACCOUNT_OBJECT from "@salesforce/schema/Account";
import NAME_FIELD from "@salesforce/schema/Account.Name";

export default class LdsCreateRecord extends LightningElement {
  accountId;
  name = "";

  handleNameChange(event) {
    this.accountId = undefined;
    this.name = event.target.value;
  }
  createAccount() {
    const fields = {};
    fields[NAME_FIELD.fieldApiName] = this.name;
    const recordInput = { apiName: ACCOUNT_OBJECT.objectApiName, fields };
    createRecord(recordInput)
      .then((account) => {
        this.accountId = account.id;
        this.dispatchEvent(
          new ShowToastEvent({
            title: "Success",
            message: "Account created",
            variant: "success",
          }),
        );
      })
      .catch((error) => {
        this.dispatchEvent(
          new ShowToastEvent({
            title: "Error creating record",
            message: error.body.message,
            variant: "error",
          }),
        );
      });
  }
}

该函数返回一个 Promise 对象,该对象在创建记录时进行解析。要将记录数据返回给组件,请使用该块。此组件返回 并将其设置为属性。createRecord(recordInput)then()account.idaccountId

使用块处理错误。catch()

提示

若要更新记录,请参阅 updateRecord(recordInput, clientOptions),其中包含示例代码。要使用 Apex 更新多条记录,请参阅使用内联编辑在表中显示数据。

与事件通信

Lightning Web 组件调度标准 DOM 事件。组件还可以创建和调度自定义事件。使用事件向上传达组件包含层次结构。例如,子组件 调度一个事件,以告知其父组件 ,用户选择了它。c-todo-itemc-todo-app

Lightning Web 组件中的事件基于 DOM 事件构建,DOM 事件是每个浏览器中可用的 API 和对象的集合。

DOM 事件系统是一种包含这些元素的编程设计模式。

  • 事件名称,称为类型
  • 用于初始化事件的配置
  • 发出事件的 JavaScript 对象

要创建事件,我们强烈建议使用界面。在 Lightning Web 组件中,提供跨浏览器的更一致的体验。它不需要设置或样板,它允许您通过属性传递任何类型的数据,这使得它很灵活。CustomEventCustomEventdetail

Lightning Web 组件实现了该接口,允许它们调度事件、侦听事件和处理事件。EventTarget

提示

若要向下通信组件包含层次结构,请通过 HTML 属性将属性传递给子级,或调用其公共方法。要在组件之间进行通信,请使用 Lightning 消息服务或发布订阅实用程序。

创建和调度事件

在组件的 JavaScript 类中创建和调度事件。若要创建事件,请使用构造函数。若要调度事件,请调用该方法。CustomEvent()EventTarget.dispatchEvent()

构造函数有一个必需的参数,该参数是指示事件类型的字符串。作为组件作者,您可以在创建事件时命名事件类型。您可以使用任何字符串作为事件类型。但是,我们建议您遵守 DOM 事件标准。CustomEvent()

  • 没有大写字母
  • 无空格
  • 使用下划线分隔单词

不要在事件名称前面加上字符串 ,因为内联事件处理程序名称必须以字符串 开头。如果调用事件,则标记将为 。注意双倍的单词,这很令人困惑。onononmessage<c-my-component ononmessage={handleMessage}>onon

使用自定义事件进行跨组件通信

https://youtube.com/watch?v=hIv22aTl3-g

示例:调度和处理事件

让我们跳到一些代码中。

该组件包含“上一个”和“下一个”按钮。当用户单击按钮时,组件将创建并调度事件。您可以将组件拖放到需要“上一个”和“下一个”按钮的任何组件中。该父组件侦听事件并处理它们。c-paginatorpreviousnextpaginator

<!-- paginator.html -->
<template>
  <lightning-layout>
    <lightning-layout-item>
      <lightning-button
        label="Previous"
        icon-name="utility:chevronleft"
        onclick={previousHandler}
      ></lightning-button>
    </lightning-layout-item>
    <lightning-layout-item flexibility="grow"></lightning-layout-item>
    <lightning-layout-item>
      <lightning-button
        label="Next"
        icon-name="utility:chevronright"
        icon-position="right"
        onclick={nextHandler}
      ></lightning-button>
    </lightning-layout-item>
  </lightning-layout>
</template>

当用户单击按钮时,将执行 or 函数。这些函数创建和调度 和 事件。previousHandlernextHandlerpreviousnext

// paginator.js
import { LightningElement } from "lwc";

export default class Paginator extends LightningElement {
  previousHandler() {
    this.dispatchEvent(new CustomEvent("previous"));
  }

  nextHandler() {
    this.dispatchEvent(new CustomEvent("next"));
  }
}

这些事件是简单的“某事发生”事件。它们不会将数据有效负载传递到 DOM 树上,它们只是宣布用户单击了一个按钮。

让我们进入一个名为 的组件,它侦听并处理 和 事件。paginatorc-event-simplepreviousnext

要侦听事件,请使用语法为 的 HTML 属性。由于我们的事件类型是 和 ,因此侦听器是 和 。oneventtypepreviousnextonpreviousonnext

<!-- eventSimple.html -->
<template>
  <lightning-card title="EventSimple" icon-name="custom:custom9">
    <div class="slds-m-around_medium">
      <p class="slds-m-vertical_medium content">Page {page}</p>
      <c-paginator onprevious={previousHandler} onnext={nextHandler}></c-paginator>
    </div>
  </lightning-card>
</template>

当收到 和 事件时,并增加和减少页码。c-event-simplepreviousnextpreviousHandlernextHandler

// eventSimple.js
import { LightningElement } from "lwc";

export default class EventSimple extends LightningElement {
  page = 1;

  previousHandler() {
    if (this.page > 1) {
      this.page = this.page - 1;
    }
  }

  nextHandler() {
    this.page = this.page + 1;
  }
}

提示

查看 github.com/trailheadapps/lwc-recipes 存储库中的 c-event-simple 组件和 c-paginator 组件。

在事件中传递数据

若要将数据传递到接收组件,请在构造函数中设置属性。接收组件访问事件侦听器的处理程序函数中属性中的数据。detailCustomEventdetail

注意

该接口对属性不施加任何类型要求或结构。但是,仅发送原始数据很重要。JavaScript 通过引用传递除基元之外的所有数据类型。如果组件在其属性中包含对象,则任何侦听器都可以在组件不知情的情况下更改该对象。这是一件坏事!最佳做法是仅发送基元,或者在将数据添加到属性之前将数据复制到新对象。将数据复制到新对象可确保仅发送所需的数据,并且接收方无法更改数据。CustomEventdetaildetaildetail

让我们看一下 lwc-recipes 存储库中的组件。c-event-with-data

具有显示姓名、职务、电话号码和电子邮件地址的选定联系人的联系人列表。

联系人列表中的每个项目都是一个嵌套组件。c-contact-list-item

该组件使用执行函数的事件侦听器将联系人姓名和图片包装在锚标记中。c-contact-list-itemonclickselectHandler

<!-- contactListItem.html -->
<template>
    <a href="#" onclick={selectHandler}>
        <lightning-layout vertical-align="center">
            <lightning-layout-item>
                <img src={contact.Picture__c}></img>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small">
                <p>{contact.Name}</p>
            </lightning-layout-item>
        </lightning-layout>
    </a>
</template>

当用户单击以选择联系人时,该组件将创建并调度被调用的 .该事件包括数据,即所选联系人的 ID。父级 , 使用联系人引用来显示有关联系人的详细信息。CustomEventselecteddetail: this.contact.Idc-event-custom

// contactListItem.js
import { LightningElement, api } from "lwc";

export default class ContactListItem extends LightningElement {
  @api contact;

  selectHandler(event) {
    // Prevents the anchor element from navigating to a URL.
    event.preventDefault();

    // Creates the event with the contact ID data.
    const selectedEvent = new CustomEvent("selected", { detail: this.contact.Id });

    // Dispatches the event.
    this.dispatchEvent(selectedEvent);
  }
}

该组件侦听属性中的事件,并在事件处理程序中处理该事件。c-event-with-dataselectedonselectedcontactSelected

<!-- eventWithData.html -->
<template>
    <lightning-card title="EventWithData" icon-name="custom:custom9">
        <lightning-layout class="slds-m-around_medium">
            <lightning-layout-item>
                <template lwc:if={listIsNotEmpty}>
                    <template for:each={contacts.data} for:item="contact">
                        <c-contact-list-item key={contact.Id} contact={contact} onselected={contactSelected}></c-contact-list-item>
                    </template>
                </template>
            </lightning-layout-item>
            <lightning-layout-item class="slds-m-left_medium">
                <template lwc:if={selectedContact}>
                    <img src={selectedContact.Picture__c}></img>
                    <p>{selectedContact.Name}</p>
                    <p>{selectedContact.Title}</p>
                    <p><lightning-formatted-phone value={selectedContact.Phone}></lightning-formatted-phone></p>
                    <p><lightning-formatted-email value={selectedContact.Email}></lightning-formatted-email></p>
                </template>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

在事件处理程序中,组件将属性分配给属性。它在通过 置备的阵列中查找具有该 ID 的联系人,并在模板中显示联系人的姓名、职务、电话和电子邮件。contactSelectedevent.detailcontactIdcontacts@wire

// eventWithData.js
import { LightningElement, wire } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class EventWithData extends LightningElement {
  selectedContact;

  @wire(getContactList) contacts;

  contactSelected(event) {
    const contactId = event.detail;
    this.selectedContact = this.contacts.data.find((contact) => contact.Id === contactId);
  }

  get listIsNotEmpty() {
    return this.contacts && Array.isArray(this.contacts.data) && this.contacts.data.length > 0;
  }
}

提示

查看 github.com/trailheadapps/lwc-recipes 存储库中的 c-event-with-data 组件。

处理事件

有两种方法可以侦听事件:从组件的 HTML 模板以声明方式侦听事件,或者使用命令式 JavaScript API 以编程方式侦听事件。最好从 HTML 模板中监听,因为它减少了您必须编写的代码量。若要处理事件,请在组件的 JavaScript 类中定义方法。

以声明方式附加事件侦听器

在所有者组件的模板中以标记形式声明侦听器,在本例中为 .c-parent

<!-- parent.html -->
<template>
  <c-child onnotification={handleNotification}></c-child>
</template>

在此示例中,在 JavaScript 文件中定义处理程序函数。handleNotificationc-parent

// parent.js
import { LightningElement } from "lwc";
export default class Parent extends LightningElement {
  handleNotification() {
    // Code runs when event is received
  }
}

以编程方式附加事件侦听器

在 JavaScript 中定义侦听器和处理程序函数。c-parent

// parent.js
import { LightningElement } from "lwc";
export default class Parent extends LightningElement {
  constructor() {
    super();
    this.template.addEventListener("notification", this.handleNotification);
  }
  handleNotification = () => {};
}

如果将同一侦听器重复添加到同一元素中,则浏览器将忽略重复项。

如果未删除事件侦听器,则可以选择在调用中内联代码。handleNotificationaddEventListener()

this.template.addEventListener("notification", (evt) => {
  console.log("Notification event", evt);
});

重要

不要使用 .这是一个反模式,因为返回一个新函数,因此组件不能使用相同的函数实例进行调用。由于组件不能使用相同的函数实例,因此侦听器会造成内存泄漏。addEventListener({eventName}, this.handleNotification.bind(this))bind()removeEventListener()

添加事件侦听器有两种语法。一个用于将事件侦听器添加到组件的影子边界内的元素,另一个用于将事件侦听器添加到模板不拥有的元素,例如,传递到槽中的元素。

要将事件侦听器添加到阴影边界内的元素,请使用 .template

this.template.addEventListener();

若要将事件侦听器添加到模板不拥有的元素,请直接调用。addEventListener

this.addEventListener();

获取对调度事件的组件的引用

若要获取对调度事件的对象的引用,请使用 Event.target 属性,该属性是事件的 DOM API 的一部分。

提示

lwc-recipes repo 中的几个配方使用 .若要查找示例,请在存储库中搜索 .Event.targetEvent.target

事件重定向

当事件冒泡 DOM 时,如果它越过阴影边界,则 的值会更改以匹配侦听器的范围。此更改称为“事件重定向”。事件被重新定位,因此侦听器无法看到调度事件的组件的影子 DOM。事件重定向保留了影子 DOM 封装。Event.target

让我们看一个简单的例子。

<!-- myButton.html -->
<template>
  <button>{label}</button>
</template>

单击侦听器始终接收作为目标,即使单击发生在元素上也是如此。<my-button>my-buttonbutton

想象一下,一个事件是从组件中的元素调度出来的。在组件的影子 DOM 中,是 .但是对于包含组件中元素的侦听器来说,是 ,因为该元素无法看到影子 DOM。divc-todo-itemEvent.targetdivpc-todo-appEvent.targetc-todo-itempc-todo-item

<c-todo-app>
  #shadow-root
  <div>
    <p>Your To Do List</p>
  </div>
  <c-todo-item>
    #shadow-root
    <div>
      <p>Go to the store</p>
    </div>
  </c-todo-item>
</c-todo-app>

有趣的是,对于 上的侦听器来说,是 ,而不是 ,因为 在阴影边界之外。c-todo-itemEvent.targetc-todo-itemdivc-todo-item

侦听输入字段的更改

若要侦听模板中接受输入的元素(如文本字段 ( 或 ))的更改,请使用该事件。<input><lightning-input>onchange

<!-- form.html -->
<template>
  <input type="text" value={myValue} onchange={handleChange} />
</template>
// form.js
import { LightningElement } from "lwc";
export default class Form extends LightningElement {
  myValue = "initial value";

  handleChange(evt) {
    console.log("Current value of the input: " + evt.target.value);
  }
}

在此示例中,每次输入值更改时都会调用 JavaScript 文件中的方法。handleChange()

该属性表示输入元素的值。此属性值不会在每次更改时自动更新。myValue

您可能需要对用户输入的值进行额外验证,以便在用户键入时自动更正或限制某些值。若要与输入的当前值保持同步,请在方法中更新。以下代码通过删除字符串开头和结尾的空格来自动更正键入的值。用于获取输入字段的当前值。myValuemyValuehandleChange()evt.target.value

// form.js
import { LightningElement } from "lwc";
export default class Form extends LightningElement {
  myValue = "initial value";

  handleChange(evt) {
    const typedValue = evt.target.value;
    const trimmedValue = typedValue.trim(); // trims the value entered by the user
    if (typedValue !== trimmedValue) {
      evt.target.value = trimmedValue;
    }
    this.myValue = trimmedValue; // updates the internal state
  }
}

此示例演示如何将 input value 属性重置为行 中的修剪值。它还演示了如何使属性与规范化值保持同步,以防组件将来被重新冻结(加载新值)。evt.target.value = trimmedValuemyValue

注意

从通过模板定义的元素中更改属性可能会在组件中的其他位置产生不希望的副作用。在我们的示例中,元素的输入值属性从模板中定义的值更改为 。该示例更改模板 () 中使用的值,以使组件元素的状态保持同步。否则,模板解除冻结会在尝试将输入元素的状态与组件的状态进行协调时检测到属性值不匹配。这种不匹配会生成运行时警告,您需要调整组件或 JavaScript,以保持整个模板中数据的完整性。evt.targetmyValue

删除事件侦听器

作为组件生命周期的一部分,该框架负责为您管理和清理侦听器。但是,如果您将侦听器添加到其他任何内容(如对象、对象等),则您有责任自行删除侦听器。windowdocument

要删除事件侦听器,请使用 disconnectedCallback 生命周期挂钩。

配置事件传播

触发事件后,它可以通过 DOM 向上传播。若要了解事件的处理位置,请了解事件的传播方式。

事件通过 DOM 冒泡;这就是孩子和父母的沟通方式——向下支撑,向上事件。当事件冒泡时,它将成为组件 API 的一部分,事件路径上的每个使用者都必须了解该事件。了解冒泡的工作原理非常重要,这样您就可以选择适用于您的组件的最具限制性的冒泡配置。

Lightning Web 组件事件的传播规则与 DOM 事件相同。Lightning Web 组件仅使用冒泡阶段。不支持调度事件或将侦听器添加到捕获阶段。简单地将事件的路径视为从组件开始,然后移动到其父组件,然后是祖父级,依此类推。

事件目标不会传播到组件实例的影子根之外。在组件外部,所有事件目标都是组件本身。但是,在影子树中,您可以处理来自树中特定目标的事件。根据事件的侦听器附加位置以及事件发生的位置,可以具有不同的目标。

注意

此内容改编自 Salesforce 开发人员博客文章 Lightning Web 组件中的事件如何冒泡

创建事件时,请使用事件的两个属性和 定义事件传播行为。bubblescomposed

  • Event.bubbles一个 Boolean 值,该值指示事件是否通过 DOM 冒泡。缺省值为 .false
  • Event.composed一个 Boolean 值,该值指示事件是否可以穿过阴影边界。缺省值为 .false

若要获取有关事件的信息,请使用事件 Web API 的这些属性和方法。

  • Event.target调度事件的元素。每个组件的内部 DOM 都封装在一个影子 DOM 中。阴影边界是常规 DOM(也称为轻量级 DOM)和阴影 DOM 之间的线。如果事件冒泡并越过阴影边界,则 value of 的值将更改为表示与侦听器位于同一作用域中的元素。事件重定向可保留组件封装,并防止暴露组件的内部。Event.target例如,单击侦听器始终接收作为目标,即使单击发生在元素上也是如此。<my-button>my-buttonbutton<!-- myButton.html --> <template> <button>{label}</button> </template>
  • Event.currentTarget当事件遍历 DOM 时,此属性始终引用事件处理程序附加到的元素。
  • Event.composedPath()当事件遍历 DOM 时,将在其上调用侦听器的事件目标的数组。

静态成分

静态合成不使用插槽。在这个简单的例子中,compose ,而 compose 。c-appc-parentc-child

<c-app onbuttonclick={handleButtonClick}></c-app>

应用程序中的父组件处理按钮单击。

<!-- app.html -->
<template>
  <h2>My app</h2>
  <c-parent onbuttonclick={handleButtonClick}></c-parent>
</template>

父组件包含一个带有子组件的包装器,这两个组件都侦听按钮单击事件。

<!-- parent.html -->
<template>
  <h3>I'm a parent component</h3>
  <div class="wrapper" onbuttonclick={handleButtonClick}>
    <c-child onbuttonclick={handleButtonClick}></c-child>
  </div>
</template>

子组件包含带有处理程序的按钮。onclick

<!-- child.html -->
<template>
  <h3>I'm a child component</h3>
  <button onclick={handleClick}>click me</button>
</template>
// child.js
handleClick() {
    const buttonclicked = new CustomEvent('buttonclick', {
        //event options
    });
    this.dispatchEvent(buttonclicked);
}

该示例从单击按钮时触发事件 。事件侦听器为以下元素上的自定义事件附加:buttonclickc-child

  • body
  • c-app主机
  • c-parent
  • div.wrapper
  • c-child主机

扁平化的树如下所示:

<body>
  <!-- Listening for buttonclick event -->
  <c-app>
    <!-- Listening for buttonclick event -->
    #shadow-root
    |  <h2>My app</h2>
    |  <c-parent>
    |    <!-- Listening for buttonclick event -->
    |    #shadow-root
    |    |  <h3>I'm a parent component</h3>
    |    |  <div class="wrapper">
    |    |     <!-- Listening for buttonclick event -->
    |    |     <c-child>
    |    |      #shadow-root 
    |    |      |  <!-- Listening for buttonclick event -->
    |    |      |  <h3>I'm a child component</h3>
    |    |      |  <button>click me</button>
    |    |      </c-child>
    |    |    </div>
    |  </c-parent>
  </c-app>
</body>

气泡:假,组合:假

默认配置。该事件不会通过 DOM 冒泡,也不会越过阴影边界。侦听此事件的唯一方法是直接在调度事件的组件上添加事件侦听器。

建议使用此配置,因为它的中断最小,并且为组件提供最佳封装。

事件最多只能冒泡。c-child

<body>
  <c-app>
    #shadow-root
    |  <c-parent>
    |    #shadow-root
    |    |  <div class="wrapper">
    |    |    <c-child>
    |    |    <!-- Event bubbles up here -->
    |    |      #shadow-root
    |    |      |  <h3>I'm a child component</h3>
    |    |      |  <button>click me</button>
    |    |     </c-child>
    |    |  </div>
    |  </c-parent>
  </c-app>
</body>

检查处理程序会在事件上返回这些值。c-child

  • event.currentTarget = c-child
  • event.target = c-child

从这里开始,您可以开始实现更宽松的配置,如以下几节所示。

提示

lwc-recipes 存储库中的 c-event-with-data 组件使用 c-contact-list-item 组件,该组件使用 和 创建事件。bubbles: falsecomposed: false

气泡:真,组合:假

事件通过 DOM 冒泡,但不会越过影子边界。因此,两者都可以对事件做出反应。c-childdiv.wrapper

<body>
  <c-app>
    #shadow-root
    |  <c-parent>
    |    #shadow-root
    |    |  <div class="wrapper">
    |    |  <!-- Event bubbles up here -->
    |    |    <c-child>
    |    |    <!-- Event bubbles up here -->
    |    |      #shadow-root
    |    |      |  <h3>I'm a child component</h3>
    |    |      |  <button>click me</button>
    |    |    </c-child>
    |    |  </div>
    |  </c-parent>
  </c-app>
</body>

事件处理程序返回以下内容。

C-Child 处理程序

  • event.currentTarget = c-child
  • event.target = c-child

div.childWrapper 处理程序

  • event.currentTarget = div.childWrapper
  • event.target = c-child

使用此配置有两个用例。

  • 创建内部活动若要在组件的模板中冒泡事件,请在模板中的元素上调度该事件。该事件仅冒泡到模板内元素的祖先。当事件到达阴影边界时,它将停止。// myComponent.js this.template.querySelector("div").dispatchEvent(new CustomEvent("notify", { bubbles: true }));必须在 中处理该事件。包含组件中的处理程序不会执行,因为事件不会越过阴影边界。myComponent.js<!-- container.html --> <template> <!-- handleNotify doesn’t execute --> <c-my-component onnotify={handleNotify}></c-my-component> </template>
  • 将事件发送到组件的祖父级如果将组件传递到槽中,并且您希望将该组件中的事件冒泡到包含该组件的模板,请在主机元素上调度该事件。该事件仅在包含组件的模板中可见。让我们看一下从 lwc-recipes 存储库中的组件中删节的示例代码。从子级到祖父级的组件层次结构是 。eventBubblingc-contact-list-item-bubbling -> lightning-layout-item -> c-event-bubbling该组件调度一个调用 的自定义事件。c-contact-list-item-bubblingcontactselectbubbles: true事件侦听器位于其父级 上,事件在其祖父级 中处理。oncontactselectlightning-layout-itemc-event-bubbling<!-- eventBubbling.html --> <template> <lightning-card title="EventBubbling" icon-name="standard:logging"> <template lwc:if={contacts.data}> <lightning-layout class="slds-var-m-around_medium"> <!-- c-contact-list-item-bubbling emits a bubbling event so a single listener on a containing element works --> <lightning-layout-item class="wide" oncontactselect={handleContactSelect}> <template for:each={contacts.data} for:item="contact"> <c-contact-list-item-bubbling class="slds-show slds-is-relative" key={contact.Id} contact={contact} ></c-contact-list-item-bubbling> </template> </lightning-layout-item> </lightning-layout> </template> </lightning-card> </template>// contactListItemBubbling.js import { LightningElement, api } from "lwc"; export default class ContactListItemBubbling extends LightningElement { @api contact; handleSelect(event) { // Prevent default behavior of anchor tag click which is to navigate to the href url event.preventDefault(); const selectEvent = new CustomEvent("contactselect", { bubbles: true, }); this.dispatchEvent(selectEvent); } }

气泡:真,组合:真

事件通过 DOM 冒泡,越过影子边界,然后继续冒泡通过 DOM 冒泡到文档根目录。

重要

如果事件使用此配置,则事件类型将成为组件公共 API 的一部分。它还强制使用组件及其所有祖先将事件作为其 API 的一部分包含在内。

由于此配置将事件一直冒泡到文档根目录,因此可能会导致名称冲突。名称冲突可能会导致触发错误的事件侦听器。

<body>
<!-- Event bubbles up here -->
  <c-app>
  <!-- Event bubbles up here -->
    #shadow-root
    |  <c-parent>
    |  <!-- Event bubbles up here -->
    |    #shadow-root
    |    |  <div class="wrapper">
    |    |  <!-- Event bubbles up here -->
    |    |    <c-child>
    |    |    <!-- Event bubbles up here -->
    |    |      #shadow-root
    |    |      |  <h3>I'm a child component</h3>
    |    |      |  <button>click me</button>
    |    |   </c-child>
    |    |  </div>
    |  </c-parent>
  </c-app>
</body>

如果确实使用此配置,请在事件类型前面加上命名空间,例如 。HTML 事件侦听器将具有尴尬的名称。mydomain__myeventonmydomain__myevent

气泡:假,组合:真

Lightning Web 组件不使用此配置。

跨 DOM 进行通信

有两种方法可以在不在同一 DOM 树中的组件之间进行通信。使用 Lightning 消息服务 (),或使用遵循发布-订阅模式的单例库。lightning/messageService

使用 Lightning 消息服务

要在单个 Lightning 页面或多个页面的组件之间进行通信,请使用 Lightning 消息服务通过 Lightning 消息通道进行通信。优点是消息通道不限于单个页面。Lightning Experience 应用程序中侦听消息通道上事件的任何组件在收到消息时都会更新。它可以在 Lightning Web 组件、Aura 组件和 Visualforce 页面之间工作,可以在 Lightning Experience 中的任何选项卡或任何弹出窗口中使用。它也可以跨命名空间工作。有关更多信息,请参阅使用 Lightning 消息服务跨 DOM 进行通信。pubsub

使用 pubsub 模块

在不支持 Lightning 消息传递服务的容器中,使用该模块。从 github.com/developerforce/pubsub 下载模块。pubsub

在发布-订阅模式中,一个组件发布一个事件。其他组件订阅以接收和处理事件。订阅该事件的每个组件都会接收该事件。该模块将事件限制在单个页面中。pubsub

活动最佳实践

我们建议在处理事件时遵循这些最佳做法。CustomEvent.detail

要将数据传达给同一影子树中的元素,请不要添加到 .事件的使用者可以使用 .myPropertyevent.detailevent.target.myProperty

若要将数据传达给不在同一影子树中的元素,请使用 .在这些情况下,不起作用,因为侦听器看不到真正的目标。(当事件冒泡 DOM 时,如果它越过影子边界,则 的值会更改以匹配侦听器的范围。事件被重新定位,因此侦听器无法看到调度事件的组件的影子树。event.detailevent.target.*Event.target

如果使用 ,请使用基元类型。JavaScript 通过引用传递除基元之外的所有数据类型。如果一个组件在其属性中包含一个对象,任何侦听器都可以在组件不知情的情况下改变该对象,这是一件坏事!detaildetail

使用非基元类型时,可以避免泄漏内部状态。在向属性添加数据之前,请将其复制到新对象或数组。将数据复制到新对象可确保仅发送所需的数据,并且接收方无法更改数据。detail

不要包含非基元 from 或 in 。这些值被包裹在只读膜中。在 IE 11 中,当穿过 LWC 到 Aura 桥时,只读膜丢失,这意味着可能会发生突变。@api@wiredetail事件传播

使用配置了 和 的事件,因为它们的中断最小。这些事件不会通过 DOM 冒泡,也不会越过影子边界。bubbles: falsecomposed: false

不建议使用冒泡和组合事件,因为它们会冒泡整个 DOM 树,跨越阴影边界,并通过父组件(除非停止)。如果使用 和 配置事件,则事件类型将成为组件公共 API 的一部分。它还强制使用组件及其所有祖先将事件作为其 API 的一部分包含在内。这是一个需要注册的大型 API 合约。如果使用此配置,则事件类型应全局唯一。bubbles: truecomposed: true

第三方 Web 组件(测试版)

注意

此功能是一项测试版服务。客户可自行决定是否试用此类测试版服务。对测试版服务的任何使用均受协议和条款中提供的适用测试版服务条款的约束。

在 LWC 中使用第三方 Web 组件可以节省在 LWC 中重新创建相同组件的时间。要使用第三方 Web 组件,请先启用 Lightning Web Security。Lightning Locker 不支持使用自定义元素,自定义元素是第三方 Web 组件的关键构建块。

在 LWC(测试版)中使用第三方 Web 组件

注意

此功能是一项测试版服务。客户可自行决定是否试用此类测试版服务。对测试版服务的任何使用均受协议和条款中提供的适用测试版服务条款的约束。

在 LWC 应用程序中使用第三方 Web 组件。第三方 Web 组件在 LWC 模板中呈现为本机 Web 组件。尽管您可以使用 iframe 或 在 LWC 模板文件中使用第三方 Web 组件,但我们建议使用 将第三方 Web 组件呈现为 LWC 模板中的本机 Web 组件。lwc:dom="manual"lwc:external

注意

Salesforce 不提供对第三方 Web 组件的支持。演示使用第三方 Web 组件的文档和示例不构成对第三方 Web 组件的认可。我们建议您查看第三方 Web 组件文档,了解使用情况信息。

第三方 Web 组件可以是各种 JavaScript 框架,也可以是用纯 vanilla JS 编写的框架。MDN Web 组件是可与 LWC 框架一起使用的第三方 Web 组件的示例。同样,webcomponents.org 提供了基于自定义元素和影子 DOM 标准的 Web 组件的集合。

如果您使用的是第三方 JavaScript 库,请参阅改用第三方 JavaScript 库。

实现第三方 Web 组件

在使用第三方 Web 组件之前,我们建议您检查 AppExchange 中是否有第三方 LWC 应用程序或组件,并安装具有所需功能的托管软件包。或者,检查基本组件是否适合您的要求。

若要使用非 LWC 第三方 Web 组件,请遵循以下解决方案之一。

  • 将文件作为静态资源上传,并使用模块中的方法加载组件。每个静态资源最多可以上传 5 MB。一个组织最多可以有 250 MB 的静态资源。loadScriptlightning/platformResourceLoader
  • 使用配置文件将第三方 Web 组件添加为 LWC 模块。组件的 JavaScript 文件的最大文件大小为 128 KB。.js-meta.xml

已知问题

此测试版存在几个已知问题。

  • 使用影子 DOM 的第三方 Web 组件仅支持封闭的影子根目录。如果第三方 Web 组件使用模式,请更新源以使组件符合 LWS。或者,使用不使用影子 DOM 的第三方 Web 组件。请参见 Light DOM。attachShadow({ mode: "closed" });open
  • loadScript目前不支持 ECMAScript 模块 (ESM)。例如,不支持。使用预捆绑的 JavaScript 文件和传统格式(如 IIFE(立即调用的函数表达式)或 UMD(通用模块定义))等自定义元素导入第三方 Web 组件。<script type="module">
  • 不支持具有 npm 和其他依赖项的第三方 Web 组件,或者需要编译和捆绑的组件。LWC 目前不支持从 npm 导入。
  • 影子 DOM 不支持按 ID 引用元素的第三方 Web 组件。例如,如果组件使用 ,LWC 无法访问全局 HTML 文档。尽可能使用模板引用。document.getElementById('some-id')
  • 启用 LWS 后,Experience Builder 站点当前不支持第三方 Web 组件。
  • Lightning Web Security 目前不支持使用该选项自定义内置元素。自定义的内置元素在 Safari 等 WebKit 浏览器中也不受支持。若要详细了解在扩展类中可以执行的操作,请参阅 HTML 规范:自定义元素。extendsHTMLElement

使用指南

使用第三方 Web 组件时,请遵循以下准则。

  • 使用特性、属性或指令将数据传递到第三方 Web 组件。将数据传递给 Web 组件时,LWC 默认将数据设置为属性,并且仅当属性存在时才设置属性。lwc:spread
  • 将标记传递到第三方 Web 组件中的插槽,类似于将标记传递到 Lightning Web 组件中的插槽。第三方 Web 组件不支持合成阴影的槽位。
  • LWC 模板仅支持根标记。如果第三方组件需要嵌套标记,请改用 JavaScript 文件。<template><template>document.createElement('template')
  • LWC 模板中的声明性事件绑定只能使用小写事件名称。如果第三方 Web 组件在事件名称中使用非小写字符或连字符,请使用 以编程方式添加事件侦听器。addEventListener()
  • 使用 和 呈现第三方 Web 组件后观察属性更改。observedAttributes()attributeChangedCallback()
  • lwc:external不支持动态组件创建。自定义元素的呈现方式类似于 Lightning Web 组件和原生 HTML 元素。

有关详细信息,请参阅将数据传递到自定义元素 (Beta)。

示例:将 Web 组件作为静态资源上载

此示例中的第三方 Web 组件类似于 lightning-relative-date-time 基本组件。在使用第三方 Web 组件之前,请检查基本组件是否满足您的要求。

此示例使用 time-elements JavaScript 文件,该文件可用作 IIFE。

time-elements提供了多个 Web 组件 – 、 、 和 。local-timerelative-timetime-agotime-until

在使用这些 Web 组件之前,请将 JavaScript 文件作为静态资源上传到组织中。在此示例中,静态资源的名称为 。timeElements

<!--externalExample.html-->
<template>
  <p>
    Relative time (auto-updated every minute):
    <relative-time datetime={date} lwc:external></relative-time>
  </p>
</template>

使用 中的函数导入静态资源。loadScriptlightning/platformResourceLoader

//externalExample.js
import { LightningElement } from "lwc";
import { loadScript } from "lightning/platformResourceLoader";
import timeElements from "@salesforce/resourceUrl/timeElements";

export default class extends LightningElement {
  isCmpInitialized = false;
  error;
  date;

  renderedCallback() {
    if (this.isCmpInitialized) {
      return;
    }
    this.isCmpInitialized = true;

    loadScript(this, timeElements)
      .then(() => {
        this.initializeComponent();
      })
      .catch((error) => {
        this.error = error;
      });
  }
  initializeComponent() {
    this.date = new Date();
  }
}

示例:将 Web 组件添加为 LWC 模块

提示

此示例中的第三方 Web 组件类似于 lightning-helptext 基本组件。在使用第三方 Web 组件之前,请检查基本组件是否满足您的要求。

此示例使用 MDN 中的 popup-info 组件。要将组件添加为 LWC 模块,请在文件夹中创建一个 Lightning Web 组件。让我们命名它。force-app/main/default/lwcpopupInfo

在模板中,在元素上包含指令。lwc:externalpopup-info

<!-- popupInfo.html -->
<template>
  <popup-info
    lwc:external
    img={logo}
    data-text="Here's some popup text for your logo'"
  ></popup-info>
</template>

在 JavaScript 文件中,请确保在模式下创建影子根。此示例使用 附加的静态资源。它还将图像路径替换为 ,该路径引用作为静态资源上传的徽标。closedtrailheadLogopopup-info"img/default.png"{$logo}

//popupInfo.js
import { LightningElement } from "lwc";
import trailheadLogo from "@salesforce/resourceUrl/trailhead_logo";

// Create a class for the element
class PopUpInfo extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Create a shadow root
    this.shadow = this.attachShadow({ mode: "closed" });
  }

  connectedCallback() {
    // Create spans
    const wrapper = document.createElement("span");
    wrapper.setAttribute("class", "wrapper");

    const icon = document.createElement("span");
    icon.setAttribute("id", "test");
    icon.setAttribute("class", "icon");
    icon.setAttribute("tabindex", 0);

    const info = document.createElement("span");
    info.setAttribute("class", "info");

    // Take attribute content and put it inside the info span
    const text = this.getAttribute("data-text");
    info.textContent = text;

    // Insert icon
    const img = document.createElement("img");
    img.src = this.hasAttribute("img") ? this.getAttribute("img") : `{$logo}`;
    icon.appendChild(img);

    // Create some CSS to apply to the shadow dom
    const style = document.createElement("style");

    style.textContent = `
      .wrapper {
        position: relative;
      }
      .info {
        font-size: 0.8rem;
        width: 200px;
        display: inline-block;
        border: 1px solid black;
        padding: 10px;
        background: white;
        border-radius: 10px;
        opacity: 0;
        transition: 0.6s all;
        position: absolute;
        bottom: 20px;
        left: 10px;
        z-index: 3;
      }
      img {
        width: 1.2rem;
      }
      .icon:hover + .info, .icon:focus + .info {
        opacity: 1;
      }
    `;

    // Attach the created elements to the shadow dom
    this.shadow.appendChild(style);
    this.shadow.appendChild(wrapper);
    wrapper.appendChild(icon);
    wrapper.appendChild(info);
  }
}

// Define the new element
customElements.define("popup-info", PopUpInfo);

export default class PopupInfo extends LightningElement {
  logo = trailheadLogo;
}

使用自定义元素(测试版)

注意

此功能是一项测试版服务。客户可自行决定是否试用此类测试版服务。对测试版服务的任何使用均受协议和条款中提供的适用测试版服务条款的约束。

第三方 Web 组件是您可以使用 创建的 Web 组件,从而生成可在 LWC 应用程序中重用的自定义元素。除非使用第三方 Web 组件,否则无需将自定义元素与 LWC 一起使用。就本文而言,自定义元素和第三方 Web 组件是可互换的。customElements.define()

注意

必须在 Salesforce 组织中启用 Lightning Web Security (LWS),因为 Lightning Locker 不支持第三方 Web 组件。

自定义元素必须遵循这些特征。

  • 使用 via 初始化自定义元素。constructor()class
  • 使用阴影 DOM 或浅色 DOM。对于影子 DOM,LWS 仅支持关闭模式。
  • 使用 customElements.define(name, constructor) 在其中注册自定义元素。必须包含连字符,并且在页面上是唯一的。CustomElementRegistryname

在自定义元素中使用标记是可选的。您可以使用 创建标签。您不能在 LWC HTML 模板中使用嵌套标记。<template><template>document.createElement("template");<template>

定义自定义元素

在 LWC 中,只有在使用第三方 Web 组件时才需要自定义元素。在 LWC 中实现第三方 Web 组件时,我们建议您参考该组件的文档以获取使用信息。

第三方 Web 组件包含自定义元素定义,该定义扩展了 HTMLElement 类。自定义元素定义描述如何显示元素以及添加或删除元素时要执行的操作。

class MyCustomElement extends HTMLElement {
  constructor() {
    super();
    /* custom element created */
  }
  connectedCallback() {
    /* element is added to document */
  }
  disconnectedCallback() {
    /* element is removed from the document */
  }
  static get observedAttributes() {
    return [
      /* array of attribute names to monitor for changes */
    ];
  }
  attributeChangedCallback(name, oldValue, newValue) {
    /* one of attributes listed above is modified */
  }
  adoptedCallback() {
    /* element is moved to a new document */
  }
}
/* register the element */
customElements.define("my-custom-element", MyCustomElement);

第三方 Web 组件行为在生命周期回调中描述,例如 in 或 。connectedCallback()disconnectedCallback()

  • constructor()-在初始化自定义元素时调用。它必须调用并可以指定任何预渲染过程,例如设置阴影的内容。super()
  • connectedCallback()-在自定义元素连接到 DOM 时调用。
  • disconnectedCallback()-在自定义元素与 DOM 断开连接时调用。
  • observedAttributes()-返回要观察的属性数组。
  • attributeChangedCallback()-在添加、删除或更改属性时调用。指定要在 中观察的属性。observeAttributes()
  • adoptedCallback()-在将自定义元素移动到新文档时调用。

创建自定义元素构造函数

在 LWC 中,只有在使用第三方 Web 组件时才需要自定义元素。在 LWC 中实现第三方 Web 组件时,我们建议您参考该组件的文档以获取使用信息。

第三方 Web 组件包含一个构造函数,用于设置初始状态和默认值、设置事件侦听器以及创建影子根。

若要建立原型链,请调用构造函数。使用 将影子根附加到自定义元素。LWS 要求将模式设置为 。了解 Lightning Web Security 与 Lightning Locker 的比较结果。super()this.attachShadow()closed

注意

在关闭模式下,该属性返回 null,因此不能使用 来访问和操作元素的影子根。在闭合模式下创建自定义元素时,请使用其他变量(如 或 )保存对影子根的引用。shadowRootshadowRootshadow__shadowRoot

要创建元素的内部影子 DOM 结构,请使用 追加内容。此示例定义 LWC 组件生命周期之外的自定义元素。.innerHTML

//myComponent.js
import { LightningElement } from "lwc";

customElements.define(
  "my-custom-element",
  class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "closed" }).innerHTML = "<div>Custom Element Constructor</div>";
    }
  },
);

export default class MyComponent extends LightningElement {
  greeting = "World";
}

若要使用自定义元素的属性和子元素,请改用 or。例如,如果要创建元素并在其上设置属性,请将它们推迟到生命周期回调之一,并在属性更改时用于定义回调。有关详细信息,请参阅自定义元素规范。connectedCallback()renderedCallback()attributeChangedCallback()

在 LWC 中使用自定义元素

将 LWC 模板中的自定义元素与指令一起使用。lwc:external

<!-- myComponent.html -->
<template>
  <div class="slds-var-m-around_medium">Hello, {greeting}!</div>
  <my-custom-element lwc:external></my-custom-element>
</template>

示例:创建递增按钮标签的自定义元素

以下示例演示了自定义元素的结构。该示例创建一个按钮,按下该按钮时,该按钮的标签上的计数器会递增。

//myCounterButton.js
import { LightningElement } from "lwc";

customElements.define(
  "my-counter",
  class extends HTMLElement {
    count = 0;
    handler = null;
    shadow = null;
    constructor() {
      super();
      this.shadow = this.attachShadow({ mode: "closed" });
      this.shadow.innerHTML = `Button:<button>${this.count}</button>`;
    }
    connectedCallback() {
      this.handler = () => {
        this.count++;
        this.shadow.firstElementChild.innerHTML = this.count;
      };
      this.shadow.firstElementChild.addEventListener("click", this.handler);
    }
    disconnectedCallback() {
      this.shadow.firstElementChild.removeEventListener("click", this.handler);
    }
  },
);

export default class MyCounterButton extends LightningElement {
  // your LWC component definition here
}

若要在 LWC 中使用自定义元素,请使用指令将其添加到模板中。lwc:external

<!-- myCounterButton.html -->
<template>
  <my-counter lwc:external></my-counter>
</template>

该组件在 DOM 中呈现如下。每次单击按钮标签时,按钮标签都会递增。${this.count}

<my-counter-button>
    <my-counter>
      #shadow-root (closed)
      |  "Button:"
      |  <button>0</button>
    </my-counter>
</my-counter-button>

示例:使用属性更改回调递增按钮

此示例与上一个示例类似,但它监视自定义元素的属性,用于在属性更改时定义回调。countattributeChangedCallback()

//myCounterWithCallback.js
import { LightningElement } from "lwc";

customElements.define(
  "my-counter",
  class MyCounter extends HTMLElement {
    constructor() {
      super();
      this.shadow = this.attachShadow({ mode: "closed" });
    }

    get count() {
      return this.getAttribute("count");
    }

    set count(val) {
      this.setAttribute("count", val);
    }

    connectedCallback() {
      this.renderButton();
      let btn = this.shadow.querySelector("#btn");
      btn.addEventListener("click", this.increment.bind(this));
    }

    static get observedAttributes() {
      return ["count"];
    }

    attributeChangedCallback(prop, oldVal, newValue) {
      if (prop === "count") {
        this.renderButton();
        let btn = this.shadow.querySelector("#btn");
        btn.addEventListener("click", this.increment.bind(this));
        // do something else
      }
    }

    increment() {
      this.count++;
    }

    renderButton() {
      this.shadow.innerHTML = `
        <button id="btn">${this.count}</button>
      `;
    }
  },
);

export default class extends LightningElement {}

将自定义元素标签添加到 LWC 模板。

<!-- myCounterWithCallback.html -->
<template>
  <my-counter count="0" lwc:external></my-counter>
</template>

将数据传递到自定义元素(测试版)

自定义元素是第三方 Web 组件的构建基块。使用特性、属性或 lwc:spread 指令将数据传递到自定义元素。传递数据时,LWC 默认将数据设置为属性,并且仅当属性存在时才设置属性。

初始化影子内容的构造函数仅调用一次。在使用特性或属性时,请考虑这些准则。this.attachShadow({ mode: 'closed' })

使用属性传递数据

呈现第三方 Web 组件后,将忽略属性更改。要观察属性并确保第三方 Web 组件呈现您的更改,请使用静态 getter 和方法。当观察到的属性发生更改时,回调将运行。若要处理属性数据的序列化和反序列化,请使用 getter 和 setter,如本示例所示。observedAttributes()attributeChangedCallback()attributeChangedCallback()

class extends HTMLElement {
    static observedAttributes = ["myAttr"];
    attributeChangedCallback(attr, oldVal, newVal) {
      if (attrName === "myAttr") {
        this.shadow.getElementById("myElement").myAttr = newVal === "true";
      }
    }
    set myAttr(bool) {
      this.setAttribute("myAttr", bool.toString());
    }
    get myAttr() {
      return this.getAttribute("myAttr") === "true";
    }
}

请参阅使用属性更改递增按钮的示例。

使用属性传递数据

要使用属性,请使用 getter 和 setter。

class extends HTMLElement {
  _message = 'Hello';
  set message(value) {
    this._message = value;
  }
  get message() {
    return this._message;
  }
}

例如,您有一个自定义元素,该元素具有使用 lwc:spread 指令传递给自定义元素的属性。

<!-- myMessage.html -->
<template>
  <c-message lwc:external lwc:spread={props}></c-message>
</template>

自定义元素显示一个按钮。要向按钮添加事件侦听器,请使用 。addEventListener()

// myMessage.js
import { LightningElement } from "lwc";

customElements.define(
  "c-message",
  class extends HTMLElement {
    constructor() {
      super();
      this.shadow = this.attachShadow({ mode: "closed" });
      this.shadow.innerHTML = `<button>click</button>`;
      this.shadow.querySelector("button").addEventListener("click", (event) => {
        console.log(`message: ${this.message}`);
      });
    }

    set message(value) {
      this._message = value;
    }
    get message() {
      return this._message;
    }
  },
);

export default class MyMessage extends LightningElement {
  props = {
    message: "Hello custom element",
  };
}

将数据传递到子组件

考虑一个父组件,该组件包含定义自定义元素的子组件。使用 lwc:spread 指令将属性传递给子组件。

<!-- myApp.html -->
<template>
  <c-cmp lwc:spread={myProps}></c-cmp>
</template>

使用具有键值对的对象。

// myApp.js
import { LightningElement } from "lwc";

export default class MyApp extends LightningElement {
  myProps = {
    name: "Guest",
    greeting: "Hello",
  };
}

在子组件中,创建自定义元素的实例。

<!-- myCmp.html -->
<template>
  <c-custom-el lwc:external> {greeting}, {name} </c-custom-el>
</template>

在 JavaScript 中调用 并将属性公开给父组件。constructor()

// myCmp.js
import { LightningElement, api } from "lwc";

customElements.define(
  "c-custom-el",
  class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "closed" }).innerHTML = "<slot></slot>";
    }
  },
);
export default class MyCmp extends LightningElement {
  @api name;
  @api greeting;
}

自定义元素的呈现方式如下。

<my-app>
  #shadow-root (open)
  |  <my-cmp> 
  |    #shadow-root (open) 
  |    |  <c-custom-el>
  |    |    #shadow-root (closed)
  |    |    | Hello, Guest
  |    |  </c-custom-el>
  |  </my-cmp>
</my-app>

将标记传递到自定义元素中的槽

将标记传递到第三方 Web 组件中的槽的行为类似于 LWC 组件中的槽。

注意

第三方 Web 组件不支持合成阴影的开槽实现。

考虑使用带有一些标记的第三方 Web 组件。

customElements.define(
  "c-custom-slot",
  class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "closed" }).innerHTML = `
    <h1>My title</h1>
    <div>
        <p>Some content here</p>
    </div>
    <slot></slot>
    `;
    }
  },
);

以下槽位内容将显示在元素中。<slot>

<template>
  <c-custom-slot lwc:external>
    <div class="slotted">slot content</div>
  </c-custom-slot>
</template>

该组件在 DOM 中呈现如下。

<c-custom-slot>
  #shadow-root (closed)
  |  <h1>My title</h1>
  |  <div><p>Some content here</p></div>
  |  <slot>
  |    <div class="slotted">slot content</div>
  |  </slot>
</c-custom-slot>

同样,您可以使用这样的命名槽。

// mySlot.js
import { LightningElement, api } from "lwc";

customElements.define(
  "c-slotting",
  class extends HTMLElement {
    constructor() {
      super();
      this.shadow = this.attachShadow({ mode: "closed" });
      this.shadow.innerHTML = `<slot name="myslot"></slot>`;
    }
  },
);
export default class MySlot extends LightningElement {}

在标记中包含命名槽。

<!-- mySlot.html -->
<c-slotting lwc:external>
  <p>slotted incorrectly</p>
  <p slot="myslot">slotted correctly</p>
</c-slotting>

该组件在 DOM 中呈现如下。

<my-slot>
  #shadow-root (open)
  |  <c-slotting>
  |    #shadow-root (closed)
  |    |  <p>slotted incorrectly</p>
  |    |  <p slot="myslot">slotted correctly</p>
  |  </c-slotting>
</my-slot>

在本机影子组件中,插槽内容保留在顶级元素中。CustomElementConstructor

例如,文件中的此顶级元素包括插槽内容。<slot>index.html

<c-custom-slot>
  <div class="slotted">Pre-existing slot content</div>
</c-custom-slot>

该组件在 DOM 中呈现如下。它保留了原始的插槽内容。

<c-custom-slot>
  #shadow-root (closed)
  |  <slot>
  |    <div class="slotted">Pre-existing slot content</div>
  |  </slot>
</c-custom-slot>

使用第三方 Web 组件中的事件

第三方 Web 组件中的事件与 LWC 中的事件类似。事件绑定仅支持小写事件。要使用具有非小写名称的事件,请使用 addEventListener() API 添加事件侦听器。

在 中添加事件侦听器。constructor()

customElements.define(
  "c-element-with-events",
  class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "closed" });
      this.shadow.innerHTML = `CLICK ME!`;
      this.addEventListener("click", this.handleClick);
    }
    handleClick() {
      this.dispatchEvent(new CustomEvent("lowercaseevent"));
      this.dispatchEvent(new CustomEvent("camelEvent"));
    }
  },
);

第三方 Web 组件注意事项

未注册的自定义元素呈现为本机接口的实例,该实例在不添加任何属性或方法的情况下进行扩展。然后,浏览器将外部组件视为本机组件,与 a 或 .HTMLUnknownElementHTMLElementspandiv

对于已注册的组件,引擎会呈现关联的第三方 Web 组件,并将升级推迟到浏览器。

有关更多信息,请参见 HTML 规范:在创建元素后升级元素。

此外,请考虑第三方 Web 组件上的这些升级行为。

  • 如果未升级第三方 Web 组件,LWC 会在挂载和更新时设置其属性。
  • 如果存在延迟升级,则设置属性而不是属性。
  • 升级后,如果属性存在,则设置该属性而不是该属性。

将样式追加到自定义元素(测试版)

第三方 Web 组件可以包含在影子 DOM 或轻量级 DOM 中创建的自定义元素。对于影子 DOM,开放模式比封闭模式更广泛地使用。要了解您正在使用的第三方 Web 组件是否正在使用特定的影子模式,请参阅 Web 组件文档。或者,查看 Web 组件源并查找方法。使用轻量级 DOM 的 Web 组件不使用该方法。attachShadow({mode: 'closed|open'})attachShadow()

确定第三方 Web 组件的 DOM 模型后,可以决定如何设置自定义元素及其内部结构的样式。

在 Shadow DOM 中实现自定义元素样式

使用影子 DOM,自定义元素无法从其容器组件继承样式。同样,在自定义元素中定义的样式也不会渗出。在关闭模式下,您无法将样式附加到自定义元素的影子 DOM。例如,返回 null。this.refs.myEl.shadowRoot

许多 Web 组件作者通过将标记附加到影子根来实现自定义元素的样式。查看 Web 组件源并查找与这些行类似的行。这些样式的范围限定在影子根内。<style>

const style = document.createElement("style");
style.textContent = `.someSelector { color: red }`;
this.shadow.appendChild(style);

若要在闭合模式下追加样式,可以使用 CSS 变量(自定义属性)、类或阴影部分。除了第三方 Web 组件上的影子根样式实现之外,我们不建议使用 JavaScript 来定义 CSS 样式。

Web 组件作者允许您覆盖影子根中的样式(如果它们包含这样的 CSS 自定义属性)。

提示

查看第三方 Web 组件源代码或文档,了解可用于设置组件中自定义元素样式的可用 CSS 自定义属性和阴影部件。

style.textContent = `.someSelector { color: var(--my-title-color, 	#AA4A44)}`;

由于 CSS 自定义属性会渗入影子 DOM,因此您可以覆盖主机组件中的样式。

假设第三方 Web 组件提供了带有 CSS 自定义属性的边框样式。在主机组件中,使用具有您自己的值的 CSS 定制属性。border: var(--my-border, 1px solid black);--my-border

/* popupInfo.css */
:host {
  --my-border: 1px solid green;
}

如果要设置第三方自定义元素的 host 元素的样式,请对文件中的自定义元素使用选择器,例如 ..cssc-custom-el { background-color: red; }

<!--styleExample.html -->
<template>
  <c-custom-el lwc:external></c-custom-el>
</template>

选择器将红色背景应用于自定义元素的内容。c-custom-el

/* styleExample.css */
c-custom-el {
  background-color: red;
}

自定义元素的呈现方式如下。

<c-style-example>
  #shadow-root (open)
  |  <c-custom-el>
  |    #shadow-root (closed)
  |    |  <slot>your text here</slot>
  |   </c-custom-el>
</c-style-example>

在 Light DOM 中实现自定义元素样式

与影子 DOM 不同,轻量级 DOM 支持从根文档设置样式以定位 DOM 节点并设置其样式。因此,容器组件上的样式级联到嵌套的自定义元素的轻量级 DOM 中。在轻量级 DOM 中呈现的自定义元素上的样式也会应用于其容器组件,直到遇到阴影边界。

如果第三方 Web 组件使用轻量级 DOM,则可以使用文件中的类来设置自定义元素内部的样式。要在自定义元素中限定样式范围,请使用组件包中的文件包含样式。.css.scoped.css

<!--styleExample.html-->
<template>
  <custom-element class="highlight" lwc:external></custom-element>
</template>

在 css 文件中,提供自定义样式。

.highlight {
  color: red;
}

组件生命周期

Lightning Web 组件的生命周期由框架管理。框架创建组件,将它们插入到 DOM 中,呈现它们,然后将它们从 DOM 中删除。它还监视组件的属性更改。

生命周期流程

此图显示了从创建到渲染的组件生命周期流程。

此图显示了从 DOM 中删除组件实例时发生的情况。

创建组件时运行代码

该方法在创建组件实例时触发。不要在构造过程中向 host 元素添加属性。您可以在任何其他生命周期挂钩中向 host 元素添加属性。constructor()

构造函数从父级流向子级。

构造函数注意事项

HTML: Custom elements 规范中的这些要求适用于 .constructor()

  • 第一个语句必须不带参数。此调用为 建立正确的原型链和值。在触摸之前务必先打电话。super()thissuper()this
  • 不要在构造函数主体中使用语句,除非它是简单的提前返回 ( 或 )。returnreturnreturn this
  • 不要使用 or 方法。document.write()document.open()
  • 不要检查元素的属性和子元素,因为它们尚不存在。
  • 不要检查元素的公共属性,因为它们是在创建组件后设置的。

不要在构造过程中向主机元素添加属性

您可以在组件生命周期的任何阶段(构造除外)向主机元素添加属性。

不建议使用此模式,因为它会向 host 元素添加一个属性。constructor()

// don't do this
import { LightningElement } from "lwc";
export default class Deprecated extends LightningElement {
  constructor() {
    super();
    this.classList.add("new-class");
  }
}

若要向 host 元素添加属性,请改用该方法。connectedCallback()

import { LightningElement } from "lwc";

export default class New extends LightningElement {
  connectedCallback() {
    this.classList.add("new-class");
  }
}

在 DOM 中插入或删除组件时运行代码

当组件插入到 DOM 中时,生命周期钩子会触发。当组件从 DOM 中删除时,生命周期挂钩会触发。connectedCallback()disconnectedCallback()

两个钩子都从父级流向子级。您无法从回调访问子元素,因为它们尚不存在。要访问 host 元素,请使用 .要访问组件模板中的元素,请使用 .thisthis.template

用于与组件的环境进行交互。例如,使用它来:connectedCallback()

  • 与当前文档或容器建立通信,并协调与环境的行为。
  • 执行初始化任务,例如获取数据、设置缓存或侦听事件
  • 订阅和取消订阅消息通道。

钩子是使用传递给组件的初始属性调用的。如果组件从属性派生其内部状态,则最好在 setter 中编写逻辑,而不是在 中编写逻辑。有关示例代码,请参阅 Salesforce 工程师 Pierre-Marie Dartus 撰写的这篇 StackExchange 文章。connectedCallback()connectedCallback()

钩子可以发射不止一次。例如,如果删除一个元素,然后将其插入到另一个位置,例如在对列表重新排序时,挂钩会触发几次。如果希望代码运行一次,请编写代码以防止其运行两次。connectedCallback()

用于清理 中完成的工作,例如清除缓存或删除事件侦听器。disconnectedCallback()connectedCallback()

要检查组件是否连接到 DOM,可以使用 this.isConnected

在组件呈现时运行代码

这是 Lightning Web 组件所独有的。使用它在组件完成渲染阶段后执行逻辑。renderedCallback()

此挂钩从子级流向父级。

当组件呈现时,将重新计算模板中使用的表达式。

连接并渲染组件后,对组件状态的更改(或突变)会将组件标记为脏组件,并将微任务排入队列以重新渲染组件。有关跟踪哪些更改的详细信息,请参阅反应性。

在应用程序的生命周期中,组件通常会呈现多次。要使用此钩子执行一次性操作,请使用布尔字段 like 来跟踪是否已执行。第一次执行时,执行一次性操作并设置 .如果 ,则不执行该操作。hasRenderedrenderedCallback()renderedCallback()hasRendered = truehasRendered = true

最好在 HTML 模板中以声明方式添加事件侦听器。但是,如果确实需要以编程方式向模板元素添加事件侦听器,请在 中执行此操作。您无需删除侦听器。如果将侦听器重复添加到同一元素中,浏览器将忽略重复项。renderedCallback()

重新呈现模板时,LWC 引擎会尝试重用现有元素。在以下情况下,引擎使用差异算法来决定是否丢弃元素。

  • 使用 for:each 指令创建的元素。重用这些迭代元素的决定取决于属性。如果发生更改,则元素可能会被重新呈现。如果未更改,则不会重新呈现元素,因为引擎假定迭代元素未更改。keykeykey
  • 作为槽内容接收的元素。引擎尝试重用 中的元素,但差异算法确定是否逐出元素并重新创建它。<slot>

更新组件的状态可能会导致无限循环。例如:renderedCallback()

  • 不要更新 中的电线适配器配置对象属性。请参阅了解 Wire Service。renderedCallback()
  • 不要更新 中的公共属性或字段。请参阅反应性。renderedCallback()

提示

lwc-recipes samples 存储库中的模块使用 lifecycle hook。libsChartjsrenderedCallback()

处理组件错误

这是 Lightning Web 组件所独有的。实现它以创建一个错误边界组件,该组件捕获其树中所有后代组件中的错误。它捕获在后代的生命周期挂钩中或在 HTML 模板中声明的事件处理程序期间发生的错误。您可以对错误边界组件进行编码,以记录堆栈信息并呈现替代视图,以告知用户发生了什么以及下一步要做什么。errorCallback()

您可以创建错误边界组件,并在整个应用中重复使用它。由您决定在哪里定义这些错误边界。您可以包装整个应用程序,也可以包装每个单独的组件。最有可能的是,你的架构介于两者之间。想一想你想在什么地方告诉用户出了问题。

<!-- boundary.html -->
<template>
  <template lwc:if={error}>
    <error-view error={error} info={stack}></error-view>
  </template>
  <template lwc:else>
    <healthy-view></healthy-view>
  </template>
</template>
// boundary.js
import { LightningElement } from "lwc";
export default class Boundary extends LightningElement {
  error;
  stack;
  errorCallback(error, stack) {
    this.error = error;
  }
}

参数是 JavaScript 本机错误对象,参数是字符串。errorstack

您不必在模板中使用。例如,假设您定义了一个组件模板。如果此组件引发错误,框架将在重新渲染期间调用并卸载该组件。lwc:[if|elseif|else]errorCallback

<!-- boundary.html -->
<template>
  <my-one-and-only-view></my-one-and-only-view>
</template>