使用 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>

组件可访问性

辅助软件和辅助技术使残障用户能够使用您构建的产品。开发组件,以便所有用户都能感知、理解、导航并与之交互。

我们建议您在创建自定义组件时遵循 WCAG 辅助功能指南。

组件辅助功能属性

若要使组件可用于屏幕阅读器和其他辅助技术,请在组件上使用 HTML 属性。HTML 属性描述它们所包含的 UI 元素。辅助功能软件通过大声朗读属性来解释 UI 元素。

辅助功能的一个关键部分是属性的使用。屏幕阅读器向用户读出属性值。当您使用具有属性的 Lightning Web 组件时,请始终指定一个值。例如,组件具有属性。titletitletitlelightning-buttontitle

<!-- parent.html -->
<template>
  <lightning-button title="Log In" label="Log In" onclick={login}></lightning-button>
</template>

该模板创建如下所示的 HTML 输出,供屏幕阅读器向用户读出“登录”。

<!-- Generated HTML -->
<lightning-button>
  <button title="Log In">Log In</button>
</lightning-button>

创建 Lightning Web 组件时,如果您希望屏幕阅读器向用户大声朗读值,请使用 to 公开公共属性。@apititle

当您通过将某个属性公开为公共属性来控制该属性时,默认情况下,该属性将不再显示在 HTML 输出中。若要将值作为属性传递到呈现的 HTML(以反映属性),请为属性定义 getter 和 setter 并调用该方法。setAttribute()

您还可以在 setter 中执行操作。使用私有属性来保存计算值。

// myComponent.js
import { LightningElement, api } from "lwc";

export default class MyComponent extends LightningElement {
  privateTitle;
  @api
  get title() {
    return this.privateTitle;
  }

  set title(value) {
    this.privateTitle = value.toUpperCase();
    this.setAttribute("title", this.privateTitle);
  }
}
<!-- parent.html -->
<template>
  <c-my-component title="Hover Over the Component to See Me"></c-my-component>
</template>
/* Generated HTML */
<c-my-component title="HOVER OVER THE COMPONENT TO SEE ME">
  <div>Reflecting Attributes Example</div>
</c-my-component>

有关使用 的详细信息,请参阅将 JavaScript 属性反映到 HTML 属性。setAttribute()

ARIA 属性

若要提供更高级的辅助功能,例如屏幕阅读器读出按钮的当前状态,请使用 ARIA 属性。这些属性为支持 ARIA 标准的屏幕阅读器提供了更详细的信息。

您可以将 ARIA 属性与 HTML 模板中的属性相关联。在组件的模板文件中,值必须是唯一的,以便屏幕阅读器可以将 ARIA 属性(如 、)与特定元素相关联。ididaria-describedbyaria-detailsaria-owns

注意

呈现模板时,值可能会转换为全局唯一值。不要在 CSS 或 JavaScript 中使用选择器,因为它与转换后的 .请改用元素的属性或类似 的属性。idididclassdata-*data-id

让我们看一些代码。该属性告诉屏幕阅读器在按下按钮时说出该按钮。使用组件时,您可以编写:aria-pressedlightning-button

<!-- parent.html -->
<template>
  <lightning-button
    title="Log In"
    label="Log In"
    onclick={login}
    aria-label="Log In"
    aria-pressed
  ></lightning-button>
</template>

该组件将 ARIA 属性定义为公共属性,并使用字段来获取和设置公共属性。

<!-- lightning-button.html -->
<template>
  <button
    title="Log In"
    label="Log In"
    onclick={login}
    aria-label={innerLabel}
    aria-pressed={pressed}
  ></button>
</template>

组件 JavaScript 使用 camel-case 属性映射来获取和设置 中的值。lightning-button.js

// lightning-button.js
import { LightningElement, api } from "lwc";
export default class LightningButton extends LightningElement {
  innerLabel;

  @api
  get ariaLabel() {
    return this.innerLabel;
  }

  set ariaLabel(newValue) {
    this.innerLabel = newValue;
  }

  pressed;

  @api
  get ariaPressed() {
    return this.pressed;
  }

  set ariaPressed(newValue) {
    this.pressed = newValue;
  }
}

生成的 HTML 为:

<lightning-button>
  <button
    title="Log In"
    label="Log In"
    onclick={login}
    aria-label="Log In"
    aria-pressed="true"
  ></button>
</lightning-button>

支持 ARIA 的屏幕阅读器读取标签并指示按钮已按下。

注意

ARIA 属性在访问器函数中使用驼峰大小写。例如,变为 .完整的映射列表位于 GitHub 存储库 lwc/packages/@lwc/template-compiler/src/parser/constants.ts 中。aria-labelariaLabel

默认 ARIA 值

组件作者可能希望在自定义组件上定义默认的 ARIA 属性,并且仍允许组件使用者指定属性值。在这种情况下,组件作者在组件的元素上定义默认的 ARIA 值。

// lightning-button.js sets "Log In" as the default label
import { LightningElement } from "lwc";
export default class LightningButton extends LightningElement {
  connectedCallback() {
    this.template.ariaLabel = "Log In";
  }
}

注意

在 中定义属性。不要在 中定义属性。connectedCallback()constructor()

当您使用组件并提供值时,将显示提供的值。aria-label

<!-- parent.html -->
<template>
  <lightning-button
    title="Log In"
    label="Submit"
    onclick={login}
    aria-label="Submit"
    aria-pressed
  ></lightning-button>
</template>

生成的 HTML 为:

<lightning-button>
  <button title="Log In" label="Submit" aria-label="Submit" aria-pressed="true"></button>
</lightning-button>

而且,如果未提供值,则会显示默认值。aria-label

<!-- parent.html -->
<template>
  <lightning-button title="Log In" label="Log In" onclick={login}></lightning-button>
</template>

生成的 HTML 为:

<lightning-button>
  <button title="Log In" label="Log In" onclick={login} aria-label="Log In"></button>
</lightning-button>

静态值

如果您创建自定义组件并且不希望属性的值发生更改,该怎么办?一个很好的例子是属性。您不希望组件使用者更改为 。按钮就是按钮。rolebuttontab

您始终希望生成的 HTML 具有 be ,如本例所示。rolebutton

<lightning-button>
  <div title="Log In" label="Log In" onclick={login} role="button"></div>
</lightning-button>

为了防止使用者更改属性的值,只需返回一个字符串。此示例始终返回该值。"button"role

// lightning-button.js
import { LightningElement, api } from "lwc";
export default class LightningButton extends LightningElement {
  set role(value) {}

  @api
  get role() {
    return "button";
  }
}

来自不同模板的链接 ID 和 ARIA 属性

同一模板中的 ID 和 ARIA 属性会自动链接。如果属性位于不同的模板中,则必须手动链接它们。

在本机影子 DOM 中,您无法在单独模板中的元素之间链接 ID 和 ARIA 属性。

要使用 ID 和 ARIA 属性将两个元素链接在一起,请使用 light DOM 将它们放置在同一个阴影根中。请参阅 Light DOM 中的辅助功能部分。

处理焦点

要使残障人士能够访问您的组件,请对它们进行编程,以处理哪个元素具有焦点并可以接收输入。

Web 浏览器可通过键盘使用 Tab 键进行完全导航。出于辅助功能目的,依赖键盘导航的用户需要视觉提示来查看哪个元素具有焦点。视力低下的人也使用屏幕阅读器来浏览网页。屏幕阅读器大声朗读页面上的元素,包括具有焦点的元素。作为开发人员,您需要确保当用户按 Tab 键浏览页面时,浏览器会将焦点移到下一个逻辑元素。

当用户按 Tab 键浏览页面时,交互元素(如 、 、 和 接收)会自动获得焦点。本机不可聚焦的元素(如 或 )可用于接收键盘焦点。<a><button><input><textarea><div><span>tabindex="0"

注意

仅支持 和 tabindex 值。分配意味着元素聚焦在标准顺序键盘导航中。分配它会从顺序键盘导航中删除该元素。有关详细信息,请参阅键盘辅助功能。0-1tabindex="0"tabindex="-1"

选项卡导航期间的焦点行为

对于自定义组件,焦点会跳过组件容器并移动到组件内部的元素。

让我们看一些代码。

在此示例中,当用户按 Tab 键时,焦点会从 按钮元素移动到 中的输入元素,从而跳过自身。parentchildchild

<!-- parent.html -->
<template>
  <button>Button</button>
  <c-child></c-child>
</template>
<!-- child.html -->
<template>
  <span
    >Tabbing to the custom element moves focus to the input, skipping the component itself.</span
  >
  <br /><input type="text" />
</template>
选择自定义组件的代码输出。

将焦点应用于子组件

若要向自定义组件添加焦点,请使用该属性。在父模板中,我们将子组件设置为将子组件本身添加到导航序列中。tabindextabindex0

<!-- parent.html -->
<template>
  <button>Button</button>
  <c-child tabindex="0"></c-child>
</template>
<!-- child.html -->
<template>
  <span>Tabbing to the custom element moves focus to the whole component.</span>
  <br /><input type="text" />
</template>
// child.js
import { LightningElement } from "lwc";

export default class Child extends LightningElement {}
选择整个自定义组件的代码输出。

在自定义组件中委派焦点

创建包含可聚焦元素的自定义组件时,您可能会发现自己需要管理选项卡索引或手动将焦点设置在这些元素上。要自动管理焦点,请设置为 。delegatesFocustrue

假设我们有一个带有按钮的自定义组件,其行为类似于本机 HTML 按钮。

<!-- coolButton.html -->
<template>
  <button>Focus!</button>
</template>
// coolButton.js
import { LightningElement } from "lwc";

export default class CoolButton extends LightningElement {
  static delegatesFocus = true;
}

使用可实现以下情况。delegatesFocus

  • 使用 将焦点添加到本机按钮 HTML 元素。coolButton.focus()
  • 如果单击阴影 DOM 中的某个节点,并且该节点不是可聚焦区域,则第一个可聚焦区域将变为焦点。这类似于单击标签并将焦点跳转到输入时。
  • 当影子 DOM 中的节点获得焦点时,除了焦点元素之外,CSS 选择器还会应用于主机。:focus

注意

不要使用 with,因为它会偏离焦点顺序。tabindexdelegatesFocus

移动就绪组件

Lightning Web 组件支持在桌面和移动设备上使用组件。在桌面上,支持各种 Web 浏览器。在移动设备上,支持的环境包括 Salesforce 分布式移动应用程序。这两种环境之间的差异不仅仅是屏幕尺寸;有新的限制,也有移动端的扩展功能。若要构建在移动体验中表现良好的组件,请遵循我们的移动就绪组件指南。

《移动和离线开发人员指南》中提供了完整的详细信息,该指南是执行移动开发时本指南的重要配套。

访问Salesforce资源

Lightning 组件可以访问全局 Salesforce 值,例如标签、资源和用户。

访问静态资源

从作用域模块导入静态资源。静态资源可以是存档(如 .zip 和 .jar 文件)、图像、样式表、JavaScript 和其他文件。@salesforce/resourceUrl

在导入静态资源之前,请先创建并上传文件。

创建并上传静态资源

要创建和上传静态资源,请执行以下操作:

  1. 在“设置”中,输入“快速查找”框,然后选择“静态资源”。Static Resources
  2. 单击“新建”。
  3. 在文本框中,输入用于标识 LWC 代码中的资源的文本。Name注意静态资源名称只能包含下划线和字母数字字符,并且在组织中必须是唯一的。它必须以字母开头,不能包含空格,不能以下划线结尾,也不能包含两个连续的下划线。
  4. 在文本区域中,指定资源的可选描述。Description
  5. 单击“浏览”导航到要上载的资源的本地副本。最大文件大小为 5 MB。一个组织最多可以有 250 MB 的静态资源。
  6. 设置用户会话,包括 API 和 Experience Cloud 用户会话:Cache Control
    • Private指定 Salesforce 服务器上缓存的静态资源数据不得与其他用户共享。静态资源仅存储在当前用户会话的缓存中。
    • Public指定缓存在 Salesforce 服务器上的静态资源数据与组织中的其他用户共享,以缩短加载时间。缓存后,所有 Internet 流量(包括未经身份验证的用户)都可以访问该资源。
  7. 点击保存

有关更多信息,请参阅 Salesforce 帮助:静态资源。

导入静态资源

使用此语法导入静态资源。

import myResource from "@salesforce/resourceUrl/resourceReference";
import myResource from "@salesforce/resourceUrl/namespace__resourceReference";
  • myResource– 引用静态资源的名称。
  • resourceReference– 静态资源的名称。
  • namespace– 如果静态资源位于托管包中,则此值为托管包的命名空间。

让我们看一些示例代码。

JavaScript 代码导入两个资源:Trailhead 徽标和 Trailhead 角色的图像。

// miscStaticResource.js
import { LightningElement } from "lwc";
import TRAILHEAD_LOGO from "@salesforce/resourceUrl/trailhead_logo";
import TRAILHEAD_CHARACTERS from "@salesforce/resourceUrl/trailhead_characters";

export default class MiscStaticResource extends LightningElement {
  // Expose the static resource URL for use in the template
  trailheadLogoUrl = TRAILHEAD_LOGO;

  // Expose URL of assets included inside an archive file
  einsteinUrl = TRAILHEAD_CHARACTERS + "/images/einstein.png";
}

静态资源可以是具有嵌套目录结构的存档文件。若要引用存档中的项目,请连接字符串以创建项目的路径,如示例中对 build .einsteinUrl

若要引用模板中的资源,请使用语法,该语法与用于引用任何 JavaScript 属性的语法相同。{property}

<!-- miscStaticResource.html -->
<template>
  <lightning-card title="MiscStaticResource" icon-name="custom:custom19">
    <div class="slds-m-around_medium">
      <img src={trailheadLogoUrl} />
      <img src={einsteinUrl} />
    </div>
  </lightning-card>
</template>

在 Salesforce DX 项目中,静态资源位于目录中。不能创建 的子目录。/force-app/main/default/staticresourcesstaticresources

为每个资源创建一个元数据文件。元数据文件定义资源的内容类型。.resource-meta

提示

此代码示例是 github.com/trailheadapps/lwc-recipes 存储库中的组件。miscStaticResource

访问内容资产文件

从作用域模块导入内容资产文件。将 Salesforce 文件转换为内容资产文件,以便在自定义应用程序和 Experience Builder 模板中使用该文件。@salesforce/contentAssetUrl

import myContentAsset from "@salesforce/contentAssetUrl/contentAssetReference";
import myContentAsset from "@salesforce/contentAssetUrl/namespace__contentAssetReference";
  • myContentAsset– 引用资源文件的名称。
  • contentAssetReference– 资源文件的名称。资产文件名只能包含下划线和字母数字字符,并且必须在您的组织中是唯一的。它必须以字母开头,不能包含空格,不能以下划线结尾,也不能包含两个连续的下划线。
  • namespace– 如果资源文件位于托管包中,则此值为托管包的命名空间。

让我们看一些示例代码。

JavaScript 代码导入两个内容资产文件。

// assetFileExample.js
import { LightningElement } from "lwc";
import SALES_WAVE_LOGO from "@salesforce/contentAssetUrl/SalesWaveLogo";
import PARTNER_LOGOS from "@salesforce/contentAssetUrl/PartnerLogos";

export default class AssetFileExample extends LightningElement {
  // Expose the asset file URL for use in the template
  salesWaveLogoUrl = SALES_WAVE_LOGO;

  // Expose URL of assets included inside an archive file
  goldPartnerLogoUrl = PARTNER_LOGOS + "pathinarchive=images/gold_partner.png";
}

内容资产文件可以是具有嵌套目录结构的存档文件。若要引用存档中的项目,请连接字符串以创建项目的路径,如示例中对 build .若要指定存档中内容资产文件的路径,请使用该参数。goldPartnerLogoUrlpathinarchive

若要引用模板中的资源,请使用语法,该语法与用于引用任何 JavaScript 属性的语法相同。{property}

<!-- assetFileExample.html -->
<template>
  <lightning-card title="Asset File Example" icon-name="custom:custom19">
    <div class="slds-m-around_medium">
      <img src={salesWaveLogoUrl} />
      <img src={goldPartnerLogoUrl} />
    </div>
  </lightning-card>
</template>

在 Salesforce DX 项目中,资产文件位于目录中。不能创建 的子目录。创建定义资产文件的元数据文件。/force-app/main/default/contentassetscontentassets.asset-meta

使用 SVG 资源

您可以通过两种方式将 SVG 资源添加到组件中。将其直接添加到组件的 HTML 模板中。将 SVG 资源作为静态资源上传,并将其导入到组件的 JavaScript 文件中。

SVG(可缩放矢量图形)是一种基于 XML 的图像格式,可用于定义线条、曲线、形状和文本。下面是一个文件示例,其中包含红色矩形、绿色圆圈和白色文本,上面写着“SVG”。

<svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
    <rect width="100%" height="100%" fill="red"></rect>
    <circle cx="150" cy="100" r="80" fill="green"></circle>
    <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>

如您所见,每个形状都有一个标签,用于描述它是什么(例如,是一个矩形)、它的尺寸和颜色。rect

将 SVG 资源直接添加到 HTML 模板

若要在 HTML 模板中包含 SVG 资源,请像任何其他元素一样将其包含在标记中。<template>

<template>
    <svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
        <rect width="100%" height="100%" fill="red"></rect>
        <circle cx="150" cy="100" r="80" fill="green"></circle>
        <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
    </svg>
</template>

将 SVG 资源作为静态资源导入

  1. 在 SVG 文件中,向标记添加属性。id<svg><svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg" id="logo"> <rect width="100%" height="100%" fill="red"></rect> <circle cx="150" cy="100" r="80" fill="green"></circle> <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text> </svg>
  2. 将 SVG 资源作为静态资源上传到您的组织。
  3. 在组件的 JavaScript 文件中,导入静态资源。定义一个字段,该字段将对静态资源的引用与其 .id// myComponent.js import { LightningElement } from "lwc"; import SVG_LOGO from "@salesforce/resourceUrl/logo"; export default class myComponent extends LightningElement { svgURL = `${SVG_LOGO}#logo`; }
  4. 使用 SVG 元素引用 HTML 模板中的字段。<use><!-- myComponent.html --> <template> <svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"> ; <use xlink:href={svgURL}></use> </svg> </template>

支持的SVG标签

Lightning Web 组件支持受限制的 SVG 标签列表。

  • <svg>
  • <a>
  • <altglyph>
  • <altglyphdef>
  • <altglyphitem>
  • <animatecolor>
  • <animatemotion>
  • <animatetransform>
  • <audio>
  • <canvas>
  • <circle>
  • <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>
  • <title>
  • <tref>
  • <tspan>
  • <video>
  • <view>
  • <vkern>
  • <use>

访问标签

从作用域模块导入标签。自定义标签是存储在 Salesforce 中的文本值,可以翻译成 Salesforce 支持的任何语言。使用自定义标签创建多语言应用程序,以用户的母语显示信息(例如,帮助文本或错误消息)。@salesforce/label

import labelName from "@salesforce/label/labelReference";
  • labelName– 引用标签的名称。
  • labelReference– 组织中标签的名称,格式为 。我们之所以使用这种格式,是因为它与托管软件包、Visualforce 和其他 Salesforce 技术中使用的格式相同。您可以使用相同的格式来访问标签,无论您在何处访问它们。namespace.labelNamemyns.labelName

此示例代码导入两个标签。

// labelExample.js
import { LightningElement } from "lwc";

// Import the URL for the static resource named 'salesforceLogo'
import SALESFORCE_LOGO from "@salesforce/resourceUrl/salesforceLogo";

// Import custom labels
import greeting from "@salesforce/label/c.greeting";
import salesforceLogoDescription from "@salesforce/label/c.salesforceLogoDescription";

export default class LabelExample extends LightningElement {
  // Expose the static resource URL to use in the template.
  logoUrl = SALESFORCE_LOGO;

  // Expose the labels to use in the template.
  label = {
    greeting,
    salesforceLogoDescription,
  };
}

若要在模板中使用标签,请使用与引用任何 JavaScript 属性相同的语法。{property}

<!-- labelExample.html -->
<template>
  <c-page-header
    header="Using static resources and custom labels"
    description="This sample shows how to reference external items like static resources and custom labels"
  ></c-page-header>

  <c-card>
    <img src={logoUrl} alt={label.salesforceLogoDescription} width="100" /><br />
    <br />
    {label.greeting}
  </c-card>
</template>

在 Salesforce DX 项目中,标签文件可以位于 的任何子目录中。例如,这个名为 的文件位于 中。force-app/main/defaultExampleLabels.labels-meta.xmlforce-app/main/default/mylabels

<?xml version="1.0" encoding="UTF-8"?>
<CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata">
    <labels>
        <fullName>quoteManual</fullName>
        <value>This is a manual quote.</value>
        <language>en_US</language>
        <protected>false</protected>
        <shortDescription>Manual Quote</shortDescription>
    </labels>
    <labels>
        <fullName>quoteAuto</fullName>
        <value>This is an automatically generated quote.</value>
        <language>en_US</language>
        <protected>false</protected>
        <shortDescription>Automatic Quote</shortDescription>
    </labels>
</CustomLabels>

访问国际化属性

从作用域模块导入国际化属性。Lightning Web 组件具有国际化属性,您可以使用这些属性针对全球用户、跨语言、跨货币和时区调整组件。@salesforce/i18n

在单一货币组织中,Salesforce 管理员为其组织设置货币区域设置、默认语言、默认区域设置和默认时区。用户可以在其个人设置页面上设置其个人语言、区域设置和时区。

基本 Lightning 组件会自动适应运行它们的 Salesforce 组织的语言、区域设置和时区设置。若要国际化组件以便它们也适应,请使用国际化属性。

import internationalizationPropertyName from @salesforce/i18n/internationalizationProperty
  • internationalizationPropertyName– 引用国际化属性的名称。
  • internationalizationProperty– 国际化属性。

为当前用户返回属性值。

Internationalization 属性描述示例值
lang语言en-US
dir文本方向ltr
locale现场en-CA
defaultCalendar基于区域设置的默认日历格式Gregorian
defaultNumberingSystem基于区域设置的默认编号系统latn
calendarData用户区域设置的日历格式gregorian: {dayPeriods : {...}, days: {...}, eras: {...}, months: {...}, quarters: {...}}
currency货币代码CAD
firstDayOfWeek一周的第一天1
isEasternNameStyle指定名称是否遵循东方风格,例如,last name first name [middle] [suffix]
common.calendarData常见日历格式的补充日历数据。该值由 确定。defaultCalendargregorian:{calendarSystem:'solar',eras:{'0':{_end:'0-12-31'},'0':{_end:'1-01-01'}}}
common.digits数值的 Unicode 字符0123456789
dateTime.shortDateFormat短样式日期格式MM/dd/yyyy
dateTime.mediumDateFormat中型日期格式MMM d, yyyy
dateTime.longDateFormat长样式日期格式MMMM d, yyyy
dateTime.shortDateTimeFormat短样式日期时间格式MM/dd/yyyy h:mm a
dateTime.mediumDateTimeFormat中型日期时间格式MMM d, yyyy h:mm:ss a
dateTime.longDateTimeFormat长样式日期时间格式MMMM d, yyyy ‘at’ h:mm:ss a * z
dateTime.shortTimeFormat短样式时间格式h:mm a
dateTime.longTimeFormat长样式时间格式h:mmss a
number.currencyFormat货币格式#, ##0.00
number.currencySymbol货币符号$
number.decimalSeparator小数点分隔符.
number.exponentialSign指数符号E
number.groupingSeparator分组分隔符,
number.infinity无穷大符号
number.minusSign减号符号-
number.nan“不是数字”的字符串NaN
number.numberFormat数字格式#, ##0.###
number.perMilleSign每千个符号
number.percentFormat百分比格式#, ###0%
number.percentSign百分号符号%
number.plusSign加号符号+
number.superscriptExponentSign上标指数符号×
showJapaneseCalendar指定是否以日本英制日历格式显示日期
timeZone时区America/Los_Angeles

注意

对于 LWR 站点,and 映射到为站点配置的语言,并由浏览器的时区而不是用户的个人设置决定。 此外,不支持 、 和 。 如果更新了网站或组织语言配置,则必须重新发布网站。langlocaletimeZonecurrencynumber.currencySymbolnumber.currencyFormat

国际化值格式

国际化属性返回的值对应于 Unicode 区域设置数据标记语言 (LDML) 描述的格式。例如,遵循日历数据 LDML 规范。common.calendarData

示例:将日期格式化为本地化字符串

日期在不同语言中的格式不同。例如,美国使用月、日、年,如 2/15/19。英国使用日、月、年,如 15/2/19。

要根据用户的语言设置日期格式,请从模块导入用户的区域设置。若要设置日期格式,请使用 JavaScript 国际化 API。@salesforce/i18n/locale

// formatDate.js
import { LightningElement } from "lwc";
import LOCALE from "@salesforce/i18n/locale";

export default class FormatDate extends LightningElement {
  date = new Date(2020, 6, 7);
  formattedDate = new Intl.DateTimeFormat(LOCALE).format(this.date);
}
<!-- formatDate.html -->
<template> {formattedDate} </template>

或者,使用 lightning-formatted-date-time base 组件进行日期和时间格式设置。

示例:将数字格式化为本地化货币字符串

此示例使用 and 属性将数字格式化为本地化的货币字符串。localecurrency

// formatNumber.js
import { LightningElement } from "lwc";
import LOCALE from "@salesforce/i18n/locale";
import CURRENCY from "@salesforce/i18n/currency";

export default class FormatNumber extends LightningElement {
  number = 123456.78;
  formattedNumber = new Intl.NumberFormat(LOCALE, {
    style: "currency",
    currency: CURRENCY,
    currencyDisplay: "symbol",
  }).format(this.number);
}
<!-- formatNumber.html -->
<template> {formattedNumber} </template>

该对象定义货币输出。对于用户给定的区域设置,将数值显示为货币,并提供本地化的货币符号。Intl.NumberFormat

或者,将闪电格式的数字基组件用于数字、货币和百分比格式。

设置 HTML 属性

若要将国际化属性绑定到 HTML 属性,请将它们作为私有属性存储在组件的 JavaScript 文件中。在这里,确定用户的语言并指定文本在 HTML 中的显示方向。langdir

// languageExample.js
import { LightningElement } from "lwc";
import LANG from "@salesforce/i18n/lang";
import DIR from "@salesforce/i18n/dir";

export default class LanguageExample extends LightningElement {
  lang = LANG;
  dir = DIR;
}

在 HTML 模板中,使用 引用值。{property}

<!-- languageExample.html -->
<template>
  <p lang={lang} dir={dir}><!-- Localized text in a paragraph --></p>
</template>

获取有关当前用户的信息

若要获取有关当前用户的信息,请使用作用域模块。@salesforce/user

import property from "@salesforce/user/property";
  • property– 支持的属性包括 ,即用户的 ID,以及 ,这是一个布尔值,指示用户是否为访客用户。使用该属性检查用户是否在Experience Builder站点中进行了身份验证。IdisGuestisGuest

此示例代码导入当前用户 ID 并将其分配给属性以在 HTML 模板中提供访问权限。userId

// miscGetUserId.js
import { LightningElement } from "lwc";
import Id from "@salesforce/user/Id";

export default class MiscGetUserId extends LightningElement {
  userId = Id;
}

若要在模板中引用用户 ID,请使用语法,该语法与用于引用任何 JavaScript 属性的语法相同。{property}

<!-- miscGetUserId -->
<template>
  <lightning-card title="MiscGetUserId" icon-name="custom:custom19">
    <div class="slds-m-around_medium">
      <p>User Id:</p>
      {userId}
    </div>
  </lightning-card>
</template>

提示

此代码示例是 lwc-recipes 存储库中的组件。miscGetUserId

获取有关当前 Experience Builder 站点的信息

从和作用域模块导入有关当前 Experience Builder 站点的信息。@salesforce/community@salesforce/site

重要

如果组件导入 或 ,则它只能面向 Experience Builder 页面。您不能在任何其他 Salesforce 容器中使用该组件。请参阅为 Experience Builder 配置组件。@salesforce/community@salesforce/site

Experience Builder 站点由包含管理设置(如电子邮件和成员资格配置)的 Network 对象和包含域和页面设置信息的 Site 对象组成。使用该模块,您可以检索站点网络部分的 ID,而该模块允许您检索站点部分的 ID。@salesforce/community@salesforce/site

@salesforce/社区

从中导入当前 Experience Builder 站点的网络 ID 和基本路径。@salesforce/community

import propertyName from "@salesforce/community/property";
  • property– 支持的属性包括:
    • Id– 当前站点的 ID。例如,导入要作为参数传递给 API 的 ID。
    • basePath– 基本路径是位于域之后的站点 URL 部分。因此,如果您的网站域名是创建网站时添加的 URL 值,则网站的 URL 为 .在本例中,是基本路径。UniversalTelco.force.commyPartnerSiteUniversalTelco.force.com/myPartnerSite/smyPartnerSite/s例如,若要生成跨多个站点工作的链接组件,请导入基本路径并使用它来动态构造完整的站点 URL。
  • propertyName– 引用导入的 Experience Builder 属性的名称。

此示例代码调用一个 Apex 控制器,该控制器返回当前站点的所有源项。

// miscGetCommunityId.js
import { LightningElement } from "lwc";
import Id from "@salesforce/community/Id";
import getFeedElementPageForCommunity from "@salesforce/apex/CommunityFeedController.getFeedElementPageForCommunity";

export default class CommunityFeedElementPage extends LightningElement {
  @wire(getFeedElementPageForCommunity, { networkId: "$Id" })
  feedElementPage;
}

Apex 控制器采用社区 ID,以确保源结果的范围限定为该站点。

// CommunityFeedController.apex
public with sharing class CommunityFeedController {
    @AuraEnabled(cacheable=true)
    public static ConnectApi.FeedElementPage getFeedElementPageForCommunity(String networkId) {
    return ConnectApi.ChatterFeeds.getFeedElementsFromFeed(networkId, ConnectApi.FeedType.UserProfile,
        'me', 3, ConnectApi.FeedDensity.FewerUpdates, null, null,
        ConnectApi.FeedSortOrder.LastModifiedDateDesc, ConnectApi.FeedFilter.CommunityScoped);
    }
}

@salesforce/站点

从中导入当前 Experience Builder 站点的站点 ID 和活动语言列表。@salesforce/site

import propertyName from "@salesforce/site/property";
  • property– 支持的属性包括:
    • Id– 当前站点的 ID。例如,导入要作为参数传递给 API 的 ID。
    • activeLanguages– Experience Builder 站点中的活动语言列表包括默认站点语言和所有其他活动语言的元数据。您可以在Experience Builder的“设置”|”语言。非活动语言将被排除在列表中。返回值是语言对象的数组,其中每个对象都包含语言的标签和代码,例如 en-US。数组按标签的字母顺序排序。
  • propertyName– 引用导入的 Experience Builder 属性的名称。

此示例代码在语言选择器组件中使用。activeLanguages

// languagePicker.js
// Sample data: [{ code: 'en-US', label: 'English (US)' },{ code: 'fr', label: 'Françias' }]
import activeLanguages from "@salesforce/site/activeLanguages";

import { LightningElement } from "lwc";
import currentLanguage from "@salesforce/i18n/lang";
import basePath from "@salesforce/community/basePath";

export default class LanguagePicker extends LightningElement {
  get options() {
    return activeLanguages.map((x) => ({ value: x.code, ...x }));
  }

  get currentValue() {
    return currentLanguage;
  }

  handleLanguageSelect(evt) {
    const selectedLanguageCode = evt.detail.value;
    // locale is in base path and needs to be replaced with new locale
    const newBasePath = this.updateLocaleInBasePath(
      basepath,
      currentLanguage,
      selectedLanguageCode,
    );

    const currentUrl = window.location.pathname;
    if (currentUrl) {
      const restOfUrl = currentUrl.substring(basepath.length);
      window.location.href = window.location.origin + newBasePath + restOfUrl;
    } else {
      // WARNING: this is a current limitation of Lightning Locker in LWR sites
      // Locker must be disabled to reference the global window object
      console.warn(
        "Lightning Locker must be disabled for this language picker component to redirect",
      );
    }
  }

  updateLocaleInBasePath(path, oldLocale, newLocale) {
    if (path.endsWith("/" + oldLocale)) {
      // replace with new locale
      return path.replace(new RegExp("/" + oldLocale + "$"), "/" + newLocale);
    } else {
      // since the default locale is not present in the base path,
      // append the new locale
      return path + "/" + newLocale;
    }
  }
}

该组件使用 lightning-combobox 显示下拉语言选择器。

<!-- languagePicker.html -->
<template>
    <lightning-combobox
        options={options}
        value={currentValue}
        onchange={handleLanguageSelect}
    ></lightning-combobox>
</template>

检查权限

从 和 作用域模块导入 Salesforce 权限。根据上下文用户的权限自定义组件的行为。@salesforce/userPermission@salesforce/customPermission

提示

请参阅 lwc-recipes 存储库中的配方。miscPermissionBasedUI

要检查用户是否具有权限,请导入对该权限的静态引用,并评估它是 还是 。trueundefined

import hasPermission from "@salesforce/userPermission/PermissionName";

自定义权限可以包含命名空间。组织使用命名空间作为其自己的自定义和包的唯一标识符。如果自定义权限是从托管包安装的,请在权限名称前面加上命名空间,后跟。__

import hasPermission from "@salesforce/customPermission/PermissionName";
import hasPermission from "@salesforce/customPermission/namespace__PermissionName";

静态引用的名称由您选择。我们选择的格式是指示引用包含布尔值。has{Permission}

此示例检查当前用户是否具有 ViewSetup 标准权限。

// app.js
import { LightningElement } from 'lwc';
import hasViewSetup from '@salesforce/userPermission/ViewSetup';

export default class App extends LightingElement {
    get isSetupEnabled() {
        return !hasViewSetup;
    }

    openSetup(e) {...}
}

如果用户具有该权限,则会导致属性的计算结果为 ,因此不会禁用该按钮。!hasViewSetupdisabledfalse

<!-- app.html -->
<template>
  <setup-panel-group>
    <setup-button disabled={isSetupEnabled} onclick={openSetup}></setup-button>
  </setup-panel-group>
</template>

此示例检查当前用户是否具有从具有命名空间的托管包安装的 ViewReport 自定义权限。acme

// app.js
import { LightningElement } from "lwc";
import hasViewReport from "@salesforce/customPermission/acme__ViewReport";

export default class App extends LightingElement {
  get isReportVisible() {
    return hasViewReport;
  }
}

如果用户具有该权限,则组件将显示组件 。expense-report

<!--– app.html -->
<template>
  <common-view></common-view>

  <template lwc:if={isReportVisible}>
    <c-expense-report></c-expense-report>
  </template>
</template>

提示

将父 Aura 组件动态引用权限的任何情况替换为 Lightning Web 组件中的静态引用。静态引用效率更高,因为它不需要网络调用。

Access 客户端外形规格

若要访问运行浏览器的硬件的外形规格,请导入作用域模块。@salesforce/client/formFactor

import formFactorPropertyName from "@salesforce/client/formFactor";
  • formFactorPropertyName– 一个名称,指的是运行浏览器的硬件的外形规格。可能的值为:
    • Large– 桌面客户端。
    • Medium– 平板电脑客户端。
    • Small– 电话客户端。

将外形规格传递给电线适配器,以获取用于创建记录的默认布局和对象信息。getRecordCreateDefaults

import { getRecordCreateDefaults } from 'lightning/uiRecordApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import FORM_FACTOR from '@salesforce/client/formFactor';

@wire(getRecordCreateDefaults, { objectApiName: ACCOUNT_OBJECT, formFactor: FORM_FACTOR})
accountDefaults;

使用 DOM

文档对象模型 (DOM) 表示 HTML 页面,以便您可以使用 JavaScript 或标准 DOM API 处理其内容、结构和样式。例如,您可以使用标准 DOM API 访问组件中的元素,无论是否使用模板引用选择器。虽然我们不建议操作 DOM,但某些第三方 JavaScript 库需要接管 DOM。querySelector()

有关 DOM 的更多信息,请参阅 MDN Web 文档:文档对象模型 (DOM)。

访问组件拥有的元素

要访问使用标准 DOM API 的组件呈现的元素,请使用 或 。要在没有选择器的情况下查找 DOM 中的元素,请使用 refs。querySelector()this.templatethis

querySelector()

访问 DOM 中元素的标准方法是使用 .要查找影子 DOM 节点,请使用 或 on 。对于轻量级 DOM 节点,请在 上使用以下方法之一。querySelector()querySelector()querySelectorAll()this.templatethis

// shadow DOM
this.template.querySelector("div");

// light DOM
this.querySelector("div");

注意

您也可以使用 .{element}.template.querySelector

使用这些方法查找组件呈现的元素。

  • 元素的顺序不保证。
  • 未呈现到 DOM 的元素不会在结果中返回。querySelector
  • 不要将 ID 选择器与 一起使用。呈现模板时,在 HTML 模板中定义的 ID 可能会转换为全局唯一值。如果在 JavaScript 中使用 ID 选择器,它将与转换后的 ID 不匹配。querySelector
  • 对于轻量级 DOM 组件,搜索直接模板之外的元素,例如轻量级 DOM 子级。使用更具体的选择器来缩小方法的范围。this.querySelector()
  • 如果组件在启用了 Lightning Locker 的组织中运行,请注意潜在的内存泄漏。如果可能,组织应启用 Lightning Web Security。或者,考虑使用 refs 而不是 .querySelector
<!-- example.html -->
<template>
  <div>First <slot name="task1">Task 1</slot></div>
  <div>Second <slot name="task2">Task 2</slot></div>
</template>
// example.js
import { LightningElement } from "lwc";

export default class Example extends LightningElement {
  renderedCallback() {
    this.template.querySelector("div"); // <div>First</div>
    this.template.querySelector("span"); // null
    this.template.querySelectorAll("div"); // [<div>First</div>, <div>Second</div>]
  }
}

重要

不要使用 或 全局属性来查询 DOM 元素。请参阅 DOM 访问遏制。此外,我们不建议使用 JavaScript 来操作 DOM,除非您通过 lightning/platformResourceLoader 使用第三方库。最好使用 Lightning Web 组件 HTML 指令来编写声明性代码。windowdocument

裁判

Refs 查找没有选择器的 DOM 元素,并且只查询包含在指定模板中的元素。首先,将指令添加到元素中并为其赋值。要调用该引用,请使用 .在此示例中,该元素具有指令 ,该指令引用以访问 at 运行时。lwc:refthis.refs<div>lwc:ref="myDiv"this.refs<div>

<template>
  <div lwc:ref="myDiv"></div>
</template>
export default class extends LightningElement {
  renderedCallback() {
    console.log(this.refs.myDiv);
  }
}

考虑

在调用 之前,必须定义该指令。如果调用不存在的 ref,则返回 .如果模板包含重复的指令,则引用最后一个 .lwc:refthis.refsthis.refsundefinedlwc:refthis.refs<div>

<template>
  <div lwc:ref="myDiv"></div>
  <div lwc:ref="myDiv"></div>
</template>

this.refs是一个普通的只读对象。尝试在其组件中添加、修改或删除属性会导致运行时错误。它的键是一个字符串,它的值是一个 DOM 元素。在 light DOM 和 shadow DOM 中引用元素的语法是相同的。this.refs

Ref 是可配置和可写的,因此在组件中定义的 ref 会覆盖 中的 ref。LightningElement.prototype

您不能应用于元素,也不能应用于轻量级 DOM 中的元素。lwc:ref<template><slot>

<template lwc:render-mode="light">
  <template lwc:if={myTemplate} lwc:ref="myTemplate"></template>
  <!-- Not allowed -->
</template>
<template lwc:render-mode="light">
  <slot lwc:ref="mySlot"></slot>
  <!-- Not allowed -->
</template>

如果将 or 循环置于 or 循环中,模板编译器将引发错误。lwc:reffor:eachiterator:*

<template for:each={items} for:item="item">
  <div key={item} lwc:ref="foo"></div>
  <!-- Not allowed -->
</template>

多个模板

this.refs指多模板组件中最近呈现的模板。当模板更改时,对象也会更改。this.refs

import a from "./a.html";
import b from "./b.html";

export default class extends LightningElement {
  count = 0;

  render() {
    return this.count % 2 === 0 ? a : b;
  }

  renderedCallback() {
    console.log(this.refs);
  }

  increment() {
    this.count++;
  }
}

const cmp = createElement("c-component", { is: Component });
// Logs `this.refs` for a.html

cmp.increment();
// Logs `this.refs` for b.html

cmp.increment();
// Logs `this.refs` for a.html

要有条件地定义基于 的元素,请在一个父模板下创建多个子模板。在此示例中,指呈现的任何子模板中的元素。<template lwc:if={boolean}>this.refs.toggleDarkMode

<template>
  <template lwc:if={darkMode}>
    <button lwc:ref="toggleDarkMode">Enable Light Mode</button>
  </template>
  <template lwc:else>
    <button lwc:ref="toggleDarkMode">Enable Dark Mode</button>
  </template>
</template>

影子 DOM

Shadow DOM 是一种封装 Web 组件的内部文档对象模型 (DOM) 结构的标准。封装 DOM 使开发人员能够共享组件并保护组件不被任意 HTML、CSS 和 JavaScript 操纵。内部 DOM 结构称为影子树。影子树会影响您使用 CSS、事件和 DOM 的方式。

由于并非所有浏览器都实现了影子 DOM,因此 LWC 对 Lightning Experience 和 Experience Cloud 使用合成影子 polyfill。polyfill 是允许功能在 Web 浏览器中工作的代码。

解释影子 DOM

https://youtube.com/watch?v=K5i9zMzVlzM

注意

如果您在 Lightning Experience 或 Experience Cloud 之外使用 LWC,例如在 Lightning Out 中,LWC 将在原生阴影中渲染。请参阅 Salesforce Platform 上的编译时差异。

影子树

为了理解影子树,让我们看一些标记。此标记包含两个 Lightning Web 组件:和 .文档片段定义了 DOM 和影子树之间的边界。阴影根下方的元素位于阴影树中。c-todo-appc-todo-item#shadow-root

<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>

让我们看看如何在这些区域中使用阴影树。CSS的

在父组件中定义的 CSS 样式不会泄漏到子组件中。在我们的示例中,样式表中定义的样式不会设置组件中元素的样式,因为样式不会进入阴影树。请参阅 CSS。ptodoApp.csspc-todo-item事件

为了防止暴露组件的内部详细信息,如果事件冒泡并越过阴影边界,则某些属性值会更改以匹配侦听器的范围。请参阅事件重定向。访问元素

影子树中的元素无法通过传统的 DOM 查询方法访问。代码无法使用或访问 Lightning Web 组件的影子树。例如,代码无法调用以选择 Lightning Web 组件的影子树中的节点。要访问自己的影子树,Lightning Web 组件会调用 .请参阅组件拥有的 Access 元素。documentdocument.bodydocument.querySelector()this.template.querySelector()访问插槽

插槽是父组件传递到组件主体中的标记的占位符。通过插槽传递给组件的 DOM 元素不归该组件所有,也不在组件的影子树中。要访问通过插槽传递的 DOM 元素,请调用 。该组件不拥有这些元素,因此您不使用 .请参阅将标记传递到槽中。this.querySelector()template测试组件

请参阅 DOM 检查测试可能会发生变化。

提示

观看 Salesforce 开发人员布道师 Alba Rivas 对 Shadow DOM 的解释。

DOM API 接口

不要使用这些 DOM API 访问使用 Lightning Locker 的组织中的组件影子树。如果您使用的第三方 JavaScript 库使用这些 DOM API 访问组件的影子树,请与库作者合作提交并修复问题。

  • Document.prototype.getElementById
  • Document.prototype.querySelector
  • Document.prototype.querySelectorAll
  • Document.prototype.getElementsByClassName
  • Document.prototype.getElementsByTagName
  • Document.prototype.getElementsByTagNameNS
  • Document.prototype.getElementsByName
  • document.body.querySelector
  • document.body.querySelectorAll
  • document.body.getElementsByClassName
  • document.body.getElementsByTagName
  • document.body.getElementsByTagNameNS

Lightning Locker 通过阻止这些 API 来防止您破坏 Lightning Web 组件之间的影子 DOM 封装。但是,在 Aura 组件版本 39.0 及更早版本中,Lightning Locker 在组件级别被禁用,因此 Aura 组件的代码可能会失败。

这些 API 不受 Lightning Web Security (LWS) 的限制。LWS 通过对所有组件的 ShadowRoot 属性强制执行值来防止破坏影子 DOM 封装。closedmode

重要

Shadow DOM polyfill 包含接口的补丁。如果您习惯于监视 DOM 树中的更改,请断开它,否则将造成内存泄漏。请注意,组件只能在其自己的模板中观察到突变。它无法观察到其他自定义元素的影子树中的突变。MutationObserverMutationObserver

轻量级 DOM

Lightning Web 组件目前在每个组件上强制执行影子 DOM,封装组件的内部标记,并使编程代码无法访问它。当您使用轻量级 DOM 时,您的组件位于影子 DOM 之外,并避免了影子 DOM 限制。这种方法简化了第三方集成和全局样式设置。

在深入探讨轻量级 DOM 的细节之前,让我们先看看不同的 DOM 结构是如何在 DOM 中呈现的。首先,考虑影子树的一些标记。原生影子 DOM 在标签中呈现组件。但是,Lightning Experience 和 Experience Cloud 改用合成阴影,这模仿了本机阴影行为。#shadow-root

使用轻量级 DOM 时,组件内容将附加到主机元素,而不是其影子树。然后,可以像文档宿主中的任何其他内容一样访问它,从而提供与不受影子 DOM 约束的内容类似的行为。

<my-app>
  <my-header>
    <p>Hello World</p>
  </my-header>
</my-app>

如需全面概述,请参阅 Google Web Fundamentals: Shadow DOM v1。

与影子 DOM 相比,Light DOM 具有几个优点。

  • CSS 主题和品牌:Light DOM 支持全局样式,可以轻松地将自定义品牌应用于组件和子组件。
  • 第三方工具和测试:使用轻量级 DOM,第三方工具可以遍历 DOM,从而启用标准的浏览器查询 API(如 和),而无需遍历影子根。例如,轻量级 DOM 使 LWR 站点中的标准组件能够跟踪事件。querySelectorquerySelectorAll
  • 可访问性:Light DOM 不限定 ID 的范围,并允许两个单独的组件引用另一个 ID。例如,即使元素位于单独的组件中,也可以引用。<label for="my-input"><input type="text" id="my-input">

使用 Light DOM 的准则

使用轻量级 DOM 会使您的组件暴露给 DOM 抓取,因此,如果您正在处理敏感数据,我们建议改用影子 DOM。换言之,轻量级 DOM 不提供影子 DOM 封装带来的好处,后者可以防止未经授权访问影子树。由于 DOM 对其他组件和第三方工具的遍历是开放的,因此您有责任保护您的轻量级 DOM 组件。

在应用中同时使用轻量级 DOM 和影子 DOM 时,请考虑以下最佳做法。

  • 您可以将轻量级 DOM 子组件嵌套在父阴影 DOM 组件中,反之亦然。
  • 我们建议将深度嵌套的轻量级 DOM 组件封装在顶层的单个阴影 DOM 组件中。然后,您可以在影子根下的所有组件之间共享样式。
  • 在文档级别查询元素或注入样式时要小心。影子树可以位于文档和组件之间。
  • 您可以通过 CSS 自定义属性和 ::p art 覆盖阴影 DOM 样式。但是,组件所有者负责公开扩展点,这可以防止下游使用者设置任意元素的样式。

哪些内容不适用于 Light DOM

  • 不支持将轻量级 DOM 限制为特定命名空间。
  • 不支持分发以轻量级 DOM 渲染的组件。托管包中的组件引用使用命名空间,并会导致命名空间冲突。c
  • 基本组件始终在影子 DOM 中呈现。
  • Aura 组件不能使用轻量级 DOM。但是,Aura 组件可以包含使用轻量级 DOM 的 LWC 组件。
  • 插槽上的生命周期挂钩永远不会被调用,因为该元素不会在 DOM 中呈现。slot
  • 不支持在迭代器中使用插槽。例如:for:each
<!-- This results in a runtime error -->
<template for:each={items} for:item="item">
  <div key={item.id}>
    <my-repeateditem item={item}>
      <slot></slot>
    </my-repeateditem>
  </div>
</template>

Lightning Locker 注意事项

Lightning Locker 和 Lightning Web Security 不支持顶级 light DOM 组件。Light DOM 组件应始终嵌套在阴影 DOM 组件中的某个位置。

禁用 Locker(例如在 Experience Builder 站点中)意味着您将无法从 Locker 获得安全优势。确保您了解 Experience Builder 站点上更宽松的 CSP 环境的后果。请参阅 CSP 和 Lightning Locker 设计注意事项。

在基于 Aura 的 Experience Builder 站点中,Lightning Locker 会阻止 DOM 访问和其他 Web API。如果您在基于 Aura 的 Experience Cloud 站点中使用轻量级 DOM,请确保至少有一个 LWC 阴影 DOM 组件作为轻量级 DOM 组件的祖先。使用影子 DOM 组件意味着封装组件的内部标记,并受到影子 DOM 限制的约束。this.querySelector()

注意

从 23 年冬季开始,如果在组织中启用了 Lightning Web Security (LWS),则 Aura 站点中包含的任何 Lightning Web 组件都将受到 LWS 而不是 Lightning Locker 的保护。如果为站点禁用 Lightning Locker,则也会禁用 Lightning Web Security。

比较 Light DOM 和 Shadow DOM

由于其强大的封装性,shadow DOM 是创作组件的推荐方法。它隐藏了组件的内部结构,因此使用者只能使用其公共 API。

Shadow DOM 不适用于以下情况。

  • 构建高度可自定义的 UI,在其中完全控制 Web 应用的外观。
  • 使用第三方库。许多流行的库与影子 DOM 不兼容。

在这些情况下,Light DOM 更适合,但请注意,消费者可以像使用公共 API 一样访问组件的内部。允许此类访问使得在不影响使用者代码的情况下实现更改变得具有挑战性。

以下是使用一个而不是另一个的利弊。

 影子 DOM轻量级 DOM
安全强大的组件封装可保护组件免受未经授权的访问弱封装使组件容易受到未经授权的访问
可移植性高度可移植,通过公共 API 进行访问控制容易受到组件作者或使用者导致的重大更改的影响
造型需要 CSS 自定义属性来覆盖样式易于覆盖样式
第三方库和工具集成与需要 DOM 遍历或事件重定向的第三方库或工具的兼容性有限与第三方库和工具的简单集成

使用第三方库(例如 Google Analytics 或其他检测库)时,如果您的影子 DOM 组件公开了正确的 API,则不必使用轻量级 DOM。假设您要检测按钮上的点击交互:

<!-- myButton.html example -->
<template>
  <button>{label}</button>
</template>

使用 light DOM,您可以在元素上附加单击事件侦听器。如果在阴影 DOM 中渲染组件,则无法从组件外部访问该元素。使用影子 DOM,成为组件的内部实现细节。在这种情况下,检测此组件的正确方法是在其自身上添加一个单击处理程序来检测它。buttonbutton<button>my-buttonmy-button

<!-- myComponent.html example -->
<template>
  <my-button label="click me" onclick={handleClick}></my-button>
</template>

仅公开要检测的最低限度,因为公开内部事件会削弱组件的封装。虽然上述示例并非始终可行,但我们建议您在选择最适合您的用例的选项之前探索您的选项。

在组件中启用轻量级 DOM

要启用轻量级 DOM,首先在组件类中设置 static 属性:renderMode

import { LightningElement } from "lwc";

export default class LightDomApp extends LightningElement {
  static renderMode = "light"; // the default is 'shadow'
}

然后使用根模板指令,这是使用轻量级 DOM 的组件所必需的。lwc:render-mode

<template lwc:render-mode="light">
  <my-header>
    <p>Hello World</p>
  </my-header>
</template>

注意

实例化后更改静态属性的值不会影响组件是在轻量级 DOM 还是阴影 DOM 中呈现。renderMode

使用 Light DOM

将组件从影子 DOM 迁移到轻量级 DOM 需要一些代码更改。影子树会影响您使用 CSS、事件和 DOM 的方式。在使用轻量级 DOM 时,请考虑以下各节中描述的差异。

组成

您的应用可以包含使用阴影或浅色 DOM 的组件。在此示例模板中,使用影子 DOM 并包含多个组件:使用轻量级 DOM 和使用影子 DOM。my-appmy-headermy-footer

<my-app>
  #shadow-root 
  | <my-header>
  |   <p>Hello World</p>
  | </my-header>
  | <my-footer>
  |   #shadow-root 
  |   |  <p>Footer</p>
  | </my-footer>
</my-app>

轻量级 DOM 组件可以包含阴影 DOM 组件。类似地,影子 DOM 组件可以包含轻量级 DOM 组件。

提示

如果您有深度嵌套的组件,请考虑在顶层使用嵌套的轻量级 DOM 组件的单个阴影 DOM 组件。此结构允许您在一个影子根中的所有子组件之间自由共享样式。

可及性

与合成阴影或原生阴影不同,light DOM 不会将 ID 范围限定为单个组件。相反,它使组件能够引用单独组件上的 ID。这一优点使您能够使用 ID 和 ARIA 属性将两个元素放在同一个影子根中来链接它们。

请考虑此示例,其中包含两个同级组件。

<!-- container.html -->
<template>
  <c-label></c-label>
  <c-input></c-input>
</template>

该组件包含一个具有属性的元素。c-label<label>id

<!-- label.html -->
<template lwc:render-mode="light">
  <label id="my-label">My label</label>
</template>

该组件包含一个元素,该元素引用组件中的元素。c-input<input><label>c-label

<!-- input.html -->
<template lwc:render-mode="light">
  <input type="text" aria-labelledby="my-label" />
</template>

CSS的

使用影子 DOM 时,在父组件中定义的 CSS 样式不适用于子组件。相比之下,轻量级 DOM 允许从根文档设置样式以针对 DOM 节点并设置其样式。

以下本机阴影组件上的样式级联到子组件的浅色 DOM 中。在这种情况下,light DOM 组件位于原生阴影组件中,并挂载在最近的原生阴影根级别,该根级别在本地作用域为整个阴影根,并影响该根内的任何轻量级 DOM 组件。

<template>
  <my-app>
    #shadow-root
    |  <style> p { color: green; }</style>
    |  <p>This is a paragraph in shadow DOM</p>
    |    <my-container>
    |      <p>This is a paragraph in light DOM</p>
    |    </my-container>
  </my-app>
</template>

同样,在轻量级 DOM 中呈现的子组件上的样式将应用于其父组件,直到在使用本机阴影 DOM 时遇到阴影边界。

对于合成阴影 DOM,阴影 DOM 样式不会级联到轻量级 DOM 子组件中。

注意

在合成影子 DOM 中,样式是在全局文档级别实现的,但使用属性来限定样式的范围。这是合成影子 DOM 的当前限制。

LWC 不会自动为您确定样式范围。为了防止样式从组件中级联出来,我们建议对文件使用作用域样式。请参阅在 Light DOM 中使用作用域样式部分。*.scoped.css

要覆盖 Lightning Web 组件中继承的样式,请在组件样式表中创建 SLDS 样式挂钩。样式挂钩充当自定义样式的占位符。有关支持样式挂钩的组件蓝图列表,请参阅蓝图概述。

提示

轻量级 DOM 组件的渲染顺序会影响样式表注入根节点的顺序,并直接影响 CSS 规则的特异性。

访问元素

在影子 DOM 中,您只能访问组件拥有的元素。

相比之下,您可以从轻量级 DOM 组件中检索节点,这对第三方集成和测试很有帮助。例如,您可以使用 查询应用中的段落。document.querySelector('p')

<!-- JS code returns "Your Content Here" -->
<template>
  <script>
    console.log(document.querySelector("my-custom-class").textContent);
  </script>
  <my-component>
    <div class="my-custom-class">Your Content Here</div>
  </my-component>
</template>

使用 shadow DOM 时,返回与组件关联的影子根。该元素不适用于使用轻量级 DOM 的组件,因此使用轻量级 DOM 时,返回 .LightningElement.prototype.templatetemplateLightningElement.prototype.templatenull

将影子 DOM 组件迁移到轻型 DOM 时,请替换为 .以下示例使用常见 DOM API 列表来处理轻量级 DOM 组件。this.template.querySelectorthis.querySelector

import { LightningElement } from "lwc";

export default class LightDomApp extends LightningElement {
  static renderMode = "light";
  query(event) {
    const el = this.querySelector("p");
    const all = this.querySelectorAll("p");
    const elById = this.getElementById("#myId");
    const elements = this.getElementsByClassName("my-class");
    const tag = this.getElementsByTagName("button");
  }
}

使用轻量级 DOM 组件,可以返回其他轻量级 DOM 组件渲染的元素。this.querySelectorAll()

元素的 id 属性在运行时保留,不会像在合成影子 DOM 中那样作。因此,您可以在 CSS 或 JavaScript 中使用选择器,因为它与运行时的元素匹配。idid

注意

或者,在处理轻量级 DOM 和阴影 DOM 中的组件时,请使用 this.refs。 访问组件中定义的元素,并且在 light DOM 和 shadow DOM 中的行为类似,这与 or 不同。this.refsthis.querySelectorthis.querySelectorAll

事件

使用 shadow DOM 时,如果事件冒泡并越过影子边界,则某些属性值会更改以匹配侦听器的作用域。使用轻量级 DOM 时,事件不会被重定向。如果单击嵌套在多个轻量级 DOM 组件层中的按钮,则可以在文档级别访问该事件。此外,返回触发事件的按钮,而不是包含的组件。clickevent.target

例如,你有一个使用轻量级 DOM 的组件,该组件嵌套在一个同样使用轻量级 DOM 的容器组件中。顶级组件使用影子 DOM。c-light-childc-light-containerc-app

<!-- app.html (shadow DOM) -->
<template>
  <c-light-container onbuttonclick={handleButtonClick}> </c-light-container>
</template>
<!-- lightContainer.html -->
<template lwc:render-mode="light">
  <p>Hello, Light DOM Container</p>
  <!-- c-light-child host -->
  <c-light-child onbuttonclick={handleButtonClick}> </c-light-child>
</template>
// lightContainer.js
import { LightningElement } from "lwc";

export default class LightContainer extends LightningElement {
  static renderMode = "light";
  handleButtonClick(event) {
    // do something
  }
}
<!-- lightChild.html -->
<template lwc:render-mode="light">
  <button onclick={handleClick}></button>
</template>
// lightChild.js
import { LightningElement } from "lwc";

export default class LightChild extends LightningElement {
  static renderMode = "light";
  handleClick(event) {
    this.dispatchEvent(new CustomEvent("buttonclick", { bubbles: true, composed: false }));
  }
}

当您调度 中的自定义事件时,处理程序将返回以下元素。buttonclickc-light-child

c-light-child主机处理程序

  • event.currentTarget:c-light-child
  • event.target:c-light-child

c-light-container主机处理程序

  • event.currentTarget:c-light-container
  • event.target:c-light-child

相反,如果使用影子 DOM,则事件不会转义影子根。c-light-container

注意

即使在轻量级 DOM 中,事件也会通过组件冒泡,因为没有影子根。composedfalse

插槽

插槽是在轻量级 DOM 中模拟的,因为浏览器不支持 shadow DOM 之外的插槽。LWC 在运行时确定插槽是否在轻量级 DOM 上运行。

假设您有一个具有命名和未命名插槽的组件。my-component

<!-- myComponent.html -->
<template>
  <slot name="title"></slot>
  <h3>Subtitle</h3>
  <slot></slot>
</template>

像这样使用组件。

<my-component>
  <p>Default slotted content</p>
  <h1 slot="title">Component Title</h1>
</my-component>

这些元素不会呈现到 DOM。内容直接附加到 DOM 中的 host 元素。<slot>

<my-component>
  <h1>Component Title</h1>
  <h3>Subtitle</h3>
  <p>Default slotted content</p>
</my-component>

在运行时,插槽内容或回退内容将平展到父元素。元素本身不会呈现,因此向元素添加属性或事件侦听器会引发编译器错误。<slot><slot>

此外,请考虑这个包含阴影 DOM 组件和轻量级 DOM 组件的轻量级 DOM 组件。c-light-slot-consumerc-shadow-slot-containerc-light-slot-container

<!-- app.html -->
<template>
  <c-shadow-component></c-shadow-component>
  <c-light-slot-consumer></c-light-slot-consumer>
</template>
<!-- lightSlotConsumer.html -->
<template lwc:render-mode="light">
  <c-shadow-slot-container>
    <p>Hello from shadow slot</p>
  </c-shadow-slot-container>
  <c-light-slot-container>
    <p>Hello from light slot</p>
  </c-light-slot-container>
</template>
<!-- shadowSlotContainer.html -->
<template>
  <slot></slot>
</template>
<!-- lightSlotContainer.html -->
<template lwc:render-mode="light">
  <slot name="other">
    <p>Hello from other slot</p>
  </slot>
  <slot>This is the default slot</slot>
</template>

如果在 中包含样式,则插槽中的所有元素(在阴影 DOM 和浅色 DOM 组件中)都会获得样式。但是,没有插槽的影子 DOM 组件不会接收样式。c-app

<c-app>
  <style type="text/css">
    p {
      background: green;
      color: white;
    }
  </style>    
  <h2>Hello Light DOM</h2>
    <p>This is a paragraph in app.html</p>
    <h3>Shadow DOM</h3>
    <c-shadow-component>
        #shadow-root (open)
        | <p>Hello, Shadow DOM container</p>
    </c-shadow-component>
    <h3>Slots</h3>
    <c-light-slot-consumer>
        <c-shadow-slot-container>
            #shadow-root (open)
            | <p>Hello from shadow-slot-container</p>
        </c-shadow-slot-container>
        <c-light-slot-container>
            <p>Hello from other slot</p>
            <p>Hello from light-slot-container</p>
        </c-light-slot-container>
    </c-light-slot-consumer>
</c-app>

考虑使用插槽的这些组合模型。轻量级 DOM 中的组件可以插入内容和其他组件。插槽支持轻量级 DOM 和阴影 DOM 组件。

<template>
  <slot name="content">Default content in the named slot</slot>
  <p>This makes the component a bit more complex</p>
  <slot>This is a default slot to test if content bypasses the named slot and goes here</slot>
</template>

以下是您的内容在插槽中的呈现方式。

<my-component>
  <!-- Inserted into the content slot -->
  <div slot="content">Some text here</div>
</my-component>

<my-component>
  <!-- Inserted into the content slot -->
  <my-shadow-lwc slot="content">Some text here</my-shadow-lwc>
</my-component>

<my-component>
  <!-- Inserted into the content slot -->
  <my-light-lwc slot="content">Some text here</my-light-lwc>
</my-component>

<my-component>
  <!-- Inserted into the default slot -->
  <my-shadow-lwc>Some text here</my-shadow-lwc>
</my-component>

<my-component>
  <!-- Inserted into the default slot -->
  <my-light-lwc>Some text here</my-light-lwc>
</my-component>

注意

不支持事件和 CSS 伪选择器,因为 slot 元素不会在 DOM 中呈现。slotchange::slotted

Light DOM 不会渲染未分配给插槽的插槽元素,因此永远不会调用它们的生命周期钩子。

<!-- c-parent -->
<template>
  <c-child>
    <span>This element is not rendered in light DOM</span>
  </c-child>
</template>

<!-- c-child -->
<template>
  <p>This component does not include a slot</p>
</template>

作用域插槽

使用作用域槽,您可以访问子组件中的数据,并在父组件内的槽内容中呈现数据。通过将数据从子组件绑定到作用域内插槽,父组件可以在插槽内容中引用子组件的数据。此数据入子组件的轻量级 DOM 中。

在此示例中,子组件将其数据绑定到作用域槽。在父组件中,作用域内的槽片段引用以及将槽入的标记。<c-child>item<slot><c-parent>{item.id}{item.name}<c-child>

<!-- c/parent.html -->
<template> <!-- Parent component doesn’t need to be light DOM -->
    <c-child>
        <template lwc:slot-data="item">
            <span>{item.id} - {item.name}</span>
        </template>
    </c-child>
</template>


<!-- c/child.html -->
<template lwc:render-mode="light"> <!-- Child must be light DOM -->
    <ul>
        <template for:each={item} for:item="item">
            <li key={item.id}>
                <slot lwc:slot-bind={item}</slot>
            </li>
        </template>
    </ul>
</template>

由于作用域内的槽片段位于父组件的模板中,因此父组件拥有槽位内容。因此,如果父组件引用作用域样式表,则这些样式也适用于作用域内插槽的内容。

父组件部分呈现作用域槽的内容,因此必须将其包含在标记中。在此示例中,作用域内的槽内容为 。父组件创建此部分片段。<template></template><span>{item.id} - {item.name}</span>

父组件也渲染每个 ,子组件控制循环逻辑。 使用从 传递的相同模板片段创建任意数量的插槽。item<c-child><c-parent>

注意

若要使用作用域插槽,子组件必须使用轻量级 DOM。 不支持影子 DOM 中的作用域插槽。父级可以是轻量级 DOM 或影子 DOM 组件。

最终的 HTML 如下所示。

<c-parent>
  #shadow-root
  | <c-child>
  |  <ul>
  |    <li>
  |      <span>1 - One</span>
  |    </li>
  |    <li>
  |      <span>2 - Two</span>
  |    </li>
  |  </ul>
  | </c-child>
</c-parent>

要在组件中引入作用域槽,请添加指令和 .有关详细信息,请参阅槽的指令和嵌套模板的指令。lwc:slot-bindlwc:slot-data

多个作用域插槽和绑定

子组件可以有多个命名的作用域插槽,但它只能有一个默认的作用域插槽。

<template>
    <c-child>
        <template lwc:slot-data="defaultdata"> <!-- This is a default slot -->
            <p>{defaultdata.title}</p>
        </template>
        <template slot="slotname1" lwc:slot-data="slot1data"> <!-- This is a named slot -->
            <p>{slot1data.title}</p>
        </template>
        <template slot="slotname2" lwc:slot-data="slot2data"> <!-- This is a named slot -->
            <p>{slot2data.title}</p>
        </template>
    </c-child>
</template

您可以将不同的作用域槽绑定到同一数据源。在下面的示例中,默认的作用域内插槽和两个命名的作用域内插槽呈现来自 的内容。slotdata

<template lwc:render-mode="light">
  <!-- This is a default slot -->
  <slot lwc:slot-bind={slotdata}></slot>
  <!-- This is a named slot -->
  <slot name="slotname1" lwc:slot-bind={slotdata}></slot>
  <!-- This is a named slot -->
  <slot name="slotname2" lwc:slot-bind={slotdata}></slot>
</template>

只能将作用域槽绑定到一个数据源。例如,将命名的作用域槽绑定到两组不同的数据,并且 会导致编译器错误。namedslotAslot1dataslot2data

<!-- Invalid usage of named scoped slot -->
<template lwc:render-mode="light">
  <slot name="namedslotA" lwc:slot-bind={slot1data}></slot>
  <slot name="namedslotA" lwc:slot-bind={slot2data}></slot>
</template>

如果尝试将默认作用域槽绑定到多个不同的数据集,编译器会引发相同的错误。

<!-- Invalid usage of default scoped slot -->
<template lwc:render-mode="light">
  <slot lwc:slot-bind={slot1data}></slot>
  <slot lwc:slot-bind={slot2data}></slot>
</template>

混合标准插槽和分区插槽

由于组件中只能有一个默认插槽,因此不能在同一组件中放置标准默认插槽和默认作用域插槽。以下代码将导致错误。

<!-- c/child.html -->
<!-- Invalid usage of default slots -->
<template lwc:render-mode="light">
  <slot lwc:slot-bind={slotdata}>Default scoped slot</slot>
  <slot>Standard default slot</slot>
</template>

在子组件中,命名范围的插槽和标准命名插槽不能共享相同的名称。以下代码将导致错误。

<!-- c/child.html -->
<!-- Invalid usage of named slots -->
<template lwc:render-mode="light">
  <slot name="slotname1" lwc:slot-bind={slotdata}>Named scoped slot</slot>
  <slot name="slotname1">Standard named slot</slot>
</template>

将父组件中的作用域槽绑定到子组件中的数据时,组件必须包含相同类型的槽。例如,如果父组件包含绑定到子组件的作用域槽,则该子组件也必须具有作用域槽。否则,不会呈现槽内容。如果启用调试模式,则还会在开发控制台中记录错误。

作用域插槽的灵活性

您可以将一个作用域插槽嵌套在另一个作用域插槽中。

<template>
  <c-table data={data}>
    <template lwc:slot-data="row">
      <c-row row={row}> <!-- This is rendered for every row in the table -->
        <template lwc:slot-data="column">
          <span> <!-- This is rendered for every column in the row -->
            Coordinates: {row.number} - {column.number} <!-- This can refer to both `row` and `column` -->
          </span>
        </template>
      </c-row>
    <template>
  </c-table>
</template>

Scoped slots can reference component bindings and scope bindings.

<template>
  {title}
  <c-list>
    <template lwc:slot-data="item">
      <div>{label}</div>
      <!-- label is a component binding that’s repeated in every row of the list -->
      <span>{item.id} - {item.name}</span>
    </template>
  </c-list>
</template>

在 Light DOM 中使用作用域样式

在轻量级 DOM 中,您可以使用作用域样式仅将 CSS 应用于组件上的元素。此行为类似于使用影子 DOM 进行样式封装。

若要向组件添加作用域样式,请在组件文件夹中创建一个文件。*.scoped.css

myCmp
    ├──myCmp.html
    ├──myCmp.css
    └──myCmp.scoped.css

在上面的示例中,对于阴影 DOM 组件和轻量级 DOM 组件,您可以包含一个、两个或两个 CSS 文件。

注意

在基于 Aura 的容器中,轻量级 DOM 组件只能加载作用域样式。例如,在基于 Aura 的 Experience Builder 站点中,您必须包含自定义组件的文件,而不是文件。*.scoped.css*.css

让我们检查一个具有作用域样式的轻量级 DOM 组件。

<!-- lightCmp.html -->
<template lwc:render-mode="light">
  <p>This is a paragraph in c-light-cmp</p>
</template>
// lightCmp.js
import { LightningElement } from "lwc";

export default class LightCmp extends LightningElement {
  static renderMode = "light";
}
/* lightCmp.scoped.css */
p {
  background: silver;
  color: black;
}

作用域样式的结果为:

<c-light-cmp class="c-lightCmp_lightCmp-host">
  <style class="c-lightCmp_lightCmp" type="text/css">
    p.c-lightCmp_lightCmp {
      background-color: silver;
      color: black;
    }
  </style>
  <p class="c-lightCmp_lightCmp">This is a paragraph in c-light-cmp</p>
</c-light-cmp>

如果文件与文件一起使用,则样式表将在样式表之前注入。作用域样式 CSS 选择器优先于无作用域选择器,因为它们是最后声明的。*.css*.scoped.css*.css*.scoped.css

注意

如果在模板上使用作用域样式表和无作用域样式表,则同时应用这两种样式表。注入顺序会影响浏览器在重复选择器时应用哪些样式。见树接近无知。

/* lightCmp.css */
p {
  background: yellow;
  color: #777;
}

在这种情况下,使用作用域样式,但无作用域样式表中的样式可能会从组件中渗出。c-light-cmp

<!-- c-app -->
<template>
  <!-- This paragraph is styled yellow from lightCmp.css -->
  <p>This is a paragraph in c-app shadow DOM</p>
  <c-light-cmp></c-light-cmp>
</template>

在前面的示例中,段落继承自 的样式。要覆盖 中的样式,请包含作用域样式表。c-applightCmp.csslightCmp.cssapp.scoped.css

注意

我们不建议使用该规则,因为它会使调试更加困难。当您在作用域内样式表和无作用域样式表中使用样式声明时,作用域样式具有更大的特异性,并且其样式将应用于组件。!important!important

作用域区域

使用 时,CSS 选择器的范围限定为组件 HTML 文件中的所有元素。*.scoped.css

<!-- c-my-cmp -->
<template>
  <div>
    <span></span>
    <button class="my-button"></button>
  </div>
</template>

在下面的 CSS 中,所有选择器都与模板中的元素匹配,并且只匹配这些元素。

/* myCmp.css */
div {
}
span {
}
button {
}
div > span {
}
div button {
}
.my-button {
}

根元素可以使用 作为目标,即使在轻量级 DOM 组件中也是如此。c-light-cmp:host

CSS 范围使用自定义 CSS 类来防止样式从组件中泄漏出来。

以根元素为目标

由于默认情况下不限定轻量级 DOM 样式的范围,因此伪选择器引用最接近的影子根宿主元素(如果有)。对于轻量级 DOM 范围样式,选择器引用轻量级 DOM 组件的根元素,即作用域 DOM 区域的根。:host:host

假设您在一个轻量级 DOM 组件上有一个无作用域和作用域的样式表。

/* light.css */
:host {
  background: red;
}
/* light.scoped.css */
:host {
  background: blue;
}

该组件呈现:

<c-shadow>
  <!-- red background -->
  #shadow-root
  <c-light class="c-light_light-host">
    <!-- blue background -->
    <style>
      :host {
        background: red;
      }
      :c-light_light-host {
        background: blue;
      }
    </style>
  </c-light>
</c-shadow>

在前面的示例中,为作用域样式进行转换。:host

注意

不支持选择器。:host-context()

将作用域样式与合成影子 DOM 进行比较

Light DOM 范围样式与 LWC 的合成阴影范围样式有一些不同。

  • 轻量级 DOM 范围样式使用类来确定范围,但合成阴影 DOM 使用 HTML 属性。
  • 轻量级 DOM 作用域样式在作用域样式表中不受支持。@import
  • 轻量级 DOM 范围样式不适用于在 lwc:dom=“manual” 中手动注入模板的内容,例如,使用 或 注入的内容。Element.appendChildElement.innerHTML

在混合阴影模式下构建组件(开发人员预览版)

混合阴影模式使组件能够使用本机阴影 DOM,即使应用了合成阴影 polyfill。

注意

混合阴影模式作为开发人员预览版提供。除非 Salesforce 在文档、新闻稿或公开声明中宣布其正式发布,否则此功能不会正式发布。所有命令、参数和其他功能都可能随时更改或弃用,恕不另行通知。不要实现使用这些命令或工具开发的功能。

现在所有主流浏览器都支持影子 DOM。Salesforce 维护旧版浏览器(如旧版本的 Microsoft Edge)的合成影子 polyfill。

为了简化开发和测试,目前甚至在支持影子 DOM 的浏览器上使用 polyfill。使用混合阴影模式,您可以获得在应用中尽可能多地使用原生阴影的速度和效率。而且,将来可以更轻松地迁移以完全使用本机影子。

原生阴影和合成阴影有几个区别,例如它们如何通过插槽与样式实现封装。虽然应用依赖于全局样式表来应用合成影子 DOM 中的样式,但组件的样式会添加到原生影子中的组件包中。此外,合成阴影不支持某些影子 DOM 功能,例如 。最重要的是,原生阴影组件比合成阴影组件更快、性能更高。::part

下面是具有混合模式组件的应用示例。它有一个父组件和子组件。当您在 Web 控制台中检查本机影子组件时,它们会显示在标签中。合成阴影根在开发人员工具中不可见,但以下示例中显示了它们,以便进行说明。c-appc-nativec-synthetic#shadow-root

<!-- c-app -->
<c-app>
    <c-native>
     #shadow-root
     |  <div>Your content for the native component</div>
    </c-native>
    <c-synthetic>
        <div>Your content for the synthetic component</div>
    </c-synthetic>
</template>

在本文中,父组件和子组件也称为祖先组件和后代组件。相反,扩展另一个组件的组件称为子类及其超类。

注意

LWC 允许遗传,但不建议这样做,因为组合通常更有效。继承不能跨命名空间工作,并且无法扩展命名空间。lightning

合成组件可以包含本机组件,但不支持反之组件。

<!-- c-app -->
<c-app>
  #shadow-root (synthetic)
  <c-synthetic>
    #shadow-root (synthetic)
    <c-native>
      #shadow-root (native)
      <p>Content inside a native shadow root</p>
    </c-native>
  </c-synthetic>
</c-app>

启用混合阴影模式

注意

默认情况下,混合影子模式在您的组织中不可用。联系 Salesforce 以参与开发人员预览版。

要在组件上启用混合阴影模式,请将 static 属性设置为 。shadowSupportModeany

// native.js
import { LightningElement } from "lwc";
export default class MixedModeApp extends LightningElement {
  static shadowSupportMode = "any";
}

的有效值包括:shadowSupportMode

  • any– 尽可能在本机阴影 DOM 中渲染整个组件子树。如果浏览器不支持影子 DOM,则子树将以合成影子形式呈现。
  • reset– 使子类能够选择不接收来自其超类的值。 仅当组件的超类正在使用且没有父组件正在使用 时才适用。shadowSupportModeresetanyany

在设置为启用混合影子模式之前,我们建议检查子树中的所有组件是否都与本机影子 DOM 兼容。例如,不能在本机影子 DOM 子树中使用属性选择器,因为该选择器仅在合成影子 DOM 中起作用。相反,请查看您的浏览器是否支持本机影子 DOM 中的伪类。 或者,您可以从叶组件开始,然后沿着组件树向上移动,以确保混合阴影模式按预期工作。shadowSupportModeany[dir=""]:dir()

使用混合阴影模式

如果父组件使用 ,则子树中的所有组件都在本机阴影中运行,而不管其值如何。本机影子模式组件只能包含本机影子模式子组件。此限制是将属性应用于整个子树的副作用。anyshadowSupportModeshadowSupportMode

默认情况下,插槽内容以本机阴影呈现。开槽内容不是从其嵌套的组件派生的,因此您可以将合成阴影内容插入到本机阴影组件中。这也意味着,插槽内容不受浏览器呈现包含插槽的组件的方式的影响。例如,如果您的浏览器在合成阴影中渲染本机组件,则插入该组件的本机内容仍会在本机阴影中渲染。#shadow-root

下面,有一个带有子组件的原生影子子树,以及开槽的内容。 在原生影子中运行,因为它是 的后代,但可以在合成影子中,因为它不是 的后代。<c-parent><c-child><c-slotted><c-child><c-parent><c-slotted><c-parent>

<!-- c-app -->
<c-app>
  #shadow-root (synthetic)
  <c-parent>
    #shadow-root (native)
    <p>Parent component</p>
    <slot>
      <c-slotted>
        #shadow-root (synthetic)
        <p>Slotted content</p>
    </slot>
    <c-child>
      #shadow-root (native)
      <p>Child component</p>
    </c-child>
  </c-parent>
</c-app>

在支持影子 DOM 的浏览器上,以下是当您包含 polyfill 并设置为 or 时父组件和子组件的呈现方式。@lwc/synthetic-shadowshadowSupportModeanyreset

<!-- c-parent -->
<template>
  <c-child></c-child>
</template>
父母孩子阴影模式
合成
重置重置合成
任何任何本地
任何重置本地
重置任何父级:合成,子级:原生
任何父级:合成,子级:原生
重置合成

包含 polyfill 时,通过将 设置为 将阴影模式重置为默认行为。@lwc/synthetic-shadowshadowSupportModereset

  • 如果存在 polyfill,则将阴影模式设置为合成阴影。reset
  • 如果 polyfill 不存在,则不会产生任何影响,并且组件会在本机阴影中呈现。shadowSupportMode

要确定元素是否具有合成影子根,请使用 .this.template.synthetic

export default class extends LightningElement {
  renderedCallback() {
    console.log("Synthetic?", !!this.template.synthetic);
  }
}

示例:使用原生和合成阴影组件

应用可以包含本机和合成阴影组件。此示例假定应用正在使用 polyfill。@lwc/synthetic-shadow

<!-- c-app -->
<c-app>
  <c-native>
    #shadow-root 
    |  <div>Your content for the native component</div>
  </c-native>
  <c-synthetic>
    <div>Your content for the synthetic component</div>
  </c-synthetic>
</c-app>

该应用程序包括两个以不同模式运行的组件。

<!-- c-app -->
<template>
  <c-native></c-native>
  <c-synthetic></c-synthetic>
</template>

这是本机组件。

<!-- c-native -->
<template>
  <div>Your content for the native component</div>
</template>
// native.js
import { LightningElement } from "lwc";
export default class Native extends LightningElement {
  static shadowSupportMode = "any";
}

这是合成组件。

<!-- c-synthetic -->
<template>
  <div>Your content for the synthetic component</div>
</template>
// synthetic.js
import { LightningElement } from "lwc";
export default class Synthetic extends LightningElement {}

原生阴影与合成阴影的比较

在这里,我们讨论原生阴影和合成阴影之间的一些区别。

元素和插槽

在原生阴影 DOM 中,元素在组件的轻量级 DOM 中呈现,并分配给子组件的阴影 DOM 中的插槽。但在合成阴影 DOM 中,LWC 不会渲染传递给子组件的元素,除非它们被分配给插槽。

对于包含 的组件,其跨度未分配给槽。在原生阴影中,此跨度在 DOM 中呈现。但是,在合成阴影中,DOM 中不存在跨度。c-parentc-childc-child

<!-- c-parent -->
<template>
  <c-child>
    <span>This is some text in a span tag</span>
  </c-child>
</template>
<!-- c-child -->
<template>
  <p>child</p>
</template>

若要确定元素分配到哪个槽,请调用 API。以前,当加载合成阴影 polyfill 时,分配给本机阴影组件中插槽的元素会返回 assignedSlot。现在,API 返回本机阴影组件中开槽元素的元素。assignedSlotnull<slot>

若要返回指定坐标处所有元素的数组,请使用 elementsFromPoint API,包括用于 DOM 中不可见的合成阴影 DOM 元素。在以下示例中,是不可见的(宽度和高度为零),但它包含的 是可见的。<c-inner><div>

<c-outer>
  #shadow-root
  <c-inner>
    #shadow-root
    <div></div>
  </c-inner>
</c-outer>

在合成影子中,调用返回 。在原生阴影中,它返回outer.shadowRoot.elementsFromPoint()[<div>, <c-outer>, <html>][<c-inner>, <c-outer>, <html>]

造型

使用合成阴影,应用程序依靠全局样式表在整个 DOM 中应用样式。在原生阴影中,这是不可能的,因为在原生阴影中,组件的样式必须添加到组件包中。

但是,使用合成阴影时,文档顶层的共享样式表可以设置页面上所有组件的样式。若要防止将顶层样式应用于子组件,请使用 @import 将共享样式表包含在需要共享样式表的组件中。

注意

在多个组件中导入相同的样式表不会影响性能,因为 LWC 会为您处理 CSS 的重复数据删除。

虽然 CSS 模块导入适用于大多数共享的 CSS 库,但如果 CSS 包含遍历阴影边界的选择器,则它不起作用。假设你有一个带有这个 CSS 的父组件和子组件。

.parent .child {
  color: red;
}

如果 和 选择器位于单独的组件中,则此 CSS 不起作用。在这种情况下,请使用 CSS 自定义属性或其他技术根据子项的父级有条件地呈现子项。.parent.child

例如。您可以将该属性传递给子组件。color

class Parent extends LightningElement {
  myColor = "red";
}

class Child extends LightningElement {
  @api color;
  get myStyle() {
    return `color: ${this.color}`;
  }
}

子组件中显示的颜色由父组件确定。

<!-- c-parent -->
<template>
  <c-child color={myColor}></c-child>
</template>

<!-- c-child -->
<template>
  <h1 style={myStyle}>My color is determined by my parent</h1>
</template>

或者,如果不需要对组件进行封装,请考虑迁移组件以改用轻量级 DOM。

可及性

使用合成阴影,您可以通过在父组件和子组件中动态设置属性来创建跨阴影边界的引用。使用原生阴影时,您不能执行相同的操作,因为元素 ID 的范围限定为特定组件。

我们推荐以下选项。

  • 如果一个元素引用了另一个元素的 ID,请将两者放在同一个组件中。
  • 如果使用 ,则使用 在元素之间复制字符串。aria-labelledbyaria-label

或者,如果您使用的是不需要封装的组件,请考虑使用轻量级 DOM。例如,在同一个影子 DOM 父组件中,有两个轻量级 DOM 组件作为同级组件,并且 ID 在同级组件之间共享。

生命周期钩子

使用合成阴影时,如果仅将槽元素分配给槽,则会渲染这些元素。对于从未分配给槽的槽元素,从不调用其生命周期挂钩。

此外,合成阴影中的生命周期钩子在分配后按出现顺序调用。相比之下,在原生阴影中,它们按模板中的出现顺序调用。

非组合事件

使用合成影子,如果事件源自子树中的非 LWC 组件,侦听器可以处理根 LWC 节点之外的非组合事件。但是,影子 DOM 不支持此行为。

基本 Lightning 组件

混合阴影模式目前不支持基本组件。Salesforce 正在按照 Web 组件标准为本机影子 DOM 准备基本组件。随着我们努力实现未来对混合影子模式和原生影子 DOM 的支持,基本组件的内部结构会不断变化。 目前,当基本组件放置在本机阴影组件中时,可能无法显示正确的样式;原生阴影渲染所有子组件,而不渲染合成阴影。

其他注意事项

我们的基准测试表明,在某些情况下,原生阴影组件比合成组件快 50%。删除合成阴影 polyfill 也会将 LWC 的整体 JavaScript 大小减少一半。混合阴影模式使我们更接近于能够完全迁移到原生阴影,然后使我们能够从 LWC 中完全删除 polyfill。

合成阴影不支持某些影子 DOM 功能,例如 .::part

请考虑原生阴影和合成阴影之间的这些额外差异。

合成阴影原生阴影
插槽是按照目标可插槽组件中定义的顺序创建的。插槽是按照调用插槽的组件中定义的顺序创建的。
该属性不会隐藏阴影内容。innerText该属性隐藏阴影内容。innerText
this.template.firstChild返回模板中的第一个元素。this.template.firstChild在某些浏览器中返回本机阴影中的元素。<style>

JavaScript

每个组件都必须有一个 JavaScript 文件。Lightning Web 组件中的 JavaScript 文件是 ES6 模块。

共享 JavaScript 代码

若要在组件之间共享代码,请在服务组件中创建一个 ES6 模块,并使用标准 JavaScript 语法导出要共享的变量或函数。

ES6 模块是一个 JavaScript 文件,它显式导出其他模块可以使用的变量或函数。模块使构建代码变得更加容易。

LWC 有两种用于共享代码的模式:

  • 创建 JavaScript 文件,这些文件将代码导出到与导入代码的组件相同的文件夹中。使用相对路径导入代码。其他组件无法直接导入此文件。此方法支持在组件中构建代码,而不是与其他组件共享代码。
  • 创建一个服务组件(库),该组件是一个组件文件夹,其中包含一个或多个用于导出代码的 JavaScript 文件。为了导入代码,其他组件使用语法。组件只能从主 JavaScript 文件导入代码,该文件与文件夹名称同名。若要共享库中补充 JavaScript 文件中的代码,请从这些文件中导出代码,然后从主 JavaScript 文件中重新导出。请参阅如何在补充 JavaScript 文件中访问导出部分。c/componentName

此示例使用第一种模式。只能从 和 导入代码。myComponent.jsutils.jsmyFunction.js

lwc
  └───myComponent
    ├──myComponent.html
    ├──myComponent.js
    ├──myComponent.js-meta.xml
    ├──myFunction.js
    └──utils.js

若要导入代码,请使用相对路径。

// myComponent.js
import { getAmount, calculateCost } from "./utils";
import myFunction from "./myFunction";

提示

JavaScript 文件可以导出命名导出或默认导出。要导入命名导出,代码将使用 中的特定名称。若要导入默认导出,代码可以使用任何名称。在前面的示例中,使用命名导出并使用默认导出。{}utils.jsmyFunction.js

此示例是一个服务组件(库)。文件夹和一个 JavaScript 文件必须具有相同的名称。在此示例中,名称为 。mortgageUtils

lwc
  ├──mortgageUtils
    ├──mortgageUtils.js
    └──mortgageUtils.js-meta.xml
  └───myComponent
    ├──myComponent.html
    ├──myComponent.js
    └──myComponent.js-meta.xml

若要将代码导入到其他组件中,请使用语法。c/componentName

// myComponent.js
import { getTermOptions, calculateMonthlyPayment } from "c/mortgageUtils";

重要

在语句中,指定要从中导入的文件夹,而不是文件,不要指定文件扩展名。其他组件只能从库的主 JavaScript 文件导入代码,该文件与文件夹同名。组件无法从具有其他名称的补充 JavaScript 文件或嵌套文件夹中的文件导入。若要共享此类文件中的代码,请导出其函数或变量,然后从主 JavaScript 文件中再次导出它们。import

导出默认函数和变量

ES6 模块可以导出单个默认函数或变量。

lwc
  └──myComponent
    ├──myComponent.html
    ├──myComponent.js
    ├──myComponent.js-meta.xml
    └──myFunction.js
// myFunction.js
export default function () { ··· }

此语法也有效。export

// myFunction.js
export { myFunction as default, … };

若要引用默认导出,导入函数的组件必须在语句中选择一个名称。它不一定是导出的函数或 JavaScript 文件的名称,这只是一个约定。import

// myComponent.js
import myFunction from "./myFunction";

导出命名函数和变量

一个 ES6 模块可以导出多个命名函数或变量。

// mortgage.js
const getTermOptions = () => {
  return [
    { label: "20 years", value: 20 },
    { label: "25 years", value: 25 },
  ];
};

const calculateMonthlyPayment = (principal, years, rate) => {
  // Your calculation logic here
};

export { getTermOptions, calculateMonthlyPayment };

导入函数的组件使用导出的名称。

// myComponent.js
import { getTermOptions, calculateMonthlyPayment } from "c/mortgage";
lwc
  ├──mortgage
    ├──mortgage.js
    └──mortgage.js-meta.xml
  └──myComponent
    ├──myComponent.html
    ├──myComponent.js
    └──myComponent.js-meta.xml

提示

请参阅 lwc-recipes 示例存储库。mortgagemiscSharedJavaScript

LWC 编译器如何解析组件入口点

LWC 模块仅将其一个文件公开为入口点。入口点可以是其 JavaScript 文件或 CSS 文件。LWC 编译器按以下顺序解析模块的入口点。c/moduleNamemoduleName

  1. 如果存在,则为入口点。moduleName.js
  2. 否则,是入口点。moduleName.css

如果两个文件都未找到,则编译失败。

在此示例中,是模块的入口点:utils.jsc/utils

utils
   ├──utils.js
   └──other.js

将函数从 resolves 导入到 。c/utilsutils.js

// someComponent.js
// This import is valid
import { getSomething } from "c/utils";

所有这些导入都失败:

// someComponent.js
// These imports are invalid
import { getSomething } from "c/utils/utils.js";
import { getSomething } from "c/utils/other.js";
import { getSomething } from "c/utils/utils";

如何访问补充 JavaScript 文件中的导出

补充 JavaScript 文件使用的文件名与组件或模块名称不同。若要从补充 JavaScript 文件访问导出,请使用语法将其导入并在主 JavaScript 文件中重新导出。此语法是 和 的组合。export fromimportexport

例如,假设您要从补充文件导出代码。moreUtils.js

lwc
  └───utils
    ├──utils.js
    └──moreUtils.js

在 中,导出代码。moreUtils.js

// moreUtils.js
const someFunction = () => {
    // logic
};

const someOtherFunction = (arg1 arg2) => {
    // logic
};

export{ someFunction, someOtherFunction };

在 中,此语句导出函数并使其可用于其他 Lightning Web 组件。这些函数不可用,除非您还像本页的第一个示例一样导入它们。utils.jsutils.js

// utils.js
export { someFunction, someOtherFunction } from "./moreUtils";

您还可以使用通配符从补充模块导出所有资源。

// utils.js
export * from "./moreUtils";

在组件中,导入函数,就像它们位于 中一样。utils.js

// someComponent.js
import { someFunction, someOtherFunction } from "c/utils";

使用第三方 JavaScript 库

提示

在使用第三方 JavaScript 库之前,我们建议您在 AppExchange 中查找符合您要求的第三方应用程序。或者,检查基本组件是否提供所需的功能。

您可以将第三方 JavaScript 库与 Lightning Web 组件一起使用。例如,使用具有交互式图表和图形的库或降低代码复杂性的库。

  1. 从第三方库的站点下载库。
  2. 将库作为静态资源上传到您的 Salesforce 组织,这是 Lightning Web 组件内容安全策略的要求。
  3. 在扩展的 JavaScript 类中:LightningElement
    • 按库的静态资源名称导入库。import RESOURCE_NAME from "@salesforce/resourceUrl/RESOURCE_NAME";例如,如果将静态资源命名为 :myLibimport myLib from "@salesforce/resourceUrl/myLib";
    • 从模块导入方法。platformResourceLoaderimport { loadStyle, loadScript } from "lightning/platformResourceLoader";请参阅 lightning/platformResourceLoader 参考文档。
  4. 加载库并在方法中调用其函数。then()loadScript(this, myLib + "/myLib.js").then(() => { let result = myLib.myFunction(2, 2); });

重要

如果您的组件在未启用 Lightning Web Security (LWS) 的组织中运行,则您的组件使用的库必须满足 Lightning Locker 要求。请参阅确定 JavaScript 库是否符合 Locker 标准。如果组织使用的是 Lightning Web Security (LWS),则大多数第三方库无需更改即可按预期工作。但是,某些库需要更改才能与 LWS 一起使用。请参阅 LWS 的第三方库注意事项。

使用 JavaScript 操作 DOM

不建议使用 JavaScript 来操作 DOM,因为 Lightning Web 组件引擎可以更高效地执行此操作。但是,有一些第三方 JavaScript 库接管了 DOM。

注意

Salesforce 不提供对第三方 JavaScript 库的支持。演示如何使用第三方 JavaScript 库的文档和示例并不构成对第三方 JavaScript 库的认可。我们建议您查看第三方 JavaScript 库文档,了解使用信息。

如果调用操作 DOM,则样式不会应用于追加的元素。appendChild()

在 Lightning Web 组件中使用这些库时,请添加到要使用 JavaScript 操作的任何 HTML 元素中。当引擎看到该指令时,它会保留封装。lwc:dom="manual"

将指令添加到空的本机 HTML 元素中。组件的所有者调用该元素来手动插入 DOM。lwc:dom="manual"appendChild()

<template>
  <div lwc:dom="manual"></div>
</template>

提示

D3 代码是 lwc-recipes 存储库中的组件。libsD3

示例:D3 JavaScript 库

此组件使用 D3 JavaScript 库创建交互式数据可视化效果。

显示由线连接的彩色点的图形的组件。您可以单击并拖动点来更改图形的形状。

在 d3js.com 下载 D3。作为静态资源上传到您的 Salesforce 组织。d3.zip

首先,创建一个组件来包含地图。在这里,容器是一个空的。lwc:dom=“manual” 指令告诉 LWC 元素中的 DOM 已手动插入。<svg><svg>

<!-- libsD3.html -->
<template>
  <div class="slds-m-around_medium">
    <svg class="d3" width={svgWidth} height={svgHeight} lwc:dom="manual"></svg>
  </div>
</template>

在组件的 JavaScript 类中,import 和 from .同时导入静态资源。请注意,这是用于加载资源的静态资源引用,并且是上传到 Salesforce 的静态资源的名称。loadStyleloadScriptlightning/platformResourceLoaderd3D3d3

若要创建图形,请在第一次渲染时调用 和 in。使用可确保页面在创建图形之前加载并呈现容器。loadStyleloadScriptrenderedCallback()renderedCallback()

调用和返回 promise。用于聚合结果,并确保在调用回调之前解析这两个文件。只有在加载完成后,并且只有在没有发生错误时才会调用回调。您可以选择提供回调来处理加载过程中发生的任何潜在错误。loadStyleloadScriptPromise.all()then()catch()

要在 promise 回调中初始化图形,请调用 ,它进入 DOM 并获取对显示图形的容器的引用,这里是一个元素。initializeD3()<svg>

// libsD3.js
/* global d3 */
import { LightningElement } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import { loadScript, loadStyle } from "lightning/platformResourceLoader";
import D3 from "@salesforce/resourceUrl/d3";
import DATA from "./data";

export default class LibsD3 extends LightningElement {
  svgWidth = 400;
  svgHeight = 400;

  d3Initialized = false;

  renderedCallback() {
    if (this.d3Initialized) {
      return;
    }
    this.d3Initialized = true;

    Promise.all([loadScript(this, D3 + "/d3.v5.min.js"), loadStyle(this, D3 + "/style.css")])
      .then(() => {
        this.initializeD3();
      })
      .catch((error) => {
        this.dispatchEvent(
          new ShowToastEvent({
            title: "Error loading D3",
            message: error.message,
            variant: "error",
          }),
        );
      });
  }

  initializeD3() {
    // Example adopted from https://bl.ocks.org/mbostock/2675ff61ea5e063ede2b5d63c08020c7
    const svg = d3.select(this.template.querySelector("svg.d3"));
    const width = this.svgWidth;
    const height = this.svgHeight;
    const color = d3.scaleOrdinal(d3.schemeDark2);

    const simulation = d3
      .forceSimulation()
      .force(
        "link",
        d3.forceLink().id((d) => {
          return d.id;
        }),
      )
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2));

    const link = svg
      .append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(DATA.links)
      .enter()
      .append("line")
      .attr("stroke-width", (d) => {
        return Math.sqrt(d.value);
      });

    const node = svg
      .append("g")
      .attr("class", "nodes")
      .selectAll("circle")
      .data(DATA.nodes)
      .enter()
      .append("circle")
      .attr("r", 5)
      .attr("fill", (d) => {
        return color(d.group);
      })
      .call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));

    node.append("title").text((d) => {
      return d.id;
    });

    simulation.nodes(DATA.nodes).on("tick", ticked);

    simulation.force("link").links(DATA.links);

    function ticked() {
      link
        .attr("x1", (d) => d.source.x)
        .attr("y1", (d) => d.source.y)
        .attr("x2", (d) => d.target.x)
        .attr("y2", (d) => d.target.y);
      node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
    }

    function dragstarted(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0.3).restart();
      }
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function dragended(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0);
      }
      d.fx = null;
      d.fy = null;
    }
  }
}

注意

在 Lightning Web 组件中,不能用于查询 DOM 元素。请改用 .例如,此 D3 代码示例使用 .请参阅 DOM 访问遏制。documentthis.templatethis.template.querySelector('svg.d3')

有用的模式

下面是一些用于加载代码的有用模式。此代码加载一个没有 CSS 文件的 JavaScript库。

loadScript(this, RESOURCE_NAME + "/lib.js").then(() => {
  /* callback */
});

此代码并行加载多个 JavaScript文件。

Promise.all([
  loadScript(this, RESOURCE_NAME + "/lib1.js"),
  loadScript(this, RESOURCE_NAME + "/lib2.js"),
  loadScript(this, RESOURCE_NAME + "/lib3.js"),
]).then(() => {
  /* callback */
});

确定 JavaScript库是否符合保险箱

使用本指南可确定第三方 JavaScript库是否与 Lightning Locker 兼容。

注意

本指南适用于组织使用 Lightning Locker 的情况。如果组织启用了 Lightning Web Security (LWS),则大多数第三方 JavaScript库无需更改即可按预期工作。但是,显式设置的库需要更改才能使用 LWS。请参阅 LWS 的第三方库注意事项。"use strict"

组件使用的任何库都必须满足与组件相同的 Lightning Locker 要求。

  • 避免跨命名空间直接操作 DOM。请参阅 DOM 访问遏制。
  • 支持 JavaScriptES5 严格模式,如 JavaScript严格模式强制执行中所述。
  • 避免使用 Lightning Locker 阻止的 JavaScriptAPI,如 Lightning Locker 工具中所述的 Locker API 查看器所示。

要确定第三方库是否与 Lightning Locker 兼容,我们建议执行以下步骤。

  1. 创建一个使用该库的小型示例应用。
  2. 验证库加载时是否正确,以及是否可以调用基本功能。
  3. 使用启用了 Locker 的 Locker 控制台。
  4. 检查以下常见违规行为:声明一个变为全局变量的变量严格模式不允许变量成为全局变量。为避免违反此规则,请在库中显式地将库全局附加到 。windowwindow.myLib = (function () { return { myFunction: function (a, b) { return a * b; }, }; })();CSP 违规扫描代码以查找 、 或 标记的任何用法。eval()new Function()<script>DOM 访问冲突如果库尝试对 DOM 进行广泛扫描,而不是仅操作传递给其 API 的元素,则它们将被阻止。如果库操作 DOM,则可以将该指令添加到本机 HTML 元素以允许它,如使用第三方 JavaScript库中所述。lwc:dom="manual"使用非标准或不受支持的 DOM API请参阅在 Locker API 查看器中标记为“支持”和“不支持”的 API。

如果库不合规,请要求库维护人员更新代码以符合 Lightning Locker,或者,如果项目是开源的,请自己贡献更改。另一种选择是分叉存储库,进行更改,然后构建自己的库版本。

符合 LWS 的 JavaScript库

启用 LWS 后,大多数第三方库都会按预期运行。

但是,某些库需要稍作更改。请参阅 LWS 的第三方库注意事项。

从 JavaScript调用 API

从 Lightning Web 组件调用 Salesforce API 和第三方 API。默认情况下,您无法从 JavaScript代码建立 WebSocket 连接或调用第三方 API。为此,请将远程站点添加为受信任的 URL(以前称为 CSP 受信任的站点)。

Lightning 组件框架使用内容安全策略 (CSP)(一种 W3C 标准)来控制可在页面上加载的内容源。默认 CSP 策略不允许从 JavaScript代码进行 API 调用。通过添加受信任的 URL 来更改策略和 CSP 标头的内容。

Salesforce API 接口

使用 Lightning 数据服务 (LDS) 处理 Salesforce 记录的数据和元数据。Lightning Data Service 建立在公共用户界面 API 之上,但它仅支持 API 的一个子集。该子集涵盖了许多处理数据的典型用例。您无法从 JavaScript代码调用除 LDS 之外的 Salesforce API。

如果 LDS 不支持您要使用的对象,或者如果您想使用其他 Salesforce API,请编写一个 Apex 类。

第三方 API

调用 Salesforce API 或第三方 API 通常需要使用 OAuth 2.0 进行身份验证和授权。出于演示目的,某些 API 还会在未经身份验证和授权的情况下提供其数据,例如使用 Google API 资源管理器。我们建议您查看第三方 API 文档,了解使用详情。

谨慎

如果需要在调用中包含身份验证标头,请使用 Apex HttpRequest 类发送请求。在 JavaScript中提供密钥并不安全。

若要调用第三方 API,必须先将第三方 URL 添加到“设置”中的“受信任的 URL”页。如果要从 获取数据,请将基 URL 添加为受信任的 URL。https://www.example.com/items/v1/brandshttps://www.example.com/

使用 Fetch API 进行第三方 API 调用。例如,您可以使用 Fetch API 从 Lightning Web 组件发出 HTTP 请求,然后使用该方法解析 JSON 响应。.json()

async getItems() {
  const response = await fetch("http://example.com/items.json");
  const items = await response.json();
}

提示

lwc-recipes 存储库中的 miscRestApiCall 组件会向 Google 图书 API 发送请求以执行图书搜索。

Fetch API 返回一个 promise,这在处理异步请求时很有用。使用该方法发出请求并获取资源。仅当请求完全失败时(例如,当用户处于脱机状态或请求超时时)时,才会调用该方法。要处理 4XX 或 5XX 错误,请使用 或在块内检查 response.status 属性。fetch()catch()then()try

async getItems() {
  try {
    const response = await fetch("http://example.com/items.json");
    if (!response.ok) {
      throw Error(response);
    }
    const myItems = await response.json();
  } catch (error) {
    console.error("There's a problem with your fetch operation:", error);
  } finally {
    // do something regardless of whether the operation was successful
  }
}

有关更多信息,请参阅 MDN Web 文档:使用 Fetch API。或者,您还可以找到符合您要求的第三方 Web 组件 (beta) 或第三方 JavaScript库。

重要

虽然您可以通过受信任的 URL 调用第三方 API,但无法从第三方站点加载 JavaScript资源,即使从受信任的 URL 也是如此。若要使用第三方站点中的 JavaScript库,请将其添加到静态资源,然后将静态资源添加到组件中。从静态资源加载库后,可以正常使用它。

动态实例化组件

动态组件实例化可以帮助您避免加载并不总是需要的大型模块。此外,当基础组件构造函数直到运行时才知道时,可以实例化组件实例。动态导入是一种方便的解决方案,可以使组件更具可定制性。但是,由于它引入了运行时性能开销,它并不总是最佳解决方案,因此不要过度使用它。

如何使用动态 Lightning Web 组件

https://youtube.com/watch?v=df7P22seEL8

重要

要动态导入和实例化 Lightning Web 组件,您必须启用 Lightning Web Security。

配置动态组件功能

若要实例化动态组件,组件的配置文件必须包含该功能。例如:lightning__dynamicComponent

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>59.0</apiVersion>
  <capabilities>
    <capability>lightning__dynamicComponent</capability>
  </capabilities>
</LightningComponentBundle>

若要使用此功能,必须将该属性设置为 55.0 或更高版本。apiVersion

有关组件配置文件的详细信息,请参阅组件配置文件。

动态组件语法

若要动态实例化组件,请在组件的 HTML 文件中将托管元素与指令一起使用。<lwc:component>lwc:is

下面是一个使用 .<lwc:component>

<template>
    <div class="container">
        <lwc:component lwc:is={componentConstructor}></lwc:component>
    </div>
</template>

<lwc:component>在 DOM 中用作占位符,用于呈现指定的动态组件。您必须与指令一起使用。<lwc:component>lwc:is

该指令在运行时向托管元素提供导入的构造函数。 接受在运行时解析为构造函数的表达式。lwc:is<lwc:component>lwc:isLightningElement

如果构造函数是伪造的,则不会呈现标记及其所有子级。<lwc:component>

如果定义了表达式值,但未定义构造函数,则会引发错误。LightningElement

在组件的 JavaScript文件中,使用 import() 动态导入语法导入自定义元素。

import { LightningElement } from "lwc";
export default class extends LightningElement {
  componentConstructor;
  // Use connectedCallback() on the dynamic component
  // to signal when it's attached to the DOM
  connectedCallback() {
    import("c/concreteComponent")
      .then(({ default: ctor }) => (this.componentConstructor = ctor))
      .catch((err) => console.log("Error importing component"));
  }
}

该调用返回一个解析为构造函数的 promise。然后呈现元素而不是占位符。用于动态组件的标记名称是为给定构造函数返回的值。import()LightningElementlwc:component

与常规组件类似,动态组件被实例化并附加到 DOM。如果动态组件的构造函数发生更改,则会从 DOM 中删除现有元素。

在此示例中,导入完成后将呈现以下 HTML。

<div class="container">
  <c-concrete-component></c-concrete-component>
</div>

除了使用 then() 方法外,还可以使用 and 运算符返回组件构造函数。asyncawait

import { LightningElement } from "lwc";
export default class App extends LightningElement {
  componentConstructor;
  async connectedCallback() {
    const { default: ctor } = await import("c/myComponent");
    this.componentConstructor = ctor;
  }
}

选择动态组件

必须先将自定义元素附加到 DOM,然后才能选择它。若要选择动态组件,请使用指令或使用分配给该组件的属性,例如类名。lwc:ref

<template>
    <lwc:component lwc:is={componentConstructor}
                lwc:ref="myCmp">
    </lwc:component>
</template>

要确定动态组件是否附加到 DOM,请执行以下操作:

  • 在动态组件中用于在附加到 DOM 时发出信号。connectedCallback
  • 在父组件上使用以检测动态组件何时呈现为 DOM。renderedCallback
import { LightningElement } from "lwc";
export default class extends LightningElement {
  componentConstructor;

  connectedCallback() {
    import("lightning/concreteComponent")
      .then(({ default: ctor }) => (this.componentConstructor = ctor))
      .catch((err) => console.log("Error importing component"));
  }

  renderedCallback() {
    // this.refs.myCmp will be available on the next rendering cycle after the constructor is set
    if (this.refs.myCmp) {
      // this.refs.myCmp will contain a reference to the DOM node
      console.log(this.refs.myCmp);
    }
  }
}

分配属性和模板指令

所有可应用于 的受支持的 HTML 属性也可以应用于 。HTMLElementlwc:component

一些例子包括:

  • 标准全局 HTML 属性
  • 自定义 HTML 属性,例如data-*
  • 事件侦听器

动态组件的行为类似于标准的 Lightning Web 组件。 支持 HTML 元素的指令,但 .lwc:componentlwc:external

子元素

您可以在动态组件上包含子元素。 首先呈现动态组件,然后呈现其子组件。每次动态组件更改时,都会从 DOM 中删除现有元素及其所有子元素。然后,新的动态组件将与其子组件一起呈现。<lwc:component>

<template>
    <lwc:component lwc:is={ctor}>
        <span>child</span>
    </lwc:component>
</template>

在标记中传递属性

将属性传递给动态组件类似于将属性传递给子组件。在动态组件中,使用属性进行批注并在模板中使用它。@api

// dynamicCmp.js
import { LightningElement, api } from "lwc";

export default class extends LightningElement {
  @api text;
}

在占位符组件中,导入自定义元素。

// myApp.js
import { LightningElement } from "lwc";
import DynamicCmp from "c/dynamicCmp";

export default class extends LightningElement {
  componentConstructor = DynamicCmp;
}

传入属性的值。text

<!-- myApp.html -->
<template>
  <lwc:component lwc:is={componentConstructor} text="I love dynamic components!"></lwc:component>
</template>

在某些情况下,可能无法通过标准标记语法设置动态组件可以接受的所有潜在属性。例如,当要实例化的组件事先不知道,或者要实例化的组件接受一组不同的公共属性时。

在这些特定情况下,[lwc’ 还使元素能够接受在运行时绑定为属性的对象。lwc:spread directive](../create/create-components-spread-properties.md) can be used to dynamically set to dynamic component properties at runtime.

通过使用 注释属性,使属性公开。@api

// dynamicCmp.js
import { LightningElement, api } from "lwc";

export default class extends LightningElement {
  @api city;
  @api state;
}

使用模板中的属性。

<!-- dynamicCmp.html -->
<template>
  <p>{city}, {state}</p>
</template>

像往常一样导入自定义元素,并使用属性名称和值创建对象。childProps

// myApp.js
import { LightningElement } from "lwc";

export default class extends LightningElement {
  componentConstructor;
  childProps = { city: "San Francisco", state: "CA" };

  connectedCallback() {
    // import your custom element
  }
}

用于传入属性,然后在动态组件上呈现“San Francisco, CA”。lwc:spread

<!-- myApp.html-->
<template>
  <lwc:component lwc:is={componentConstructor} lwc:spread={childProps}></lwc:component>
</template>

包中的动态组件

您只能在托管包中使用动态组件。不支持未锁定包中的动态组件。

性能注意事项

由于动态导入本质上是“动态”的,因此框架不会提前预取这些模块,这有时会对用户体验造成不利影响。

使用静态 import 语句时,框架会在运行时在单个 JavaScript文件中提供组件及其所有依赖项。若要获取动态导入的组件,如果内容尚未存储在浏览器 HTTP 缓存中,则框架必须执行网络往返。

在此示例中,组件类与组件类一起作为单个 JavaScript模块提供给浏览器。调用函数时,将在运行时检索组件类。BundleExampleStaticImportDynamicImportloadModule

import { LightningElement } from "lwc";
import StaticImport from "c/static-import";

export default class BundleExample extends LightningElement {
  async loadModule() {
    const { default: DynamicImport } = await import("c/dynamic-import");
    return DynamicImport;
  }
}

使用动态组件时,请考虑以下建议。

使动态导入可静态分析

虽然目前尚未实现,但未来的框架优化可以优化可静态分析动态导入的代码。将 JavaScript 字符串文字传递给函数:import()

import("c/analyzable"); // 👍: Statically analyzable
import("c/" + "analyzable"); // 👎: Not statically analyzable
import("c/" + componentName); // 👎: Not statically analyzable

在有些情况下,动态导入总是无法进行静态分析。当通过元数据定义组件名称时,这种情况尤其如此。对于所有其他情况,我们强烈建议将所有动态导入进行静态分析,以便将来进行框架优化。

不要过度使用动态导入

动态导入是一种方便的解决方案,可以使组件更具可定制性。但是,它并不总是最佳解决方案,因为它引入了运行时性能开销。让我们用一些例子来说明潜在的陷阱。

在第一个示例中,我们创建一个图表组件 .它接受图表类型作为公共属性,可以设置为条形图、饼图或折线图。在内部,该组件使用 c/barChart、c/pieChart 或 c/lineChart 组件之一来根据类型呈现图表。c/chart

// 👎 DON'T USE THIS
// Example with non statically analyzable dynamic import:
import { LightningElement, api } from "lwc";

const KNOWN_TYPE = new Set(["bar", "pie", "line"]);

export default class App extends LightningElement {
  chartCtor;

  _type = "line";

  @api
  get type() {
    return this._type;
  }
  set type(val) {
    if (!KNOWN_TYPE.has(val)) {
      console.warn(`Unknown chart type: ${val}`);
    }

    this._type = val;

    const chartComponentName = `c/${val}Chart`;
    import(chartComponentName).then(({ default: ChartCtor }) => {
      this.chartCtor = ChartCtor;
    });
  }
}

如果 、 和 的总捆绑包大小较小,则最好将组件更新为使用静态导入,以避免运行时的网络往返。通常,我们建议您从静态导入开始,如果由于导入非严格需要的组件而导致性能成为问题,则使用动态导入。c/barChartc/pieChartc/lineChartc/chart

// 👍 USE THIS
// Example with static imports:
import { LightningElement, api } from "lwc";

import BarChart from "c/barChart";
import PieChart from "c/pieChart";
import LineChart from "c/lineChart";

const KNOWN_TYPE = new Set(["bar", "pie", "line"]);
const CHART_MAPPING = {
  bar: BarChart,
  pie: PieChart,
  line: LineChart,
};

export default class App extends LightningElement {
  chartCtor;

  _type = "line";

  @api
  get type() {
    return this._type;
  }
  set type(val) {
    if (!KNOWN_TYPE.has(val)) {
      console.warn(`Unknown chart type: ${val}`);
    }

    this._type = val;
    this.chartCtor = CHART_MAPPING[val];
  }
}

如果静态导入所有三个模块由于包大小增加而对运行时性能产生负面影响,仍然可以更新示例以将非静态可分析的动态导入转换为静态可分析的动态导入。

// 👍 USE THIS
// Example with statically analyzable dynamic import:
import { LightningElement, api } from "lwc";

const KNOWN_TYPE = new Set(["bar", "pie", "line"]);
const CHART_MAPPING = {
  bar: () => import("c/barChart"),
  pie: () => import("c/pieChart"),
  line: () => import("c/lineChart"),
};

export default class App extends LightningElement {
  chartCtor;

  _type = "line";

  @api
  get type() {
    return this._type;
  }
  set type(val) {
    if (!KNOWN_TYPE.has(val)) {
      console.warn(`Unknown chart type: ${val}`);
    }

    this._type = val;
    CHART_MAPPING[val]().then(({ default: ChartCtor }) => {
      this.chartCtor = ChartCtor;
    });
  }
}

这是另一个说明这一原则的例子。在此示例中,我们创建了一个负责呈现实体字段值的组件。由于它是一个泛型组件,因此它接受一个渲染器公共属性,该属性是用于渲染此字段的组件的名称。与前面的示例不同,已知渲染器的列表事先是未知的,因为组件可能接受任何组件名称。c/fieldc/field

// 👎 DON'T USE THIS
// Example with component name as prop:
import { LightningElement, api } from "lwc";

export default class Field extends LightningElement {
  rendererCtor;

  _renderer;

  @api
  get renderer() {
    return this._renderer;
  }
  set renderer(val) {
    this._renderer = val;

    import(val).then(({ default: rendererCtor }) => {
      this.rendererCtor = rendererCtor;
    });
  }
}

性能更高的方法是让组件接受渲染器构造函数作为公共属性,而不是渲染器组件名称。仅当组件未暴露给构建器(如 Lightning 应用程序构建器)时,这才有效。c/field

// 👍 USE THIS
// Example with component constructor as prop:
import { LightningElement, api } from "lwc";

export default class Field extends LightningElement {
  @api rendererCtor;
}

在这种替代设计中,字段组件将渲染器组件类的加载委托给其父组件。父组件现在可以根据其要求使用静态或动态导入。

字段、属性和特性

在组件的 JavaScript 类中声明字段。在组件的模板中引用它们以动态更新内容。

字段属性几乎是可以互换的术语。组件作者声明类中的字段。类的实例具有属性。对于组件使用者来说,字段是属性。在 Lightning Web 组件中,只有组件作者用来修饰的字段才能作为对象属性公开提供给使用者。@api

属性和属性几乎是可以互换的术语,可能会造成混淆。一般来说,在 HTML 中我们谈论属性,在 JavaScript 中我们谈论属性。

提示

如果您已经开发过 Aura 组件,那么您就熟悉术语 attribute。在 Lightning Web 组件中,最接近 Aura 属性的是 JavaScript 属性。

反应

反应性是 Lightning Web 组件框架的核心系统。该框架观察字段和属性值的更改。当它观察到变化时,它会做出反应。它重新计算模板中使用的所有表达式,并重新呈现组件,从而显示新值。

字段属性几乎是可以互换的术语。组件作者声明类中的字段。类的实例具有属性。对于组件使用者来说,字段是属性。在 Lightning Web 组件中,只有组件作者用来修饰的字段才能作为对象属性公开提供给使用者。@api

公共属性

若要公开公共属性,请使用 修饰字段。公共属性定义组件的 API。模板中使用的公共属性是反应式的。如果模板中使用的公共属性的值发生更改,则组件将重新呈现。@api

当组件重新渲染时,将重新计算模板中使用的表达式,并执行 renderedCallback() 生命周期钩子。

@api装饰器

https://youtube.com/watch?v=F7J5yn3MIXw

提示

装饰器是一种 JavaScript 语言功能。@api装饰器是 Lightning Web 组件独有的。一个字段只能有一个修饰器。

从 导入装饰器。此代码将字段公开为公共属性。@apilwcitemName

// todoItem.js
import { LightningElement, api } from "lwc";
export default class TodoItem extends LightningElement {
  @api itemName = "New Item";
}

在模板中显示的值。itemName

<!-- todoItem.html -->
<template>
  <div class="view">
    <label>{itemName}</label>
  </div>
</template>

此 JavaScript 类和 HTML 模板定义组件,其中是命名空间。使用该组件的组件可以设置该属性。c-todo-itemcc-todo-itemitemName

注意

JavaScript 中的属性名称采用驼峰大小写,而 HTML 属性名称采用烤肉串大小写(破折号分隔)以匹配 HTML 标准。在 中,标记中的属性映射到 的 JavaScript 属性。todoApp.htmlitem-nameitemNamec-todo-item

<!-- todoApp.html -->
<template>
  <div class="listing">
    <c-todo-item item-name="Milk"></c-todo-item>
    <c-todo-item item-name="Bread"></c-todo-item>
  </div>
</template>

在其标记中使用组件的所有者组件可以通过 DOM 属性访问组件的公共属性。DOM 属性是在类中声明的公共字段。它们可以通过带有点表示法的 DOM 元素访问。在此示例中,该组件将一个公共字段声明为 ,其值可通过 (DOM 元素)实例上的 DOM 属性访问。c-todo-item@api itemNamec-todo-item

// todoApp.js
myItem = this.template.querySelector("c-todo-item").itemName;

提示

请参阅存储库中的 CompositionBasics 示例。lwc-recipes

字段、对象和数组的反应性

如果字段的值发生更改,并且该字段在模板中使用或在模板中使用的属性的 getter 中使用,则组件将重新呈现并显示新值。如果为字段分配了对象或数组,则框架会观察到对象或数组内部的一些更改,例如在分配新值时。

当组件重新渲染时,将重新计算模板中使用的表达式,并执行 renderedCallback() 生命周期钩子。

当您在“名字”或“姓氏”字段中输入值时,组件会将其转换为大写并显示。

名字和姓氏的输入字段。输入的名称将转换为大写。
<!-- helloExpressions.html -->

<template>
  <lightning-card title="HelloExpressions" icon-name="custom:custom14">
    <div class="slds-m-around_medium">
      <lightning-input
        name="firstName"
        label="First Name"
        onchange={handleChange}
      ></lightning-input>
      <lightning-input
        name="lastName"
        label="Last Name"
        onchange={handleChange}
      ></lightning-input>
      <p class="slds-m-top_medium">Uppercased Full Name: {uppercasedFullName}</p>
    </div>
  </lightning-card>
</template>

组件的类定义了 和 字段。由于它们在模板 () 中使用的属性的 getter 中使用,因此当其值更改时,组件会重新呈现。firstNamelastNameuppercasedFullName

// helloExpressions.js

import { LightningElement } from "lwc";

export default class TrackExample extends LightningElement {
  firstName = "";
  lastName = "";

  handleChange(event) {
    const field = event.target.name;
    if (field === "firstName") {
      this.firstName = event.target.value;
    } else if (field === "lastName") {
      this.lastName = event.target.value;
    }
  }

  get uppercasedFullName() {
    return `${this.firstName} ${this.lastName}`.trim().toUpperCase();
  }
}

注意

字段是反应式的。Expando 属性是在运行时添加到对象的属性,不是响应式的。

提示

请参阅存储库中的 helloExpressions 组件。lwc-recipes

反应性注意事项

尽管字段是反应式的,但 LWC 引擎以浅层方式跟踪字段值更改。通过使用 比较值标识来将新值分配给字段时,将检测到更改。此方法适用于数字或布尔值等基元类型。===

import { LightningElement } from "lwc";

export default class ReactivityExample extends LightningElement {
  bool = true;
  number = 42;
  obj = { name: "John" };

  checkMutation() {
    this.bool = false; // Mutation detected

    this.number = 42; // No mutation detected: previous value is equal to the newly assigned value
    this.number = 43; // Mutation detected

    this.obj.name = "Bob"; // No mutation detect: `obj` field value is not reassigned
    this.obj = { name: "John" }; // Mutation detected - redefining the object with the same value creates a new object
    this.obj = { ...this.obj, title: "CEO" }; // Mutation detected
  }
}

在操作复杂类型(如对象和数组)时,必须创建一个新对象并将其分配给要检测更改的字段。

为了避免在处理复杂对象时出现此类问题,请使用修饰器深入跟踪对字段值所做的更改。@track

跟踪对象和数组内部的更改

若要观察对象属性或数组元素的更改,请使用 修饰字段。@track

当字段修饰有 时,Lightning Web 组件会跟踪对以下各项内部值的更改:@track

  • 使用以下方式创建的普通对象{}
  • 使用[]

该框架以递归方式观察对普通对象和数组的突变,包括嵌套对象、嵌套数组以及对象和数组的混合。还处理循环引用。

但是,该框架不会观察到对复杂对象(例如继承自 、类实例、 、 或 的对象)的突变。ObjectDateSetMap

@track装饰器

https://youtube.com/watch?v=evIUIHcWbZI

观察对象的属性

要告诉框架观察对象属性的变化,请使用 修饰字段。@track

注意

如前所述,在不使用 的情况下,框架会观察到为字段分配新值的更改。如果新值不是以前的值,则组件将重新呈现。@track===

例如,让我们稍微更改代码以声明字段,该字段包含一个具有两个属性的对象,以及 .该框架会观察到将新值分配给 的更改。fullNamefirstNamelastNamefullName

fullName = { firstName: "", lastName: "" };

此代码为字段分配一个新值,以便组件重新呈现。fullName

// Component rerenders.
this.fullName = { firstName: "John", lastName: "Doe" };

但是,如果我们为对象的某个属性分配一个新值,则组件不会重新呈现,因为不会观察到这些属性。

// Component doesn't rerender.
this.fullName.firstName = "John";

该框架会观察到为字段分配新值的更改。此代码不会执行此操作,而是为对象的属性分配一个新值。fullNamefirstNamefullName

要告诉框架观察对象属性的变化,请使用 修饰字段。现在,如果我们更改任一属性,组件就会重新渲染。fullName@track

// Component rerenders.
@track fullName = { firstName : '', lastName : ''};
this.fullName.firstName = 'John';

如果属性包含对象,若要跟踪对象属性的更改,请使用 注释该属性。为了理解,让我们修改我们的示例。我们的示例初始化 and 为空字符串,这些字符串是原始值,因此组件在它们更改时会重新呈现。@trackfirstNamelastName

使用新属性重新呈现对象

仅当更新了在上一个渲染周期中访问的属性时,组件才会重新渲染,即使对象使用 批注了 。这样可以防止组件过度重新渲染,@track

考虑这个跟踪对象和一个打印对象属性的 getter。

@track obj = {value1: 'Hello'};

get words() {
    return Object.entries(this.obj)
              .map(([key, value]) => ({key, value}));
  }

在第一个渲染周期中,框架会记录所访问的内容。任何不影响的突变都会被忽略,因为它不会影响呈现的内容。因此,更改 会触发重新呈现,但向 添加新属性或更改不会触发重新呈现。obj.value1objvalue1value1objvalue2

// Component rerenders.
setValue1(e) {
  this.obj.value1 = 'Hello World';
}

// Component doesn’t rerender.
setValue2(e) {
  this.obj.value2 = 'Hello LWC';
}

若要在添加新属性时重新呈现组件,请将该对象分配给具有这两个值的新对象。

setValue2(e) {
  this.obj = {
    ...this.obj,
      value2: 'Hello LWC'
  };
}

观察数组的元素

另一个用例是告诉框架观察数组元素的变化。@track

如果不使用 ,框架会观察到为字段分配新值的更改。@track

arr = ["a", "b"];

当您将新值分配给 时,组件将重新呈现。arr

// Component rerenders.
this.arr = ["x", "y", "z"];

但是,如果我们在数组中更新或添加元素,则该组件不会重新渲染。

// Component doesn’t rerender.
this.arr[0] = "x";
this.arr.push("c");

若要告诉框架观察数组元素的变化,请使用 此外,框架不会自动将数组转换为字符串以更新数组元素。要返回更新的字符串,请使用 getter 将数组的元素转换为字符串。arr@trackjoin()

@track arr = ['a','b'];

get computedArray() { return this.arr.join(','); }

update() {
  this.arr[0] = 'x';
  this.arr.push('c');
}

观察复杂物体

让我们看一个字段为 的组件,其类型为 。该模板有几个按钮,用于更改 的内部状态。此示例突出显示了创建对象,因为它不是普通的 JavaScript 对象,LWC 引擎不会观察到内部状态突变,即使代码使用 .xDatexnew Date()@track

// trackDate.js
import { LightningElement, track } from "lwc";
export default class TrackDate extends LightningElement {
  @track x = new Date();

  initDate() {
    this.x = new Date();
  }

  updateDate() {
    this.x.setHours(7); // No mutation detected
  }
}

与前面的示例类似,该模板有几个按钮可以更改 的内部状态。x

<!-- trackDate.html -->
<template>
  <p>Date: {x}</p>

  <button onclick={initDate}>Init</button>
  <button onclick={updateDate}>Update</button>
</template>

单击“初始化”按钮时,将观察到更改并重新呈现模板。Lightning Web Components 观察到它指向一个新对象。但是,单击“更新”时,不会重新呈现模板。Lightning Web 组件不会观察到对象值的变化。xDateDate

若要确保在值更改时重新呈现模板,请克隆现有日期并更新其值。

updateDate() {
  const cloned = new Date(this.x.getTime());
  cloned.setHours(7);

  // Assign the new date instance to rerender the component.
  this.x = cloned;
}

注意

将属性设置为无法跟踪的值时,将记录警告。如果尝试调试组件在更改时未重新呈现的方案,请查看浏览器控制台。在我们的示例中,浏览器控制台会记录以下有用的警告:

Property "x" of [object:vm TrackDate] is set to a non-trackable object, which means changes into that object cannot be observed.

属性和属性名称

JavaScript 中的属性名称采用驼峰大小写,而 HTML 属性名称采用烤肉串大小写(破折号分隔)以匹配 HTML 标准。

例如,名为 的 JavaScript 属性映射到名为 的 HTML 属性。itemNameitem-name

// child.js
import { LightningElement, api } from "lwc";

export default class extends LightningElement {
  @api itemName;
}
<!-- parent.html -->
<template>
  <c-child item-name="Child item"></c-child>
</template>

JavaScript 属性名称

不要以这些字符开头属性名称。

  • on(例如,onClick)
  • aria(例如,ariaDescribedby)
  • data(例如,dataProperty)

不要将这些保留字用于属性名称。

  • slot
  • part
  • is

HTML 属性名称

模板中的 HTML 属性不能包含大写字符。属性名称可以以以下字母开头:

  • 小写字母字符
  • 下划线 (_)
  • 美元符号 ($)
  • 可选连字符后跟字母字符(-a)

属性名称可以包含重复的下划线 () 或下划线后跟连字符 ()。___-

如果连字符不是属性名称中的第一个字符,则允许使用连字符后跟下划线 ()。例如,这些是有效的属性名称:-_

  • _myattribute
  • $myattribute
  • my_-attribute

如果 JavaScript 属性以大写字符开头,并且希望通过 HTML 属性进行设置,则必须使用特殊语法。属性名称的大写字符为小写,并以连字符 为前缀。前导连字符告诉引擎,属性名称中的第一个字母字符在 JavaScript 类中是用前导大写字符声明的。例如,JavaScript 属性对应于 HTML 属性。-upper@api Upperupper

在 JavaScript 中访问 HTML 全局属性

不建议使用 HTML 全局属性,这些属性类似于所有 HTML 元素,并且是所有 HTML 元素通用的属性。如果确实使用全局 HTML 属性,请使用 .classtitle@api

某些 HTML 全局属性不遵循 Lightning Web 组件驼峰大小写和烤肉串大小写约定。如果在 JavaScript 中为这些 HTML 全局属性之一创建 getter 或 setter,请使用此列表中的大小写。

HTML 全局属性JavaScript 中的属性
accesskeyaccessKey
bgcolorbgColor
colspancolSpan
contenteditablecontentEditable
crossorigincrossOrigin
datetimedateTime
forhtmlFor
formactionformAction
ismapisMap
maxlengthmaxLength
minlengthminLength
novalidatenoValidate
readonlyreadOnly
rowspanrowSpan
tabindextabIndex
usemapuseMap

例如,要访问 JavaScript 中的 HTML 属性,请使用 .maxlengthtextareamaxLength

此外,要从 JavaScript 访问 HTML 属性,请使用 .forhtmlFor

在 JavaScript 中访问 ARIA 属性

ARIA 属性支持辅助技术。要在 JavaScript 中访问这些属性,请使用驼峰大小写。例如,要访问 ,请使用 .要访问 ,请使用 .aria-checkedariaCheckedaria-errormessageariaErrorMessage

Web API 属性

Lightning Web 组件反映了许多 Web API 的属性。

元素

Lightning Web 组件反映了 Element 界面的这些属性。

childrenclassListclassNamefirstElementChildgetAttributegetAttributeNSgetBoundingClientRectgetElementsByClassNamegetElementsByTagNamehasAttributeidlastElementChildquerySelectorquerySelectorAllremoveAttributeremoveAttributeNSsetAttributeNSsetAttributeshadowRootslot

请参阅 Shadow DOM、访问组件拥有的元素以及将标记传递到插槽中。

在 Salesforce 组织中启用 Lightning Web Security 后,setAttributeNS、setAttribute 和 shadowRoot 会因失真而修改。

事件目标

Lightning Web 组件反映了 EventTarget 接口的这些属性。

addEventListenerdispatchEventremoveEventListener

请参阅与事件通信。

HTMLElement

Lightning Web 组件反映了 HTMLElement 接口的这些属性。

accessKeyLabelcontentEditabledatasetdirhiddenisContentEditablelangoffsetHeightoffsetLeftoffsetParentoffsetTopoffsetWidthtitle

在 Salesforce 组织中启用 Lightning Web Security 时,数据集会因失真而修改。

节点

Lightning Web 组件反映了 Node 界面的这些属性。

childNodesfirstChildisConnectedlastChild

请参见在 DOM 中插入或删除组件时运行代码。

WAI-ARIA 状态和属性

Lightning Web 组件反映了这些 WAI-ARIA 状态和属性。

ariaActiveDescendantariaAtomicariaAutoCompleteariaBusyariaCheckedariaColCountariaColIndexariaColSpanariaControlsariaCurrentariaDescribedByariaDetailsariaDisabledariaErrorMessageariaExpandedariaFlowToariaHasPopupariaHiddenariaInvalidariaKeyShortcutsariaLabelariaLabelledByariaLevelariaLiveariaModalariaMultiLineariaMultiSelectableariaOrientationariaOwnsariaPlaceholderariaPosInSetariaPressedariaReadOnlyariaRelevantariaRequiredariaRoleDescriptionariaRowCountariaRowIndexariaRowSpanariaSelectedariaSetSizeariaSortariaValueMaxariaValueMinariaValueNowariaValueText

请参阅组件可访问性。

使用 getter 和 setter 修改数据

若要在每次设置公共属性时执行逻辑,请编写自定义 setter。

如果为公共属性编写 setter,则还必须编写 getter。用 注释 getter 或 setter,但不能同时注释两者。最佳做法是注释 getter。@api

若要将属性值保存在 getter 和 setter 中,请使用字段。此示例使用该属性,该属性以下划线为前缀,以指示该属性是私有的。_uppercaseItemName

此示例组件将字符串转换为大写。<c-todo-item>

<!-- todoItem.html -->
<template> {itemName} </template>

属性值通过 getter 提供给模板。

// todoItem.js
import { LightningElement, api } from "lwc";
export default class TodoItem extends LightningElement {
  _uppercaseItemName;

  @api
  get itemName() {
    return this._uppercaseItemName;
  }

  set itemName(value) {
    this._uppercaseItemName = value.toUpperCase();
  }
}

提示

有关使用 getter 和 setter 的另一个示例,请参阅 lwc-recipes 示例存储库中的 和 组件。apiSetterGettertodoList

Boolean 属性

标准 HTML 元素上的布尔属性是通过将属性添加到元素来设置的。如果缺少该属性,则该属性默认为 。因此,属性的默认值始终为 。Lightning Web 组件对布尔属性使用相同的原则。truefalsefalse

静态设置属性

始终将布尔公共属性的默认值设置为 。如果将默认值设置为 instead in ,则无法静态地将值切换为 in markup。falsetruebool.jsfalse

// bool.js
import { LightningElement, api } from "lwc";

export default class Bool extends LightningElement {
  @api show = false;
}

下面是 HTML 文件。

<!-- bool.html -->
<template>
  <p>show value: {show}</p>
</template>

父组件包括 ,它显示默认值 ,因为该属性未添加到 。c-boolfalseshow<c-bool>

重要

在标记中指定的唯一方法是省略 boolean 属性。在标记中设置的计算结果为 。falseshow="false"true

<!-- parent.html -->
<template>
  <c-bool></c-bool>
</template>

若要将属性设置为 ,请将具有空值的属性添加到标记中。此版本的 显示属性的值。showtrueshowc-parenttrueshow

<!-- parent.html -->
<template>
  <c-bool show></c-bool>
</template>

的值是,但属性不会反映在呈现的 HTML 中,除非您通过调用 来显式反映该属性。请参阅将 JavaScript 属性反映为 HTML 属性。showtrueshowsetAttribute

注意

当布尔属性的值为 时,呈现的 HTML 为 。true<my-component my-attribute>

动态设置属性

若要动态切换值,请从父组件传递动态计算值。例如:

<!-- parent.html -->
<template>
  <c-bool show={computedValue}></c-bool>
</template>

使用 JavaScript getter in 返回 的值。parent.js{computedValue}

将 JavaScript 属性反映为 HTML 属性

您可以控制公共 JavaScript 属性是否在 Lightning Web 组件的渲染 HTML 中显示为属性。在创建可访问的组件时,允许属性显示为属性尤为重要,因为屏幕阅读器和其他辅助技术使用 HTML 属性。

默认情况下,所有 HTML 属性都是响应式的。当组件 HTML 中的属性值发生更改时,将重新呈现该组件。

当您通过将某个属性公开为公共属性来控制该属性时,默认情况下,该属性将不再显示在 HTML 输出中。若要将值作为属性传递到呈现的 HTML(以反映属性),请为属性定义 getter 和 setter 并调用该方法。setAttribute()

您还可以在 setter 中执行操作。使用字段保存计算值。

此示例公开为公共属性。它将标题转换为大写,并使用该属性来保存标题的计算值。setter 调用以将属性的值反映到 HTML 属性。title_privateTitlesetAttribute()

// myComponent.js
import { LightningElement, api } from "lwc";

export default class MyComponent extends LightningElement {
  _privateTitle;

  @api
  get title() {
    return this._privateTitle;
  }

  set title(value) {
    this._privateTitle = value.toUpperCase();
    this.setAttribute("title", this._privateTitle);
  }
}
/* parent.html */
<template>
  <c-my-component title="Hover Over the Component to See Me"></c-my-component>
</template>
/* Generated HTML */
<c-my-component title="HOVER OVER THE COMPONENT TO SEE ME">
  <div>Reflecting Attributes Example</div>
</c-my-component>

要确保您了解 JavaScript 属性如何反映到 HTML 属性,请查看不调用 .生成的 HTML 不包含该属性。setAttribute()title

// myComponent.js
import { LightningElement, api } from "lwc";

export default class MyComponent extends LightningElement {
  _privateTitle;

  @api
  get title() {
    return this._privateTitle;
  }

  set title(value) {
    this._privateTitle = value.toUpperCase();
    // this.setAttribute('title', this._privateTitle);
  }
}
/* parent.html */
<template>
  <c-my-component title="Hover Over the Component to See Me"></c-my-component>
</template>
/* Generated HTML */
<c-my-component>
  <div>Reflecting Attributes Example</div>
</c-my-component>

在设置值之前,请检查使用者是否已经设置了该值。

// myComponent.js
import { LightningElement } from "lwc";

export default class MyComponent extends LightningElement {
  connectedCallback() {
    const tabindex = this.getAttribute("tabindex");

    // Set the tabindex to 0 if it hasn’t been set by the consumer.
    if (!tabindex) {
      this.setAttribute("tabindex", "0");
    }
  }
}

在此标记中设置 using 结果。tabindexthis.setAttribute()

<c-my-component tabindex="0"></c-my-component>

要设置这些属性,请使用 .setAttribute()

  • for
  • aria-activedescendant
  • aria-controls
  • aria-describedby
  • aria-details
  • aria-errormessage
  • aria-flowto
  • aria-labelledby
  • aria-owns

要从呈现的 HTML 中隐藏 HTML 属性,请调用 .removeAttribute()

管理 Getter 中的属性依赖关系

HTML 中的属性变成了 JavaScript 中的属性赋值。在这两种情况下,都不能保证分配顺序。要检查是否存在其他属性,请使用 getter。

在模板中使用 getter 引用。不要使用 getter,也不要使用依赖于其他属性中的值的 setter。@api@api@api

假设我们有一个数据表组件,该组件在所选行上显示复选标记。我们有两个独立的属性和 ,它们依赖于另一个属性。rowsselectedRows

<template>
  <c-datatable selected-rows="1,2" rows="1,2,3,4"> </c-datatable>
</template>

由于无法保证接收属性的顺序,因此请使用 getter 来检查依赖关系。

export default class Datatable extends LightningElement {
  @track state = {};

  @api
  get rows() {
    return this.state.rows;
  }

  set rows(value) {
    this.state.rows = value;

    // Check to see if the rows have
    // been marked as selected.
    if (this.state.selectedRows && !this.selectedRowsSet) {
      this.markSelectedRows();
      this.selectedRowsSet = true;
    }
  }

  @api
  set selectedRows(value) {
    this.state.selectedRows = value;

    // If rows haven’t been set,
    // then we can't mark anything
    // as selected.
    if (!this.state.rows) {
      this.selectedRowsSet = false;
      return;
    }

    this.markSelectedRows();
  }

  get selectedRows() {
    return this.state.selectedRows;
  }

  markSelectedRows() {
    // Mark selected rows.
  }
}

使用 getter 和 setter 可确保公共 API 协定易于执行。组件不应更改用 批注的属性的值。@api

如果某些内容在设定的时间依赖于该值,则规范化 setter 中的数据,例如,以编程方式将 CSS 类附加到元素上。返回 getter 中的原始值。规范化也可以在 getter 中完成,这样即使使用者没有设置任何内容,模板也可以访问值。

@track state = {
    selected : false
};

privateSelected = 'false';

@api
get selected() {
    return this.privateSelected;
}
set selected(value) {
    this.privateSelected = value;
    this.state.selected = normalizeBoolean(value)
}

组件

您可以在另一个组件的主体中添加组件。组合使您能够从更简单的构建块组件构建复杂的组件。

注意

允许继承,但不建议这样做,因为组合通常更有效。若要在组件之间共享逻辑,请使用仅包含逻辑的模块。请参阅其他 JavaScript 文件。如果您选择使用继承,请注意,它不适用于 Lightning Locker 下的命名空间。您必须启用 LWS 才能跨命名空间使用继承。不能在任一安全体系结构中扩展命名空间。lightning

Compose 组件

使用一组较小的组件组合应用和组件非常有用,以使代码更易于重用和维护。命名空间包含许多基本组件,例如 ,您可以使用这些组件来构建组件。lightninglightning-button

让我们看一个由组件组成的简单应用程序。标记是人为的,因为我们想说明所有者容器的概念。在实际应用中,实例的数量是可变的,并在循环中动态填充。c-todo-itemfor:each

<!-- todoApp.html -->
<template>
  <c-todo-wrapper>
    <c-todo-item item-name="Milk"></c-todo-item>
    <c-todo-item item-name="Bread"></c-todo-item>
  </c-todo-wrapper>
  <template></template
></template>

所有者

所有者是拥有模板的组件。在此示例中,所有者是组件。所有者控制其包含的所有组合组件。所有者可以:c-todo-app

  • 在组合组件上设置公共属性
  • 在组合组件上调用方法
  • 侦听组合组件触发的任何事件

容器

容器包含其他组件,但其本身包含在所有者组件中。在此示例中,是一个容器。容器不如所有者强大。容器可以:c-todo-wrapper

  • 读取但不能更改包含组件中的公共属性
  • 在组合组件上调用方法
  • 侦听它所包含的组件冒泡的一些(但不一定是全部)事件。

父母和孩子

当一个组件包含另一个组件时,另一个组件又可以包含其他组件,我们有一个包含层次结构。在文档中,我们有时会讨论父组件和子组件。父组件包含子组件。父组件可以是所有者,也可以是容器。

设置子组件的属性

若要向下通信包含层次结构,所有者可以在子组件上设置属性。HTML 中的属性变成了 JavaScript 中的属性赋值。

父组件可以在子组件上设置基元值,如字符串或数字。但是,传递给组件的对象或数组等非原始值是只读的,您必须进行浅拷贝才能修改任何嵌套值。

在子组件上设置基元值

让我们看一下所有者 , 如何在 的两个实例上设置公共属性。c-todo-appc-todo-item

看。修饰器将字段公开为公共属性。todoItem.js@apiitemName

// todoItem.js
import { LightningElement, api } from "lwc";
export default class TodoItem extends LightningElement {
  @api itemName;
}

若要设置公共属性,请在每个组件上设置该属性。itemNametodoApp.htmlitem-namec-todo-item

<!-- todoApp.html -->
<template>
  <c-todo-item item-name="Milk"></c-todo-item>
  <c-todo-item item-name="Bread"></c-todo-item>
</template>

JavaScript 中的属性名称采用驼峰大小写,而 HTML 属性名称采用烤肉串大小写(破折号分隔)以匹配 HTML 标准。在 中,标记中的属性映射到 的 JavaScript 属性。todoApp.htmlitem-nameitemNamec-todo-item

提示

此示例使用 和 的静态值,但实际组件通常会对所有者的 JavaScript 文件 .MilkBreadfor:eachtodoApp.js

有关稍微复杂的示例,请参阅 lwc-recipes 存储库中的组合基本配方。

您还可以在 lwc.dev 的 Playground 中使用类似的代码,这是 Lightning Web 组件:开源开发人员网站。

在子组件上设置非基元值

传递给组件的非基元值(如对象或数组)是只读的。若要更改数据,请创建要更改的对象的浅拷贝。

让我们看一个父组件,它将对象传递给子组件,然后让子组件更新其值。

<!-- parent.html -->
<template>
  <div>Parent: {serializedObj}</div>
  <c-child obj="{obj}"></c-child>
</template>

在 JavaScript 中设置值。

// parent.js
import { LightningElement } from "lwc";

export default class Parent extends LightningElement {
  obj = {
    msg: "hello",
  };

  get serializedObj() {
    return JSON.stringify(this.obj);
  }
}

反应式对象从父组件传递到子组件。子组件显示带有两个按钮的序列化对象字符串,这两个按钮通过更新原始对象或其浅表副本来更新对象值。obj

<!-- child.html -->
<template>
  <div>Child: {serializedObj}</div>

  <button onclick="{updateOriginal}">Update original</button>
  <button onclick="{updateShallow}">Update shallow</button>
</template>

该组件无法更改对象或数组的内容。当您尝试更新原始对象值时,会引发错误,因为子组件正在尝试改变对象上的属性。Uncaught Error: Invalid mutation: Cannot set "msg" on "[object Object]". "[object Object]" is read-only.

// child.js
import { LightningElement, api } from "lwc";

export default class Child extends LightningElement {
  @api obj;

  get serializedObj() {
    return JSON.stringify(this.obj);
  }

  updateOriginal() {
    this.obj.msg += "!!!"; // throws an invalid mutation error
  }

  updateShallow() {
    this.obj = { ...this.obj, msg: this.obj.msg + "!" };
  }
}

具有来自父组件的引用的非基元值(如 所示)包装在代理中,无法修改。但是,您可以通过创建非基元的浅拷贝来修改其内容。this.obj

当抛出无效的突变错误时,值赋值仅更新子组件上的值。父组件上的值不会更新。this.obj.msgthis.objupdateShallow(){serializedObj}{serializedObj}

数据流

为了防止代码复杂性和意外的副作用,数据应该沿一个方向流动,从父级流向子级。

当组件修饰字段以将其公开为公共属性时,它应该仅在初始化字段时设置该值。初始化字段后,只有所有者组件才能设置该值。组件应将传递给它的值视为只读。@api

若要触发所有者组件提供的属性值的突变,子组件可以向父组件发送事件。如果父级拥有数据,则父级可以更改属性值,该属性值通过单向数据绑定向下传播到子组件。

传递给组件的对象是只读的

传递给组件的非基元值(如对象或数组)是只读的。该组件无法更改对象或数组的内容。如果组件尝试更改内容,您会在浏览器控制台中看到错误:Uncaught Error: Invalid mutation: Cannot set "msg" on "[object Object]". "[object Object]" is read-only.

若要更改数据,请创建要更改的对象的浅拷贝。让我们看一下更新对象值的组件。

<!-- myCmp.html -->
<template>
  <p>{serializedObj}</p>
  <button onclick="{updateShallowCopy}">Update</button>
</template>

创建对象的浅拷贝,该拷贝仅复制顶级属性。不会复制嵌套对象的值。在此示例中,更新为单击按钮时。{"msg":"hello"}{"msg":"hello!"}

// myCmp.js
import { LightningElement, api } from "lwc";

export default class extends LightningElement {
  obj = {
    msg: "hello",
  };

  get serializedObj() {
    return JSON.stringify(this.obj);
  }

  updateShallowCopy() {
    this.obj = { ...this.obj, msg: this.obj.msg + "!" };
  }
}

也可以使用 重新分配对象的值。该组件拥有该字段,并且可以为该字段分配一个新值。相反,如果组件尝试使用 更改对象上的嵌套值,则会在 Web 控制台中收到无效的突变错误。this.obj = { msg: 'My new message' }myCmpobjthis.obj.msg = 'new value'

同样,具有来自父组件的引用的非基元值包装在代理中,并且无法修改。请参见设置子组件的属性。

若要触发所有者组件提供的属性值的突变,组件应向所有者发送事件。

对公共属性使用基元值

建议对属性使用基元数据类型,而不是使用对象数据类型。在更高级别的组件中对复杂的数据结构进行切片,并将基元值传递给组件后代。

最好使用基元值有几个原因。

  • 基元值需要明确定义数据形状的特定属性。接受对象或数组需要文档来指定形状。如果对象形状发生变化,使用者就会中断。@api
  • 标准 HTML 元素仅接受属性的基元值。当标准 HTML 元素需要复杂形状时,它使用子组件。例如,元素使用 和 元素。只能在 HTML 中定义基元类型。例如,在 HTML 中不是值,而是在 Lightning Web 组件中。tabletrtd<table data={...}>

在子组件上调用方法

若要公开公共方法,请使用 .公共方法是组件 API 的一部分。若要在包含层次结构中向下通信,所有者和父组件可以在子组件上调用 JavaScript 方法。@api

注意

若要在包含层次结构中向上通信,请在子组件中触发事件,并在所有者或容器组件中处理该事件。请参阅与事件通信。

查看 lwc-recipes 存储库中的组件(以前命名为 ),该组件在其子组件上调用方法。apiMethodapiFunctionclock

定义方法

此示例通过向组件中添加装饰器来公开属性和 和方法的 getter。包含的父组件可以读取属性并调用方法。下面是 JavaScript 文件。isPlayingplay()pause()c-video-player@apic-video-player

// videoPlayer.js
import { LightningElement, api } from "lwc";

export default class VideoPlayer extends LightningElement {
  @api videoUrl;

  @api
  get isPlaying() {
    const player = this.template.querySelector("video");
    return player !== null && player.paused === false;
  }

  @api
  play() {
    const player = this.template.querySelector("video");
    // the player might not be in the DOM just yet
    if (player) {
      player.play();
    }
  }

  @api
  pause() {
    const player = this.template.querySelector("video");
    if (player) {
      // the player might not be in the DOM just yet
      player.pause();
    }
  }

  // private getter for computed value
  get videoType() {
    return "video/" + this.videoUrl.split(".").pop();
  }
}

videoUrl是公共财产。装饰器可用于在组件上定义公共属性和公共 JavaScript 方法。公共属性是组件公共 API 的另一部分。@api

注意

若要访问模板拥有的元素,代码将使用 template 属性。

现在,让我们看一下定义 video 元素的 HTML 文件。

<!-- videoPlayer.html -->
<template>
  <div class="fancy-border">
    <video autoplay>
      <source src={videoUrl} type={videoType} />
    </video>
  </div>
</template>

在实际组件中,通常具有播放或暂停视频本身的控件。为了说明公共 API 的设计,此示例中控件位于调用公共方法的父组件中。c-video-player

调用方法

该组件包含并具有用于调用 中的 和 方法的按钮。下面是 HTML。c-method-callerc-video-playerplay()pause()c-video-player

<!-- methodCaller.html -->
<template>
  <div>
    <c-video-player video-url={video}></c-video-player>
    <button onclick={handlePlay}>Play</button>
    <button onclick={handlePause}>Pause</button>
  </div>
</template>

单击中的按钮将在我们连接 中的 和 方法后播放或暂停视频。c-method-callerc-video-playerhandlePlayhandlePausec-method-caller

下面是 的 JavaScript 文件。c-method-caller

// methodCaller.js
import { LightningElement } from "lwc";

export default class MethodCaller extends LightningElement {
  video = "https://www.w3schools.com/tags/movie.mp4";

  handlePlay() {
    this.template.querySelector("c-video-player").play();
  }

  handlePause() {
    this.template.querySelector("c-video-player").pause();
  }
}

中的函数调用元素中的方法。 返回 中的元素。该调用可用于访问子组件,以便可以在组件上调用方法。handlePlay()c-method-callerplay()c-video-playerthis.template.querySelector('c-video-player')c-video-playermethodCaller.htmlthis.template.querySelector()

中的函数调用元素中的方法。handlePause()c-method-callerpause()c-video-player

返回值

若要从 JavaScript 方法返回值,请使用该语句。例如,请参见中的方法。returnisPlaying()c-video-player

@api get isPlaying() {
    const player = this.template.querySelector('video');
    return player !== null && player.paused === false;
}

方法参数

若要将数据传递给 JavaScript 方法,请为该方法定义一个或多个参数。例如,您可以定义采用控制视频播放速度的参数的方法。play()speed

@api play(speed) { … }

查询选择器

该方法是一个标准的 DOM API,它返回与选择器匹配的第一个元素。querySelector()

如果要遍历数组,请考虑向元素添加一些其他属性,例如 或 value,并使用它来选择所需的元素。classdata-*

该方法返回一个 DOM 元素数组。querySelectorAll()

注意

不要将 传递给查询方法,例如 。呈现 HTML 模板时,可以将值转换为全局唯一值。如果在 JavaScript 中使用选择器,则它与转换后的 .idquerySelectorididid

有关详细信息,请参阅 developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll 和 developer.mozilla.org/en-US/docs/Web/API/Element/querySelector。

子组件上的 Spread 属性

使用指令将对象中的一组属性传递给子组件。 还使元素能够接受在运行时绑定为属性的对象。lwc:spreadlwc:spread

提示

lwc-recipes 存储库有一个演示指令的组件。apiSpreadlwc:spread

该指令接受一个对象。lwc:spread

<!-- app.html -->
<template>
  <c-child lwc:spread={childProps}></c-child>
</template>

使用具有键值对的对象,其中键是属性名称。

// app.js
import { LightningElement } from "lwc";

export default class extends LightningElement {
  childProps = { name: "James Smith", country: "USA" };
}

在子组件中,使用模板中的属性。

<!-- child.html -->
<template>
  <p>Name: {name}</p>
  <p>Country : {country}</p>
</template>

使用修饰器向父组件公开属性。@api

// child.js
import { LightningElement, api } from "lwc";

export default class Child extends LightningElement {
  @api name;
  @api country;
}

lwc:spread始终最后应用,因此它会覆盖模板中直接声明的任何属性。一个指令只能使用一个实例。lwc:spread

<!-- app.html -->
<template>
  <c-child name="lwc" lwc:spread={childProps}></c-child>
</template>

在此示例中,即使父组件传入,也要传递 。最终,子组件将采用该名称。c-childnameLightning Web Componentsname="lwc""lwc"

// app.js
import { LightningElement } from "lwc";

export default class extends LightningElement {
  childProps = { name: "Lightning Web Components" };
}

lwc:spread不将组件绑定到模板中定义的事件处理程序。例如,可以在对象中传入处理程序作为属性名称。onclick

<!-- app.html -->
<template>
  <c-child lwc:spread={simpleProps}></c-child>
</template>

在这里,作为 .单击 c-child 元素时,将替换为 。c-childnameLWCspreadClick()nameLightning Web Components

// app.js
import { LightningElement } from "lwc";

export default class extends LightningElement {
  simpleProps = { name: "LWC", onclick: this.spreadClick.bind(this) };
  spreadClick() {
    this.simpleProps = { name: "Lightning Web Components" };
  }
}

虽然我们没有包含在 中,但元素包含之前通过对象分配给它的元素。onclickspreadClick()onclicksimpleProps

在子组件上反映 HTML 属性

大多数 HTML 属性都反映为属性。例如,属性反映为属性。classclassName

假设您将属性传递给子组件。

<!-- app.html -->
<template>
  <c-child lwc:spread={spanProps}></c-child>
</template>

因此,该属性会导致元素呈现为 。spanProps<c-child class="spanclass" id="mySpan"></c-child>

// app.js
import { LightningElement } from "lwc";

export default class extends LightningElement {
  spanProps = { className: "spanclass", id: "myspan" };
}

将标记传递到插槽中

将槽添加到组件的 HTML 文件中,以便父组件可以将标记传递到组件中。一个组件可以有零个或多个插槽。

插槽 () 是父组件传递到组件主体中的标记的占位符。<slot></slot>

重要

您无法将 Aura 组件传递到插槽中。如果将 Lightning Web 组件嵌套在 Aura 组件中,则也无法将其传递到插槽中。

未命名插槽

此示例有一个未命名的插槽。未命名的插槽使用该元素作为父组件传递到 主体中的任何标记的占位符。<slot>c-slot-demo

<!-- slotDemo.html -->
<template>
  <h1>Add content to slot</h1>
  <div>
    <slot></slot>
  </div>
</template>

下面是使用 .c-slot-demo

<!-- slotWrapper.html -->
<template>
  <c-slot-demo>
    <p>content from parent</p>
  </c-slot-demo>
</template>

呈现时,未命名的插槽将替换为传递到 正文中的标记。下面是 的渲染输出。c-slot-democ-slot-democ-slot-wrapper

<h1>Add content to slot</h1>
<div>
  <slot>
    <p>content from parent</p>
  </slot>
</div>

如果组件具有多个未命名槽,则传递到组件主体中的标记将插入到所有未命名槽中。但是,一个组件通常有零个或一个未命名的插槽。

命名插槽

此示例组件有两个命名槽和一个未命名槽。

<!-- namedSlots.html -->
<template>
  <p>First Name: <slot name="firstName">Default first name</slot></p>
  <p>Last Name: <slot name="lastName">Default last name</slot></p>
  <p>Description: <slot>Default description</slot></p>
</template>

您可以为 HTML 元素的属性设置动态值。在这里,该元素的属性设置为变量 。slot<span>slotdynamicName

<template>
  <c-item>
    <span slot={dynamicName}></span>
  </c-item>
</template>

传递到属性中的动态值被强制转换为字符串。例如,如果将数字 4 传递给属性,则该属性将转换为字符串 。如果传入无法转换为字符串的数据类型(如 ),则会引发 TypeError。slot"4"Symbol()

此更改不会影响元素的属性。例如,您仍然必须将静态字符串传递到元素的属性中。<slot>name<slot>

<template>
  <slot name="”staticName”"> </slot>
</template>

下面是使用 .c-named-slots

<!-- slotsWrapper.html -->
<template>
  <c-named-slots>
    <span slot="firstName">Willy</span>
    <span slot="lastName">Wonka</span>
    <span>Chocolatier</span>
  </c-named-slots>
</template>

该组件通过:c-slots-wrapper

  • Willy进入插槽firstName
  • Wonka进入插槽lastName
  • Chocolatier进入未命名的插槽

下面是呈现的输出。

<c-named-slots>
  <p>
    First Name:
    <slot name="firstName"><span slot="firstName">Willy</span></slot>
  </p>
  <p>
    Last Name:
    <slot name="lastName"><span slot="lastName">Wonka</span></slot>
  </p>
  <p>
    Description:
    <slot><span>Chocolatier</span></slot>
  </p>
</c-named-slots>

通过插槽传递的访问元素

该元素是组件影子树的一部分。要访问其影子树中的元素,组件将调用 和 .<slot></slot>this.template.querySelector()this.template.querySelectorAll()

但是,传递到插槽中的 DOM 元素不是组件影子树的一部分。要访问通过插槽传递的元素,组件将调用 和 .this.querySelector()this.querySelectorAll()

此示例演示如何将 DOM 元素从子组件的上下文传递给子组件。为 和 提供选择器名称,例如元素。this.querySelector()this.querySelectorAll()

// namedSlots.js
import { LightningElement } from "lwc";

export default class NamedSlots extends LightningElement {
  renderedCallback() {
    this.querySelector("span"); // <span>push the green button.</span>
    this.querySelectorAll("span"); // [<span>push the green button</span>, <span>push the red button</span>]
  }
}

在此示例中,接受元素 .querySelectorspan

注意

不要将 传递给查询方法,例如 。呈现 HTML 模板时,可以将值转换为全局唯一值。如果在 JavaScript 中使用选择器,则它与转换后的 .idquerySelectorididid

有条件地渲染插槽

要有条件地呈现插槽,请使用 、 和/或指令将插槽嵌套在标记中。<template>lwc:iflwc:elselwc:elseif

<template>
  <template lwc:if={expression}>
    <div class="my-class">
      <slot></slot>
    </div>
  </template>
  <template lwc:else>
    <slot></slot>
  </template>
</template>

模板编译器将条件指令视为有效的用例,并且知道不会呈现两次。<slot>

如果使用 legacy 和 指令,编译器会警告你重复的插槽,因为不清楚是否只会呈现一次。例如,getter 每次的返回值都可能不一致。if:trueif:false<slot>expression

在 slotchange 上运行代码

所有元素都支持该事件。当元素中节点的直接子节点发生更改时,将触发该事件。例如,当追加或删除新内容时,会发生这种情况。只有元素支持此事件。<slot>slotchangeslotchange<slot><slot>

元素的子元素中的更改不会触发事件。<slot>slotchange

在此示例中,元素处理事件。<slot>slotchange

<!-- container.html -->
<template>
  <slot onslotchange={handleSlotChange}></slot>
</template>
// container.js
handleSlotChange (e) {
   console.log("New slotted content has been added or removed!");
}

元件被传递到插槽中。c-child

<c-container>
  <c-child></c-child>
  <template lwc:if={addOneMore}>
    <c-child></c-child>
  </template>
</c-container>

如果该标志设置为 True,则控制台将在首次呈现组件时打印。addOneMore

<!-- child.html -->
<template>
  <button onclick={handleClick}>Toggle Footer</button>
  <template lwc:if={showFooter}>
    <footer>Footer content</footer>
  </template>
</template>

即使为 true 并且追加了页脚元素,也不会触发该事件。slotchangeshowFooter

使用插槽与数据组合组件

创建包含其他组件的组件时,请考虑组件层次结构的生命周期,使用带有槽的声明性方法,或数据驱动方法,其中子组件对其父组件的数据更改做出反应。

以声明方式构建组件的常见模式如下所示。

<c-parent>
  <c-custom-child></c-custom-child>
  <c-custom-child></c-custom-child>
</c-parent>

此示例具有使用元素的组件。尽管对使用者来说很方便,但您必须管理通过元素传递的内容的生命周期。c-parentslotslot

有几种方法可以管理传递到槽中的内容。

  • 使用 slotchange 事件。这是推荐的方法。 通过 DOM 向上冒泡,但不会越过阴影边界,从而使包含插槽的父组件能够对其做出反应。slotchange
  • 使用自定义事件将子组件的更改通知父组件,这使您能够将要与之交互的组件上的方法设为私有。我们不再建议使用此方法,因为我们正在逐步迁移组件以使用本机影子,这会强制组件使用该事件。slotchange

将 slotchange 事件与插槽一起使用

使用带有槽的事件,可以管理父组件和子组件之间内容的生命周期。此模式的一个示例是具有子组件的基本组件。slotchangeslotlightning-button-grouplightning-button

<lightning-button-group>
  <lightning-button label="Refresh"></lightning-button>
  <lightning-button label="Edit"></lightning-button>
  <lightning-button label="Save"></lightning-button>
</lightning-button-group>

父组件包含一个带有事件的元素,用于管理传入内容的生命周期。lightning-button-groupslotonslotchange

<!-- buttonGroup.html -->
<template>
  <slot onslotchange={handleSlotChange}></slot>
</template>

注意

基本组件示例仅用于演示目的。基本组件内部结构可能会发生变化。我们记录了对组件功能和行为的更改,但不记录对其内部的更改。有关基本组件的公共属性和方法,请参阅组件参考。

当槽的内容发生更改时,处理程序将处理对槽元素的更新。在这种情况下,父组件根据子组件在组中的显示顺序(第一个、中间、最后一个,或者它是否是组中的唯一按钮)来确定子组件上的 CSS 类。slotchangelightning-button-grouplightning-button

// buttonGroup.js
handleSlotChange(event) {
  const slot = event.target;
  const children = slot.assignedElements() || [];

  // Loop through each child and
  // set the order value based on position in the group
  this.updateGroupOrder(children); }

使用 getter 修改按钮类。

<!-- button.html -->
<template>
  <button class={computedButtonClass} ...>{label}</button>
</template>
// button.js
get computedButtonClass() {
    return classSet('slds-button')
    .add({
        // Other button classes here
        'slds-button_first': this._order === 'first',
        'slds-button_middle': this._order === 'middle',
        'slds-button_last': this._order === 'last'
    })
    .toString();
}

触发寄存器事件,以便组件可以注册组件。lightning-button-grouplightning-button

// button.js
connectedCallback() {
  this._connected = true;
    const privatebuttonregister = new CustomEvent('privatebuttonregister', {
        bubbles: true,
        detail: {
            callbacks: {
                setOrder: this.setOrder.bind(this),
                setDeRegistrationCallback: (deRegistrationCallback) => {
                    this._deRegistrationCallback = deRegistrationCallback;
                }
            }
        }
    });

    this.dispatchEvent(privatebuttonregister);
}

当子组件不再可用时通知父组件。

// button.js
disconnectedCallback() {
  this._connected = false;
  if (this._deRegistrationCallback) {
      this._deRegistrationCallback();
  }
}

使用数据驱动型方法

使用数据驱动的方法,当数据发生变化时,组件会以反应性方式获取更改。

此示例使用数据驱动方法编写子组件。

<template>
  <div class="c-parent">
    <template for:each={itemsData} for:item="itemData">
      <c-child onclick={onItemSelect} id={itemData.id} key={itemData.id}> </c-child>
    </template>
  </div>
</template>

若要传入数据,请使用 JavaScript 对象。子组件仅对来自其父组件的数据更改做出反应。

itemsData = [
    {
        label : 'custom label 1',
        id : 'custom-id-1'
        selected : false
    },
    {
        label : 'custom label 2',
        id : 'custom-id-2'
        selected : false
    }
]

当您有复杂的用例时,建议采用数据驱动的方法。使用数据驱动方法创建的基本组件的一个示例是 lightning-datatable

查看组件依赖关系

使用依赖项树查看器可查看组件使用的自定义组件和 Apex 类。您可以快速查看组件的结构并导航到其依赖项的源。

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

  • 要在详细信息页面上查看 Lightning Web 组件的依赖关系,请单击其名称旁边的 V 形图标,展开 Lightning Web 组件行。
  • 若要查看其中一个依赖项的详细信息,请单击“名称”列中的链接。
查看组件依赖关系树

依赖关系树显示组件的最多三个级别的依赖关系。例如,您可以看到一个组件及其嵌套的子组件、孙子组件和曾孙子组件。若要查看更深层次的依赖项,请单击其中一个嵌套组件的“名称”列中的链接。