组件可访问性

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

我们建议您在创建自定义组件时遵循 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 组件行。
  • 若要查看其中一个依赖项的详细信息,请单击“名称”列中的链接。
查看组件依赖关系树

依赖关系树显示组件的最多三个级别的依赖关系。例如,您可以看到一个组件及其嵌套的子组件、孙子组件和曾孙子组件。若要查看更深层次的依赖项,请单击其中一个嵌套组件的“名称”列中的链接。

Lightning CSS

要为您的组件提供 Lightning Experience 外观,请使用 Lightning Design System。要走自己的路,请编写自己的 CSS。

使用 Lightning Design System 设置组件样式

Salesforce Lightning Design System (SLDS) 是一个 CSS 框架,可提供与 Lightning Experience 一致的外观。使用 SLDS 样式为您的自定义 Lightning Web 组件提供与 Salesforce 一致的 UI,而无需对我们的样式进行逆向工程。最重要的是,它只适用于在 Lightning Experience 和 Salesforce 移动应用程序中运行的 Lightning 组件。

提示

交互式基本组件示例可在组件参考中找到。

使用基本 Lightning 组件

命名空间中的基本组件使用 Lightning Design System 样式。许多基本组件都是根据 SLDS 组件蓝图构建的。蓝图与框架无关,可访问 HTML 和 CSS。一些示例包括 、 和 组件。lightninglightning-accordionlightning-cardlightning-tree

基本组件提供了一个属性,以便您可以将 SLDS 实用程序类或定制类添加到组件的外部元素中。例如,要将边距实用程序类应用于基本组件,请使用 。这里它用于增加按钮之间的边距。classlightning-buttonclass="slds-m-left_medium"

<template>
  <lightning-button label="Submit" title="submit" onclick={handleSubmit}></lightning-button>
  <lightning-button
    variant="brand"
    label="Cancel"
    title="cancel"
    onclick={handleCancel}
    class="slds-m-left_medium"
  ></lightning-button>
</template>

某些组件提供属性,以便您可以轻松地应用设计变体。variant

重要

在设置组件样式时,不要依赖基本 Lightning 组件的内部标记或 CSS 类,因为它们可能会在将来的版本中更改。不支持覆盖基本组件样式,除非使用记录的样式挂钩。请参阅样式组件的反模式。

使用基本 Lightning 组件设计变体和实用程序类

要更改基本 Lightning 组件的样式,请首先检查“组件参考”以查看该组件是否有设计变体。要更改组件的间距,例如对齐方式、填充、边距甚至其排版,请使用 Lightning Design System 实用程序类。

使用样式挂钩覆盖默认样式

如果设计变体和实用程序类不符合您的要求,请考虑设置挂钩样式。有关更多信息,请参阅使用 Lightning Design System 样式挂钩设置组件样式。

应用自定义 CSS类

如果样式挂钩不适用于您的用例,请尝试使用具有全局访问权限的设计令牌。

提示

从 24 年冬季开始,我们建议您尽可能使用全局颜色样式挂钩而不是设计标记,以符合 Web 内容可访问性指南 (WCAG) 2.1 颜色对比度标准。请参阅 Lightning Design System:迁移指南。

如果全局设计标记不起作用,请创建自定义 CSS 类,而不是重写 SLDS 类。创建一个类,而不是以您不拥有的类名为目标,因为该类名可能会更改。例如,不要以 为目标,因为它是基本组件内部标记的一部分。相反,创建一个自定义类并将其传递到 class 属性中。.slds-input

位于 Lightning Experience 或 Salesforce 移动应用程序中的自定义组件可以在没有任何语句或静态资源的情况下使用 Lightning Design System。只需将 Lightning Design System CSS 类分配给 HTML 元素即可。import

/* Do this */
.my-custom-styles {
  padding: var(--lwc-spacingMedium); /* --lwc-spacingMedium: 16px; */
}

/* DON’T DO THIS */
.slds-button {
  padding: 16px;
}

以增量方式应用样式。

/* Do this */
.my-style {
  padding: var(--scoped-spacing);
}

/* DON’T DO THIS */
.slds-scoped-notification {
  padding: var(--scoped-spacing);
}
<div class="slds-scoped-notification my-style"></div>

养成良好的CSS习惯,不要让选择器过载。

/* Don’t do this */
button.button {
  ...;
}
.card > * {
  ...;
}
.button {
  margin-bottom: 18px !important;
}
body.container > div.sidebar > article.card {
  ...;
}

从基础组件创建自定义组件

要构建 Lightning Web 组件,请通过将较小的基本组件组合成一个更复杂的自定义组件来组合它。尝试从按钮和媒体对象等组件构建您的设计。使用实用程序(例如网格和间距类)进行布局。当您向组件添加新功能时,请在 Lightning Design System 中搜索有效的模式,并将这些类应用于模板。lightning

例如,使用实用程序类更改标头大小或向容器添加填充。div

<template>
  <h1 class="slds-text-heading_large">SLDS Example</h1>
  <div class="slds-p-top_medium">
    <p>Hello!</p>
  </div>
</template>

从 Lightning Design 系统蓝图创建自定义组件

如果您找不到适合您的用例的基本组件,请找到最接近的 SLDS 蓝图来帮助您构建组件。请参见从 SLDS 蓝图创建组件。

使用基本 Lightning 组件设计变体

许多基本的 Lightning 组件都有多种设计变体。每个设计变体都赋予组件不同的外观。

基础组件设计变体对应于闪电设计系统 (SLDS) 中的变体。例如,支持将不同文本和背景颜色应用于按钮的设计变体。lightning-button

如果组件具有设计变体,则该组件具有属性。使用该属性选取设计变体。variantvariant

本示例创建一个具有设计变体的按钮,该按钮在按钮上提供蓝色背景色。brand

<lightning-button
  variant="brand"
  label="Submit"
  title="submit form"
  onclick={handleClick}
></lightning-button>

如果未指定 ,或者指定了不支持的值,则使用默认值。对于 ,缺省值为 。variantlightning-buttonneutral

如果您没有看到您喜欢的设计变体,或者组件没有属性,请使用 Lightning Design System 类来实现您想要的样式。variant

例如,要将边距实用程序类应用于基本组件,请使用 。lightning-buttonclass="slds-m-left_medium"

<lightning-button
  variant="brand"
  label="Submit"
  title="submit"
  onclick={handleSubmit}
  class="slds-m-left_medium"
></lightning-button>

还可以将实用工具类应用于嵌套在某些基本组件中的元素,如以下示例所示。基本组件的主体使用填充实用程序类。lightning-cardslds-p-horizontal_small

<lightning-card title="Hello">
  <lightning-button label="New" slot="actions"></lightning-button>
  <p class="slds-p-horizontal_small">Card Body (custom component)</p>
  <p slot="footer">Card Footer</p>
</lightning-card>

如果找不到合适的设计变体或 SLDS 类,请考虑使用样式挂钩。

提示

在“组件参考”中查看基本组件的设计变体和交互式示例。

使用 Lightning Design System 样式钩子设置组件样式

借助 Salesforce Lightning Design System (SLDS) 样式挂钩,可以轻松自定义组件样式并表达您的品牌。样式挂钩提供与 SLDS 组件蓝图和设计变体相对应的 CSS 自定义属性。要自定义 SLDS 样式,请在组件的样式表中声明相应的自定义属性。您可以使用自定义属性来设置基本组件和自定义组件的样式。

提示

要更改基本 Lightning 组件的样式,请首先检查该组件是否有设计变体。要更改组件的间距,例如对齐方式、填充、边距,甚至其排版,请查看 Lightning Design System 实用程序类。如果这些技术不适用于您的用例,请尝试 SLDS 样式挂钩。

样式钩子是 SLDS 样式表中的占位符,例如,它使您能够使用相应的 CSS 自定义属性来自定义样式。var(--slds-c-badge--color-background, #ecebea)--slds-c-badge-color-background

CSS 定制属性对应于 SLDS 组件蓝图及其设计变体。例如,自定义属性与具有品牌变体的按钮蓝图相对应。--slds-c-button-brand-*

组件级与全局颜色样式挂钩

组件级样式挂钩 () 使您能够更新特定组件中的属性。例如,若要将较粗的边框应用于单个按钮,请编辑该按钮的组件挂钩,它不会影响任何其他组件。请参阅 Lightning Design System: Styling Hooks。--slds-c-*

全局颜色样式挂钩 () 是预定义的值,用于在应用中全局使用。例如,若要重新命名应用程序,请使用全局挂钩对正在使用的所有组件进行更改。请参阅 Lightning Design System: Global Color Styling Hooks。--slds-g-*

我们建议您进行的任何颜色自定义都符合 Web 内容辅助功能指南 (WCAG) 2.1 文本和非文本颜色对比度标准。请参阅 Lightning Design System:迁移指南。

组件级样式钩子示例

让我们看一个 Lightning Web 组件,该组件实现了带有品牌变体的 SLDS 按钮蓝图,该蓝图将背景颜色转换为标准 Salesforce 蓝色。

<!-- myButton.html -->
<template>
  <button class="slds-button slds-button_brand">Submit</button></div>
</template>

重要

不要直接覆盖 SLDS 类,如本示例所示。

/* myButton.css */
/* Anti-pattern - Don't do this */
.slds-button_brand {
  background-color: purple;
}

不支持 CSS 覆盖,因为 SLDS 类和基本组件内部结构在将来的发行版中可能会更改。替代可能会过时,不再按预期工作。

请改用 CSS 自定义属性,如下例所示。

要对品牌变体应用不同的背景颜色,请指定相应的 CSS 自定义属性,即 。要使用自定义样式定位组件中的所有元素,请使用 :host 选择器。--slds-c-button-brand-color-background

/* myButton.css */
:host {
  --slds-c-button-brand-color-background: purple;
  --slds-c-button-brand-color-border: purple;
  /* Other CSS custom properties here */
}

或者,您可以使用其他选择器(如元素或自定义类)来限定自定义范围。

基本组件实现 SLDS 组件蓝图并使用 SLDS 样式。要在基本组件(如 )上自定义 SLDS 样式,请使用相应的 CSS 自定义属性。lightning-button

<!-- myBaseButton.html -->
<template>
  <lightning-button variant="brand" label="Submit" onclick={handleClick}></lightning-button>
</template>
/* myBaseButton.css */
:host {
  --slds-c-button-brand-color-background: orange;
  --slds-c-button-brand-color-border: orange;
  /* Other CSS custom properties here */
}

注意

有关样式挂钩的完整列表,请参阅每个组件的蓝图。例如,Buttons 样式挂钩列在 lightningdesignsystem.com/components/buttons/#Styling-Hooks-Overview 中。若要了解有关设置挂钩样式的详细信息,请参阅 lightningdesignsystem.com/platforms/lightning/styling-hooks/。

支持限制

组件蓝图

这些 Lightning Design System 组件蓝图及其 CSS 自定义属性在基础组件上不受支持。

  • 模态 – 模态组件蓝图与基础组件不对应。
  • Toast – 模块不支持自定义属性。lightning/platformShowToastEvent--slds-c-toast-*
  • 工具提示 – 组件不支持自定义属性。lightning-helptext--slds-c-tooltip-*

链接

不支持使用自定义属性设置链接的样式。链接用于基本组件(如 和)和模块中。lightning-breadcrumblightning-formatted-*lightning/navigation表单元素

不支持使用自定义属性设置表单元素的样式。表单元素用于,,,组件中。lightning-input-*lightning-checkbox-grouplightning-radio-grouplightning-textarea

助您构建自己的自定义组件。

首先,我们假设您已经用尽了“组件引用”中可用的基本组件列表,并且您无法使用样式挂钩自定义基本组件上的样式以满足您的要求。

要从 SLDS 蓝图构建组件,请搜索 SLDS 蓝图目录以查找最接近您的组件的设计。如果 SLDS 蓝图具有相应的基本组件,则该蓝图在页面右上角有一个 Lightning 组件按钮 (1)。每个蓝图都提供了其设备支持 (2) 的详细信息 – 自适应、响应式或仅限桌面。自适应蓝图具有非桌面断点的标记,而响应式蓝图则可跨屏幕大小优雅地缩放。

尽管基本组件实现了 SLDS 样式,但并非所有基本组件都支持移动屏幕。基本组件文档中列出了支持。

例如,数据表 SLDS 蓝图是自适应的,但基本组件不支持移动设备。此外,请考虑自适应选项卡 SLDS 蓝图,其中选项卡在移动屏幕上堆叠并充当按钮。相应的不支持此移动行为。lightning-datatablelightning-tabset

从基本变化开始

假设您要创建一个作用域通知,并且您知道没有相应的 Lightning Web 组件。从 SLDS 蓝图上的基本变体开始,然后从那里开始构建。首先,将响应式基本变体标记复制并粘贴到模板中。

<template>
  <div
    class="slds-scoped-notification slds-media slds-media_center slds-scoped-notification_light"
    role="status"
  >
    <div class="slds-media__figure">
      <span class="slds-icon_container slds-icon-utility-info" title="information">
        <svg class="slds-icon slds-icon_small slds-icon-text-default" aria-hidden="true">
          <use xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#info"></use>
        </svg>
        <span class="slds-assistive-text">information</span>
      </span>
    </div>
    <div class="slds-media__body">
      <p>
        It looks as if duplicates exist for this lead.
        <a href="javascript:void(0);">View Duplicates.</a>
      </p>
    </div>
  </div>
</template>

通知在标记中包含一个图标。将图标替换为基本组件。svg<div class="slds-media__figure">lightning-icon

<template>
  <div
    class="slds-scoped-notification slds-media slds-media_center slds-scoped-notification_light"
    role="status"
  >
    <div class="slds-media__figure">
      <lightning-icon
        icon-name="utility:info"
        alternative-text="information"
        size="small"
      ></lightning-icon>
    </div>
    <div class="slds-media__body">
      <p>
        It looks as if duplicates exist for this lead.
        <a href="javascript:void(0);">View Duplicates.</a>
      </p>
    </div>
  </div>
</template>

将消息移动到组件的 JavaScript 文件,并将属性绑定到模板。message

<template>
  <div
    class="slds-scoped-notification slds-media slds-media_center slds-scoped-notification_light"
    role="status"
  >
    <div class="slds-media__figure">
      <lightning-icon
        icon-name="utility:info"
        alternative-text="information"
        size="small"
      ></lightning-icon>
    </div>
    <div class="slds-media__body">
      <p>{message}</p>
    </div>
  </div>
</template>
import { LightningElement, api } from "lwc";

export default class ScopedNotification extends LightningElement {
  @api message = "It looks as if duplicates exist ...";
}

创建样式主题和变体

作用域通知蓝图包括要在组件中捕获的不同主题(浅色和深色)。使用 getter 函数返回所选主题的类名。

import { LightningElement, api } from "lwc";

export default class ScopedNotification extends LightningElement {
  @api message = "Your message here";
  @api theme = "";

  get scopedNotificationClass() {
    let className = "slds-scoped-notification slds-media slds-media_center";
    if (this.theme === "light") {
      className += " slds-scoped-notification_light";
    }
    if (this.theme === "dark") {
      className += " slds-scoped-notification_dark";
    }
    return className;
  }
}

更改主题时,图标样式会更改。深色主题使用反向变体。添加一个 getter 以返回正确的变体。

get iconVariant() {
  let iconVariant = null;

  if (this.theme == 'dark') {
    iconVariant = 'inverse';
  }

  return iconVariant;
}

更新标记,使其根据您使用的主题进行调整。从蓝图中修改此静态标记以使用动态样式。

<div
  class="slds-scoped-notification
    slds-media
    slds-media_center
    slds-scoped-notification_light"
  role="status"
></div>

将模板中的类绑定到 getter。scopedNotificationClass

<div class={scopedNotificationClass} role="status"></div>

同样,将 variant 属性绑定到 getter。lightning-iconiconVariant

<lightning-icon
  icon-name="utility:info"
  alternative-text="information"
  size="small"
  variant={iconVariant}
></lightning-icon>

实现组件

您的组件已准备好开始操作。您可以在其他组件中使用它,也可以将其包含在 Lightning 应用程序生成器的页面上。

此示例使用另一个 Lightning Web 组件中的组件。该示例提供响应式网格布局,并与 Tabs SLDS 蓝图相对应。c-scoped-notificationlightning-layoutlightning-tabset

<lightning-card>
  <lightning-layout multiple-rows>
    <lightning-layout-item size="12" small-device-size="9" padding="around-small">
      <lightning-tabset>
        <lightning-tab label="Item one">
          <div class="slds-m-bottom_small">
            <c-scoped-notification message="Hello notification!" theme="light">
            </c-scoped-notification>
          </div>
        </lightning-tab>
        <lightning-tab label="Item two"> Tab 2 content here </lightning-tab>
      </lightning-tabset>
    </lightning-layout-item>
    <lightning-layout-item size="12" small-device-size="3" padding="around-small">
      <p>Sidebar Content Goes Here</p>
    </lightning-layout-item>
  </lightning-layout>
</lightning-card>

由于我们指定 ,第一个中的内容占据了平板电脑或台式机上屏幕宽度的 75%(480 px 或更响应的断点)。在移动设备上查看时,将内容调整为占据屏幕宽度的 100%。请参阅创建响应式布局。small-device-size="9"lightning-layout-itemsize="12"

使用 Lightning Design System Design 令牌

设计标记是存储视觉设计属性的命名实体,例如边距和间距值、字体大小和系列或颜色的十六进制值。

提示

从 24 年冬季开始,我们建议您尽可能使用全局颜色样式挂钩而不是设计标记,以符合 Web 内容可访问性指南 (WCAG) 2.1 颜色对比度标准。请参阅 Lightning Design System:迁移指南。

在 Lightning Web 组件中使用 CSS 变量来访问 Lightning Design System 设计令牌。Lightning Web 组件可以使用任何标记为“全局访问”的 Lightning 设计系统设计令牌。

重要

如果收到错误:,则可能是您使用的令牌未标记为“全局访问”。使用全局令牌或使用自定义 Aura 设计令牌。No TOKEN named {tokenName} found

要在 Lightning Web 组件的 CSS 中引用设计令牌,请使用前缀并引用 中的令牌名称。--lwc-camelCase

/* myWebComponent.css */
div {
  margin-right: var(--lwc-spacingSmall);
}

注意

在编译时,我们将变量替换为它们的实际值,这意味着在运行时,对变量的引用不起作用。例如,不能使用标准 API 或变量。CSSStyleDeclaration.getPropertyValue()CSSStyleDeclaration.setPropertyValue()

使用自定义 Aura Token

Lightning Web 组件的 CSS 文件可以使用在您的组织中创建或从非托管软件包安装的自定义 Aura 令牌。

将视觉设计的基本价值捕获到自定义 Aura Token中。定义一次令牌值,然后在 Lightning 组件的 CSS 中重复使用它。令牌可以轻松确保您的设计保持一致,甚至更容易随着设计的发展而更新设计。

提示

从 24 年冬季开始,我们建议您尽可能使用全局颜色样式挂钩而不是自定义 Aura 令牌,以符合 Web 内容可访问性指南 (WCAG) 2.1 颜色对比度标准。请参阅 Lightning Design System:迁移指南。

通过创建 Lightning Token捆绑包,在开发者控制台中创建自定义 Aura Token。例如,此令牌捆绑包具有一个名为 的自定义 Aura 令牌。myBackgroundColor

<aura:tokens>
  <aura:token name="myBackgroundColor" value="#f4f6f9" />
</aura:tokens>

自定义 Aura 令牌并不新鲜,但现在您可以使用标准 CSS 函数在 Lightning Web 组件的 CSS 文件中使用它们。在自定义 Aura 令牌之前添加。var()--c-

/* myLightningWebComponent.css */
color: var(--c-myBackgroundColor);

使用 CSS 样式表设置组件样式
若要将一组样式与组件捆绑在一起,请在组件的文件夹中创建一个与组件同名的样式表。样式表将自动应用于组件。如果要使用一个或多个样式表设置组件的样式,请配置 static 属性。stylesheets

一个组件的文件夹中只能有一个样式表。样式表使用标准的 CSS 语法,您可以使用大多数选择器。样式表可以从多个 CSS 模块导入样式规则。

在组件的样式表中定义的样式的范围限定为该组件。此规则允许组件在不同的上下文中重用,而不会丢失其样式。它还可以防止组件的样式覆盖页面其他部分的样式。

深入探讨:Lightning Web 组件中的影子 DOM 和 CSS

此示例演示了在父组件中定义的 CSS 样式如何无法进入子组件。有两个组件,以及 .每个组件都包含一个标记。样式表将样式定义为 。运行代码时,样式仅适用于父级中的标记,而不适用于嵌套子级中的标记。c-parentc-child<h1>parent.cssh1xx-large<h1><h1>

<!-- parent.html -->
<template>
  <h1>To Do List (h1)</h1>
  <c-child></c-child>
</template>
/* parent.css */
h1 {
  font-size: xx-large;
}
<!-- child.html -->
<template>
  <h1>To Do Item (h1)</h1>
</template>
Parent text is extra large. Child text is normal size.

父组件可以设置子组件的样式,但它将其样式设置为单个元素。父母不能接触孩子。让我们添加一个选择器,用于定义子组件周围的边框。c-childparent.css

/* parent.css */
h1 {
  font-size: xx-large;
}

c-child {
  display: block;
  border: 2px solid red;
}
The child component has a red box around it. 

现在,让我们从组件自己的样式表 .c-childchild.css

在添加选择器之前,让我们先删除选择器以删除红色框。从组件及其父级设置组件样式不是一个好的做法,因为它可能会造成混淆并产生意外的结果。这是删除的。child.cssc-childparent.cssparent.cssc-child

/* parent.css */
h1 {
  font-size: xx-large;
}
组件的样式表可以到达并设置其自己的元素的样式,在此示例中为 .c-child

不要使用选择器,而应使用 :host 选择器。c-child

/* child.css */
:host {
  display: block;
  background: yellow;
}
Child component's background is yellow.

通过使用 以 host 元素为目标,我们已将样式应用于 ,from 。:host<c-child>child.css

选择器接受可选的选择器列表。若要匹配,主机元素必须具有与传递的选择器匹配的类。为了了解选择器的工作原理,让我们重写示例代码,使其看起来更像一个真正的应用程序。我们添加一个 to,以便我们可以在待办事项列表上传递项目的名称。:host:host<slot>c-child

<!-- child.html -->
<template>
  <h1>To Do Item</h1>
  <slot></slot>
</template>
让我们在子组件中设置样式,但要让它比我们在父组件中定义的要小一些。我们还将所有列表项设置为浅灰色,但活动项除外,该活动项为浅绿色。h1largexx-largeh1

/* child.css */
h1 {
  font-size: large;
}
:host {
  display: block;
  background: lightgray;
}

:host(.active) {
  background-color: lightgreen;
}
在父级中,让我们添加三个组件并使一个处于活动状态。在实际应用中,活动组件将是所选项目。c-child

<!-- parent.html -->
<template>
  <h1>To Do List</h1>
  <c-child>Buy potatoes</c-child>
  <c-child>Donate to a good cause</c-child>
  <c-child class="active">Plan a party</c-child>
</template>
/* parent.css */

h1 {
  font-size: xx-large;
}
To do list with active to do item in light green. 

CSS 支持和性能影响

CSS 作用域与 CSS 作用域模块级别 1 标准匹配,但有一些例外。

不支持 :host-context() 伪类函数。
不支持 ::p art 伪元素。
CSS 中不支持 ID 选择器。确保值在 HTML 模板中是唯一的。呈现模板时,值可能会转换为全局唯一值。如果在 CSS 中使用 ID 选择器,它将与转换后的 ID 不匹配。idid
LWC 不会将组件的自定义公共属性反映到其相应的 HTML 属性中。若要为子元素提供标识以进行样式设置,请使用属性或属性。classdata-*
作用域 CSS 会影响性能,因此请谨慎使用。每个选择器链都有作用域,传递给的每个复合表达式都分布到多个选择器中。这种转换增加了生成的 CSS 的大小和复杂性。这些增加意味着网络上的位数更多、解析时间更长、样式重新计算时间更长。:host()

为了确保 CSS 封装,每个元素都有一个额外的属性,这也增加了渲染时间。例如,元素具有属性。<c-parent>c-parent_parent-host

<c-parent c-parent_parent-host>
  <h1 c-parent_parent>To Do List</h1>
  <c-child c-parent_parent c-child_child-host>
    <h1 c-child_child>To Do Item</h1>
  </c-child>
  <c-child class="active" c-parent_parent c-child_child-host>
    <h1 c-child_child>To Do Item</h1>
  </c-child>
</c-parent>
将 CSS 样式表分配给组件

若要使用一个或多个样式表自定义组件,请将 static 属性添加到构造函数中。此属性接受样式表数组,其默认值为空数组。您可以设置光 DOM 和阴影 DOM 组件的属性。stylesheetsLightningElementstylesheets

例如,要将 和 样式表注入 ,请在 中配置 static 属性。header-styles.cssbutton-styles.cssmyComponentstylesheetsmyComponent.js

myComponent/
    ├── myComponent.js
    ├── myComponent.html
    ├── myComponent.css
    ├── header-styles.css
    └── button-styles.css
// myComponent.js
import { LightningElement } from 'lwc';
import headerStyles from './header-styles.css';
import buttonStyles from './button-styles.css';

export default Example class extends LightningElement {
    static stylesheets = [
        headerStyles,
        buttonStyles
    ];
}
LWC 引擎注入的第一个样式表是 ,因为它与组件的模板隐式关联。然后,引擎将按照数组中列出的顺序加载与该属性关联的所有样式表。根据 的此配置 ,按以下顺序加载其样式表:、 和 。myComponent.cssstylesheetsstylesheetsmyComponentmyComponent.cssheader-styles.cssbutton-styles.css

在组件类定义期间,LWC 引擎在应用程序的生存期内缓存数组。如果在评估组件代码后进行修改,则不会影响注入的样式表。stylesheetsstylesheets

子类组件不会自动从超类组件继承。如果要从超类组件扩展到子类组件,请使用手动将超类与子类组件的样式表合并。stylesheetsstylesheetssuper.stylesheetsstylesheets

import { LightningElement } from "lwc";
import superclassStylesheet from "./superclass.css";
import subclassStylesheet from "./subclass.css";

class Superclass extends LightningElement {
  static stylesheets = [superclassStylesheet];
}

class Subclass extends Superclass {
  static stylesheets = [...super.stylesheets, subclassStylesheet];
}

为组件创建样式挂钩

若要公开自定义组件的样式挂钩,请使用 CSS 自定义属性。CSS 自定义属性还使代码更易于阅读和更新。

将组件的样式钩子记录为组件 API 的一部分。要更改组件的样式,使用者只需设置样式钩子的值,他们不需要知道您如何实现样式。

使用公共属性设置 LWC 的样式

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

要在组件的样式表中定义 CSS 自定义属性,请在该属性前面加上前缀。要插入该属性的值,请使用 .--var()

:host {
  --important-color: red;
}

.important {
  color: var(--important-color);
}

CSS 自定义属性从其父组件继承其值。继承的属性刺穿了影子 DOM。一些 CSS 属性(如颜色)也是继承的。由于 CSS 自定义属性是继承的,因此使用者可以在 DOM 树中的更高级别设置其值并设置组件的样式。

这些 CSS 自定义属性为两个主题创建样式挂钩:浅色和深色。将回退值作为可选的第二个参数传递给 。var()

.light {
  background-color: var(--light-theme-background-color, lightcyan);
  color: var(--light-theme-text-color, darkblue);
}

.dark {
  background-color: var(--dark-theme-background-color, darkslategray);
  color: var(--dark-theme-text-color, ghostwhite);
}

在 lwc.dev 的 playgrounds 中试验此代码。

样式组件的反模式

以下是开发人员在设置 Lightning Web 组件样式时常犯的一些错误。这些技术不受支持,我们希望看到一些具体的例子对你有所帮助,这样你就可以避免它们。

最常见的反模式是根据 Lightning 基本组件的渲染标记设置样式,并直接覆盖 SLDS 类和样式。还应避免使用精确的类属性字符串匹配来查询元素。

反模式:设置基本组件的 HTML 样式

当你检查你的浏览器控制台并看到基本组件的渲染 HTML 时,很容易制作你的 CSS 来定位你看到的元素和类。但是,Salesforce 不支持基于呈现输出的样式。

Salesforce 需要灵活地重新设计组件的内部结构,以提高性能、增强功能并支持可访问性。因此,不能假定组件标记在版本上呈现相同的版本。如果基于这样的假设创建样式,则样式将来可能会中断。我们记录了对组件功能和行为的更改,但不记录对其内部的更改。

重要

不支持覆盖基本组件样式,除非使用记录的样式挂钩。

这是一个常见的错误,将 SLDS 类替换为您自己的类。

.myCustomClass .slds-class_name;

假设您的公司 Acme Widgets 在您的一个自定义组件中使用了基本组件。你用 Acme 自己的一个 SLDS 类替换一个 SLDS 类,因为它很方便,这是一个常见的错误。组件 CSS 以选择器为目标。lightning-combobox.slds-combobox__input

.acme-combobox_box .slds-combobox__input;

如果 Salesforce 更改为不再使用选择器,并且对 Acme 组件样式造成不利影响,则由您来修复组件的样式。lightning-combobox.slds-combobox__input

请参阅使用 Lightning Design System 设置组件样式,了解支持设置组件样式的方法。

反模式:覆盖 SLDS 类

获取 SLDS 选择器名称并稍作调整很简单,但不受支持。

覆盖 SLDS 选择器的属性时,将来对 SLDS 的更改可能会导致组件样式出现意外结果。Lightning Design System 不断改进,并将所有更改记录在 SLDS 发行说明中。

下面是一些不受支持的替代示例。

/* myButton.css */
/* Anti-pattern - Don't do this */
.slds-button_brand {
  background-color: purple;
}
/* Don’t do this */
.slds-button {
  padding: 16px;
}
/* Don’t do this */
.slds-scoped-notification {
  padding: var(--scoped-spacing);
}

请改用 Style Components with Lightning Design System 中讨论的技术。

反模式:具有精确属性字符串匹配的 querySelector

由于优化缩短了 Lightning Web 组件的渲染时间,静态节点的值和属性可能会使用额外的空格或意外字符进行渲染。更新使用类或样式属性的 JavaScript 代码,因为此更改可能会影响它们。styleclass

以前,您可以包含空格,以便通过使用精确的属性字符串匹配来使用元素的类名来查询元素。

/* Don’t do this */
document.querySelector('[class="highlight yellow"]');

该属性已呈现,如下所示。highlight yellow

<div class="highlight yellow">

但是,该属性现在可以使用额外的空格进行呈现。

<div class=" highlight yellow">

修改 JavaScript 代码以使用忽略空格的选择器。

/* Do this instead */
document.querySelector(".highlight.yellow");

反模式:依赖于生成的 CSS 范围令牌

为了在组件中限定 CSS 的范围,LWC 会自动向 DOM 元素添加属性和类。这些属性和类是内部实现细节,可以随时更改。

<c-cmp c-cmp_cmp-host>
  <span c-cmp_cmp></span>
</c-cmp>

虽然使用生成的类似于组件名称的 CSS 范围令牌很容易,但如果依赖于不拥有的组件的内部结构,则代码可能会中断。由于性能改进,LWC 为 Winter ’24 的 CSS 范围标记生成一个混淆字符串,格式为 ,其中是唯一的字母数字字符串。lwc-hashstringhashstring

<c-cmp lwc-2s44vctlls4-host>
  <span lwc-2s44vctlls4></span>
</c-cmp>

仅当升级到 LWC API 版本 59.0 时,为 CSS 范围令牌生成模糊处理字符串的更改才会影响您的自定义组件。但是,此更改会立即影响 Lightning Experience 和 Experience Builder 站点中 Salesforce 创作的组件,例如在记录页面或列表视图上呈现的组件,以及基本 Lightning 组件。

不要依赖代码中的内部属性和类,或使用 进行测试,因为内部实现可能随时更改。除了使用 ,您还可以使用模板 refs (lwc:ref)。this.template.querySelector()this.template.querySelector()

共享 CSS 样式规则

使用通用 CSS 模块为 Lightning Web 组件创建一致的外观。在 CSS 模块中定义样式,并将该模块导入到要共享样式的组件中。

您可以导入一个或多个 CSS 模块。导入的样式规则将应用于模板,就像未导入的样式规则一样。Lightning Web 组件支持 CSS 级联算法。

/* myComponent.css */

/* Syntax */
@import "namespace/moduleName";

/* Example */
@import "c/cssLibrary";

/* Note: Lightning web components can access modules only from the c and lightning namespaces
under Lightning Locker. If Lightning Web Security is enabled, Lightning web components 
can access modules from other namespaces. */

有关 的更多信息,请参阅 MDN Web 文档:@import。MDN 列出了两个语法语句:和 .LWC 不遵守 .@import@import {url}@import {url list-of-media-queries}{list-of-media-queries}

  1. 创建一个包含 CSS 文件和配置文件的 Lightning Web 组件。文件夹和文件名必须相同。这个组件是你的CSS模块。cssLibrary ├──cssLibrary.css └──cssLibrary.js-meta.xml
  2. 在 CSS 文件中定义样式规则。/* cssLibrary.css */ h1 { font-size: xx-large; }
  3. 配置文件只需要这些标记。<!-- cssLibrary.js-meta.xml --> <?xml version="1.0" encoding="UTF-8" ?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>49.0</apiVersion> <isExposed>false</isExposed> </LightningComponentBundle>
  4. 在 Lightning Web 组件的 CSS 文件中,导入 CSS 模块。myComponent ├──myComponent.html ├──myComponent.js ├──myComponent.js-meta.xml └──myComponent.css/* myComponent.css */ @import "c/cssLibrary"; /* Define other style rules for myComponent here */
  5. 导入的样式规则将应用于模板,就像未导入的样式规则一样。所有样式规则级联。在模板中,标记中的文本使用 中定义的样式。myComponent.html<h1>xx-largecssLibrary.css<!-- myComponent.html --> <template> <h1>Words to the Wise</h1> <p>An apple a day keeps the doctor away.</p> </template>

提示

您可以在 lwc.dev/guide/css 的游乐场玩代码

创建 Lightning Web 组件

Lightning Web 组件是具有自己的 API 的可重用自定义 HTML 元素。

请查看这些部分,了解有关创建自己的 Lightning Web 组件的更多信息。

  • 定义组件
  • HTML 模板
  • CSS的
  • 组成
  • 字段、属性和特性
  • Javascript的
  • 访问 Salesforce 资源
  • 组件可访问性
  • 生命周期钩子

定义组件

呈现 UI 的 Lightning Web 组件必须包含 HTML 文件、JavaScript 文件和元数据配置文件。这些文件必须使用相同的名称,以便框架可以自动连接它们。服务组件(库)必须包含 JavaScript 文件和元数据配置文件。

另见

  • Lightning Web 组件视频库:LWC 剖析

HTML 模板

Lightning Web 组件的强大之处在于模板系统,它使用虚拟 DOM 来智能高效地渲染组件。最好的做法是让 LWC 操作 DOM,而不是编写 JavaScript 来执行此操作。

在 HTML 模板中使用根标记。您还可以使用嵌套标记来处理指令。<template><template>

使用 HTML 模板

使用标准 HTML 和一些 Lightning Web 组件特有的指令编写模板。

当组件呈现时,标记将替换为组件的名称 。<template><namespace-component-name>

<!-- myComponent.html -->
<template>
  <div class="slds-var-m-around_medium">Hello World</div>
</template>

例如,在浏览器控制台中,带有模板的组件呈现为 ,其中 是默认命名空间。myComponent.html<c-my-component>c

<c-my-component>
  <div class="slds-var-m-around_medium">Hello, World!</div>
</c-my-component>

HTML 模板还将数据呈现给 DOM。 使用简单的语法以声明方式将组件的模板绑定到组件的 JavaScript 类中的数据。

嵌套模板

模板可以包含带有指令的嵌套标记。<template>

指令是特殊的 HTML 属性,如 和 ,它赋予您在标记中操作 DOM 的更多权力。lwc:iffor:each

嵌套标记必须包含以下指令之一:、、或 。<template>for:eachiterator:iteratorNamelwc:iflwc:elselwc:elseifif:true|false

注意

嵌套标记不能与其他指令或 HTML 属性一起使用。无效的模板用法将被忽略 – 模板及其子级不会在 DOM 中呈现,并且在加载组件时会返回警告。<template>

例如,不要在嵌套标签上使用该属性,而是在 or 标签上使用该属性。class<template>class<div><span>

提示

如果您使用 VS Code,我们建议您安装 Salesforce 扩展包。此扩展会在您键入时标记格式错误或不正确的代码,这在使用模板指令时很有帮助。

有关根标记或嵌套标记支持哪些指令的详细信息,请参阅 HTML 模板指令。<template>

模板中的数据绑定

将组件模板中的属性绑定到组件的 JavaScript 类中的属性。

在模板中,用不带空格的大括号将属性括起来:.若要计算属性的值,请在 JavaScript 类 property 中使用 JavaScript getter。在模板中,属性可以是 JavaScript 标识符(例如,),也可以是从对象 () 访问属性的点表示法。LWC 不允许使用像 这样的计算表达式。{property}get(){}personperson.firstNameperson[2].name['John']

模板中使用的属性应包含基元值,但在 for:each 或迭代器指令中使用时除外。

示例:将组件模板属性绑定到 JavaScript 属性

若要在代码编辑器中继续操作,请打开 github.com/trailheadapps/lwc-recipes 存储库中的 、 和组件。您也可以将代码复制到 webcomponents.dev/create/lwc。hellohelloBindinghelloExpressions

下面是数据绑定的最简单示例。模板中的属性绑定到 JavaScript 类中的属性。greetinggreeting

<!-- hello.html -->
<template> Hello, {greeting}! </template>
// hello.js
import { LightningElement } from "lwc";

export default class Hello extends LightningElement {
  greeting = "World";
}

中的属性必须是有效的 JavaScript 标识符或成员表达式。例如,和 都有效。不要在属性周围添加空格,例如,不是有效的 HTML。{ }{data}{data.name}{ data }

此组件没有硬编码字符串,而是具有一个输入字段,该字段要求提供要问候的名称。

该字段使用该属性来侦听其值的更改。当值更改时,将执行 JavaScript 文件中的函数。请注意,要将函数绑定到模板,我们使用相同的语法,lightning-inputonchangehandleChangehandleChange{handleChange}

<!-- helloBinding.html -->
<template>
  <p>Hello, {greeting}!</p>
  <lightning-input label="Name" value={greeting} onchange={handleChange}></lightning-input>
</template>

我们在另一个主题中深入讨论事件处理,但请注意,在 JavaScript 类中传递了一个对象。该对象包含有关更改事件的信息。该组件使用该对象来获取用户在输入字段中输入的值。handleChangeeventeventevent

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

export default class HelloBinding extends LightningElement {
  greeting = "World";

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

这些代码大部分是标准的 HTML 和 JavaScript。

注意

当组件重新呈现时,将重新计算模板中使用的表达式。

使用 getter 代替表达式

若要计算属性的值,请使用 JavaScript getter。例如,若要将名称转换为所有大写字母,请在 JavaScript 类中使用 getter 函数,而不是模板中的表达式。

getter 比表达式强大得多,因为它们是 JavaScript 函数。Getter 还支持单元测试,从而减少错误并增加乐趣。

定义一个 getter 来计算 JavaScript 类中的值。

get propertyName() { ... }

从模板访问 getter。

{
  propertyName;
}

在此示例中,用户输入其名字和姓氏。JavaScript getter 计算一个新值,模板呈现它:DEANNA LIuppercasedFullName

模板中的属性绑定到 JavaScript 类中的 getter。uppercasedFullNameget uppercasedFullName()

<!-- helloExpressions.html -->
<template>
  <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>
</template>

该函数将 和 属性设置为用户输入的值。getter 将名称组合并大写。handleChangefirstNamelastNameuppercasedFullName()

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

export default class HelloExpressions 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}`.toUpperCase();
  }
}

提示

所有字段都是反应式的。如果字段在模板中使用,或间接用于模板中使用的属性的 getter 中,则当属性的值更改时,组件将重新呈现。在此示例中,when 和 change 会重新呈现模板,因为它们在 getter 中使用,并且该属性在模板中使用。请参阅字段、对象和数组的反应性。firstName lastName uppercasedFullName

有条件地呈现 DOM 元素

若要有条件地呈现 HTML,请将 and 指令添加到包含条件内容的嵌套标记中。lwc:if|elseif={property}lwc:else<template>

提示

指令是特殊的 HTML 属性。Lightning Web 组件编程模型具有一些自定义指令,允许您使用标记来操作 DOM。

这些指令绑定到模板,根据数据是真实值还是虚假值来删除和插入 DOM 元素。lwc:if|elseif{property}

注意

不再建议使用旧指令和指令,因为我们打算在将来弃用和删除这些指令。我们建议您将其用条件指令替换为条件指令,以使您的代码面向未来。if:trueif:falselwc:if|elseif|else

此示例有两个属性和 。property1property2

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

这三个语句中只有一个呈现:

  • Statement1如果为 true,则呈现。property1
  • Statement2如果为 false,则呈现 true。property1property2
  • Statement3呈现 if 和 are false。property1property2

尽管该示例使用所有三个指令,并且是可选的。lwc:elseiflwc:else

让我们看另一个例子。此模板包含一个标记为“显示详细信息”的复选框。当用户选中或取消选中该复选框时,该函数将设置属性的值。如果属性为 ,则该指令将呈现嵌套模板,该模板显示 These are the details!handleChangeareDetailsVisibleareDetailsVisibletruelwc:if

<!-- helloConditionalRendering.html -->
<template>
  <lightning-card title="HelloConditionalRendering" icon-name="custom:custom14">
    <div class="slds-m-around_medium">
      <lightning-input
        type="checkbox"
        label="Show details"
        onchange={handleChange}
      ></lightning-input>
      <template lwc:if={areDetailsVisible}>
        <div class="slds-m-vertical_medium">These are the details!</div>
      </template>
    </div>
  </lightning-card>
</template>

请注意,JavaScript 不会操作 DOM,它只是更改属性的值。

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

export default class HelloConditionalRendering extends LightningElement {
  areDetailsVisible = false;

  handleChange(event) {
    this.areDetailsVisible = event.target.checked;
  }
}

注意

若要在标记中切换布尔属性的值,请将该值默认为 。请参见布尔属性。false

提示

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

渲染列表

若要呈现项列表,请使用 directive 或 the directive 循环访问数组。将该指令添加到包含要重复的 HTML 元素的嵌套标记中。for:eachiterator<template>

该指令具有 和 属性,可用于将特殊行为应用于数组中的第一个和最后一个项目。iteratorfirstlast

无论使用哪个指令,都必须使用指令为每个项目分配唯一的 ID。当列表更改时,框架使用 仅重新呈现已更改的项。模板中的 用于性能优化,在运行时不会反映在 DOM 中。keykeykey

提示

若要在代码编辑器中继续操作,请打开 github.com/trailheadapps/lwc-recipes 存储库中的 和组件。还可以将代码复制到组件 IDE。helloForEachhelloIterator

适合:每个

使用该指令时,use 用于访问当前项目。此示例不使用它,但要访问当前项的索引,请使用 .for:eachfor:item="currentItem"for:index="index"

若要将键分配给嵌套模板中的第一个元素,请使用该指令。key={uniqueId}

此示例循环访问一个名为 的数组,该数组在组件的 JavaScript 类中定义。contacts

<!-- helloForEach.html -->
<template>
  <lightning-card title="HelloForEach" icon-name="custom:custom14">
    <ul class="slds-m-around_medium">
      <template for:each={contacts} for:item="contact">
        <li key={contact.Id}>{contact.Name}, {contact.Title}</li>
      </template>
    </ul>
  </lightning-card>
</template>
// helloForEach.js
import { LightningElement } from "lwc";

export default class HelloForEach extends LightningElement {
  contacts = [
    {
      Id: 1,
      Name: "Amy Taylor",
      Title: "VP of Engineering",
    },
    {
      Id: 2,
      Name: "Michael Jones",
      Title: "VP of Sales",
    },
    {
      Id: 3,
      Name: "Jennifer Wu",
      Title: "CEO",
    },
  ];
}

重要

列表中的每个项目都必须具有 .当列表更改时,框架使用 来标识每个项目,以便它只能重新呈现已更改的项目。必须是字符串或数字,不能是对象。不能用作 的值。为传入数据集分配唯一键。若要将新项添加到数据集,请使用私有属性来跟踪和生成键。keykeykeyindexkey

迭 代

若要将特殊行为应用于列表中的第一项或最后一项,请使用指令 。在标记上使用指令。iteratoriterator:iteratorName={array}iteratortemplate

使用 iteratorName 访问以下属性:

  • value– 列表中项目的值。使用此属性可访问数组的属性。例如。{iteratorName}.value.{propertyName}
  • index– 列表中项目的索引。
  • first– 一个布尔值,指示此项目是否为列表中的第一项。
  • last– 一个布尔值,指示此项目是否为列表中的最后一项。

此示例代码使用与上一个示例相同的数组。若要将特殊呈现应用于列表中的第一项和最后一项,代码将 and 属性与指令一起使用。firstlastlwc:if

如果项目位于列表中的第一个,则标记将使用 CSS 类中定义的样式进行呈现。如果项目在列表中排在最后,则标记将使用 CSS 类中定义的样式进行呈现。<div>list-first<div>list-last

<template>
  <lightning-card title="HelloIterator" icon-name="custom:custom14">
    <ul class="slds-m-around_medium">
      <template iterator:it={contacts}>
        <li key={it.value.Id}>
          <div lwc:if={it.first} class="list-first"></div>
          {it.value.Name}, {it.value.Title}
          <div lwc:if={it.last} class="list-last"></div>
        </li>
      </template>
    </ul>
  </lightning-card>
</template>
.list-first {
  border-top: 1px solid black;
  padding-top: 5px;
}

.list-last {
  border-bottom: 1px solid black;
  padding-bottom: 5px;
}

lwc-recipes 存储库中的组件使用基本组件来显示选择列表值列表。wireGetPicklistValues lightning-input

渲染多个模板

您可能希望呈现具有多种外观的组件,但不希望将 HTML 混合在一个文件中。例如,组件的一个版本是纯文本,另一个版本显示图像和额外的文本。在这种情况下,您可以导入多个 HTML 模板并编写有条件地呈现这些模板的业务逻辑。此模式类似于某些 JavaScript 框架中使用的代码拆分。

注意

尽管一个组件可以呈现多个模板,但我们建议改用指令有条件地呈现嵌套模板。lwc:if|elseif|else

在组件包中创建多个 HTML 文件。将它们全部导入,并在方法中添加条件,以根据组件的状态返回正确的模板。该方法返回的值必须是模板引用,这是从 HTML 文件导入的默认导出。render()render()

在此示例中,模板引用是 和 。templateOnetemplateTwo

// miscMultipleTemplates.js

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

export default class MiscMultipleTemplates extends LightningElement {
  showTemplateOne = true;

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

  switchTemplate() {
    this.showTemplateOne = !this.showTemplateOne;
  }
}
<!-- templateOne.html -->
<template>
  <lightning-card title="Template One">
    <div>This is template one.</div>
    <p class="margin-vertical-small">
      <lightning-button label="Switch Templates" onclick={switchTemplate}> </lightning-button>
    </p>
  </lightning-card>
</template>
<!-- templateTwo.html -->
<template>
  <lightning-card title="Template Two">
    <div>This is template two.</div>
    <p class="margin-vertical-small">
      <lightning-button label="Switch Templates" onclick={switchTemplate}> </lightning-button>
    </p>
  </lightning-card>
</template>

若要从额外模板引用 CSS,CSS 文件名必须与额外模板的文件名匹配。例如,只能从 引用 CSS。它不能从 或 引用 CSS。templateTwo.htmltemplateTwo.cssmiscMultipleTemplates.csstemplateOne.css

MiscMultipleTemplates
   ├──miscMultipleTemplates.js
   ├──miscMultipleTemplates.js-meta.xml
   ├──templateOne.html
   ├──templateOne.css
   ├──templateTwo.html
   └──templateTwo.css

如果包含具有匹配名称的模板,则默认方法将返回该模板,除非包含上一示例中讨论的替代。miscMultipleTemplates.htmlrender()

提示

查看 lwc-recipes 存储库中的组件。miscMultipleTemplates

Lightning Web 组件入门

使用 Lightning Web 组件 (LWC) 框架在 Salesforce 平台上构建自定义用户界面、Web 和移动应用程序以及数字体验。Lightning Web 组件是使用 HTML 和 JavaScript 构建的自定义 HTML 元素。

Salesforce 提供基于 Lightning Design System 构建的基本 Lightning Web 组件,用作自定义体验的构建块。使用基本的 Lightning Web 组件为您的用户提供一致的外观,并简化您的开发体验。Salesforce Lightning Experience 基于 Lightning Design System 和基础 Lightning 组件构建。

提示

首先,请选择适合您的路径。

  • 开始编码:创建您的第一个组件
  • 设置开发环境
  • 探索 Trailhead 和示例代码
  • 了解如何使用 Salesforce 数据
  • 了解如何自定义 Salesforce 功能

编写标准的 JavaScript 和 HTML

Lightning Web 组件使用核心 Web 组件标准,并且仅提供在 Salesforce 支持的浏览器中正常运行所需的内容。由于 Lightning Web 组件基于在浏览器中本地运行的代码构建,因此它是轻量级的,可提供卓越的性能。您编写的大多数代码都是标准的 JavaScript 和 HTML。

Salesforce 致力于开发开放的 Web 标准,并且是万维网联盟 (W3C) 的成员。Salesforce 开发人员是 Ecma 国际技术委员会 39 (TC39) 的贡献成员,该委员会是发展 JavaScript 的委员会。

Lightning Web Components 也是开源的。

向后兼容性

您可以使用两种编程模型构建 Lightning 组件:Lightning Web 组件和原始模型 Aura 组件。Lightning Web 组件和 Aura 组件可以在一个页面上共存和互操作。对于管理员和最终用户来说,它们都显示为 Lightning 组件。

基础 Lightning 组件可作为 Lightning Web 组件和 Aura 组件使用。组件参考包括两者的文档、规格和示例。请参阅基本组件:Aura 与 Lightning Web 组件,了解它们之间的差异。

当您在 LWC 和 Aura 之间进行选择时,请选择 LWC。了解如何选择 Lightning Web 组件或 Aura。

Lightning 组件库

Lightning 组件库包括 Lightning Web Security 和 Lightning Locker 的组件参考信息和工具。

您可以在两个位置找到组件库:公共站点和链接到您的 Salesforce 组织的经过身份验证的站点。在经过身份验证的站点中,“组件库”的“组件引用”部分具有一些附加功能。公共组件库

在不登录 Salesforce 的情况下查看此站点。“组件参考”包括基本组件的文档和参考信息。

https://developer.salesforce.com/docs/component-library组织的组件库

通过登录您的 Salesforce 组织并导航到 来查看此站点。或者,单击公共站点右上角的“链接到您的组织”。https://<myDomainName>.lightning.force.com/docs/component-library

经过身份验证的站点具有组件引用的更多功能。

  • 查看您的组织独有的 Lightning 组件。
  • 查看安装在托管软件包中的 Lightning 组件。您可以进行筛选以查看您的组织拥有的组件或安装在软件包中的组件。

注意

《开发人员指南》在组件库中不再可用。访问会将您重定向到 。https://developer.salesforce.com/docs/component-library/documentation/en/lwchttps://developer.salesforce.com/docs/platform/lwc/guide

组件参考

在组件参考中,您可以找到有关每个基本 Lightning 组件的详细信息。它记录了 Lightning Web 组件和 Aura 编程模型的全套基本组件。

以下是有关组件参考的一些亮点。过滤器组件

在“概述”页上,筛选以重点关注你感兴趣的组件。例如,若要仅查看与导航相关的组件,请选择“筛选器”|”分类 |导航。查看目标

选择组件时,顶部的“目标”面板会显示可以使用该组件的位置。目标可以包括 Salesforce 应用程序、Lightning Experience、Experience Builder Sites、Salesforce 移动应用程序、独立 Lightning 应用程序等。在组件之间切换

通过单击“以 Aura 组件形式查看”和“以 Lightning Web 组件形式查看”按钮,在组件的版本之间切换。查看示例、文档和规格页面

大多数组件都有“示例”、“文档”和“规范”页面。某些组件(如 和)需要 Salesforce 数据。它们不能在组织外部使用,也没有示例页面。lightning-record-formlightning-input-field修改交互式示例

示例页包含交互式代码示例,用于演示组件的外观和行为。您可以查看正在运行的代码。对于 Lightning Web 组件,您还可以在 Lightning Mini Playground 中编辑示例代码,并立即在交互式示例中查看您的更改。但是,示例中使用的组件不是最新版本。请参阅组件参考示例。

组件引用的已知问题

以下是组件引用的一些已知问题。示例使用旧版本的基本 Lightning 组件和 Salesforce Lightning Design System (SLDS)

由于技术限制,示例的运行时环境不使用最新版本的组件和 SLDS。有关详细信息,请参阅组件参考示例。公共组件引用与经过身份验证的组件引用中的非闪电命名空间差异

在公共站点中,非 lightning 命名空间的组件参考可能包含过时的内容。对于非闪电组件(如 或),请验证 中的规格和文档。wave:waveDashboardforce:createRecordhttps://<myDomainName>.lightning.force.com/docs/component-library不支持在您的组织中开发的 Lightning Web 组件的文档

“组件参考”不显示自定义 Lightning Web 组件的文档规格面板缺少内容

模块不会在“规范”页上显示说明或方法。说明和方法位于文档页面上。模块包括:

  • lightning/empApi
  • lightning/flowSupport
  • lightning/messageService
  • lightning/navigation

某些具有默认值的属性不会在“默认”列中显示值。有关默认值,请参阅“说明”列。

自定义事件不会显示在“规范”页面中。有关此信息,请参阅组件的文档页面。不支持版本控制和本地化

组件参考不像其他 Salesforce 开发人员文档那样提供版本或语言选择器。它仅显示与当前版本相对应的文档,并且内容仅提供英文版本。

组件参考示例

“示例”选项卡显示在页面上的运行时环境中呈现的基本 Lightning 组件示例代码。

要试验 Lightning Web 组件示例,请修改代码并单击运行。Aura 组件示例不可编辑,但可以渲染,您可以与它们交互。

由于我们正在努力解决的技术限制,运行时环境不使用最新版本的基本 Lightning 组件。因此,组件更改(如最近版本中记录的新属性)不会反映在示例中,如果将它们添加到示例中,则不起作用。

运行时环境也跟不上 SLDS 版本。我们在 See Improved Color Contrast in UI Elements Winter ’24 发行说明中宣布的 Web 内容辅助功能指南 (WCAG) 颜色更新未反映在示例中。但是,您在自定义组件中使用并在 Salesforce 组织中运行的基本 Lightning 组件确实包含基本组件和 SLDS 的最新功能。

Lightning Web Security 控制台和 LWS 失真查看器工具

Lightning Web Security 控制台和 LWS 失真查看器可帮助您开发与 Lightning Web Security 一起运行的安全 JavaScript 代码。

有关使用这些工具的更多信息,请参阅在 Lightning Web Security 控制台中评估 JavaScript 和在 LWS 失真查看器中查找失真详细信息。

有关 Lightning Web Security 及其与 Lightning Locker 的比较的更多信息,请参阅 Lightning Web Security。

Locker 控制台和 Locker API 查看器工具

Locker 控制台和 Locker API 查看器可帮助您开发与 Lightning Locker 兼容并高效运行的安全 JavaScript 代码。

有关使用这些工具的更多信息,请参阅《Lightning Aura 组件开发人员指南》中的 Lightning Locker 工具。

有关 Lightning Locker 和 Lightning Web 组件的更多信息,请参阅 Lightning Locker 的安全性。

Lightning Web 组件发行说明

使用 Salesforce 发行说明了解 Lightning Web 组件的最新更新和更改。

有关影响 Lightning Web 组件的更新和更改,请参阅 Salesforce 发行说明中的 Lightning 组件。

有关新的和更改的组件和模块,请参阅 Salesforce 发行说明中的 Lightning 组件:新增和更改的项目。

开始编码

编写第一个 Lightning Web 组件代码的最快方法是使用实时代码环境。

要开始使用简单的组件,我们建议使用 LWC.studio 或 StackBlitz。或者,使用 Salesforce DX 工具将 LWC 代码推送到您的组织。

注意

StackBlitz 和 LWC.studio 是第三方产品,受其自身条款和条件的约束。Salesforce 对这些网站上提供的内容、服务或付费选项概不负责。您使用这些网站和平台的风险由您自行承担,并受任何适用的附加条款的约束,例如第三方的服务条款或隐私政策。

特定于基本组件的实时示例也可在 studio.webcomponents.dev/workspace/lwc 和组件参考中找到。

创建您的第一个组件

让我们使用 LWC.studio 中的第三方 IDE 创建一个 Hello World Lightning Web 组件。

  1. 在浏览器中,转到 app.lwc.studio。使用您的 GitHub 帐户登录。
  2. 登录后,单击“+新建”按钮。
  3. 通过单击文件夹旁边显示的“新建文件”图标来创建文件,并将其命名为 。将此代码复制到代码编辑器中。srchelloWorld.html
  4. 创建一个文件并将其命名为 。将此代码复制到代码编辑器中。helloWorld.js
  5. 若要查看组件,请将其添加到文件中最后一个结束标记之前。app.html</div>骆驼大小写组件名称 () 映射到 HTML () 中的烤肉串大小写。默认命名空间是 ,因此完整组件 HTML 标记是 。另请注意,组件必须使用结束标记。若要引用自己的组件,请始终使用默认命名空间 .无论代码在何处运行,都可以使用:在有或没有命名空间的组织中,在托管或非托管包中。helloWorldhello-worldc<c-hello-world>cc
  6. 要保存所有更改,请单击代码编辑器右上角的“保存”图标。“故事”面板将刷新以显示“Hello, World!”文本。
  7. 让我们更进一步,向组件添加一个公共属性。在 中,导入 ,并使用它来装饰字段。c-hello-worldhelloWorld.jsapifirstName
  8. 由于公开了公共属性,因此使用该属性的组件可以设置该属性。在我们的示例中,该组件设置了属性。c-hello-worldc-hello-worldappfirstName在 中,向标记添加属性。骆驼大小写 JavaScript 属性映射到 HTML 中的 kebab 大小写。app.htmlfirst-name<c-hello-world>firstNamefirst-name
  9. 现在,您的组件是可重用的。例如,您可以添加多个组件,并将每个组件设置为不同的值。c-hello-worldapp.html

若要继续编码,请跳转到模板中的数据绑定。玩得愉快!

Lightning Web 组件:开源

Lightning Web 组件是开源的,使您能够探索源代码,自定义行为以满足您的需求,并在任何平台(而不仅仅是 Salesforce)上构建企业级 Web 组件。

过去,您必须使用不同的框架来构建托管在 Salesforce 外部的应用程序的不同方面。例如,您使用 Aura 在 Salesforce 上构建应用程序面向员工的一面。您使用 React、Angular 或 Vue 构建了应用程序面向客户的端,并将其部署在 Heroku 或其他平台上。现在,您可以使用 Lightning Web 组件来构建应用程序的两端。好处是显着的。您现在可以学习一个框架而不是多个框架。您可以在应用之间共享代码。而且,您正在使用基于 Web 标准并基于最新模式和最佳实践构建的尖端框架。

  • GitHub 存储库 – github.com/salesforce/lwc
  • 开源开发人员站点 – lwc.dev

《Lightning Web 组件开发人员指南》(您现在正在阅读)介绍了如何开发在 Lightning 平台上运行的 Lightning Web 组件,以及如何使用 Lightning Data Service、Lightning Locker 和 Lightning Out 等平台功能。

与 Lightning Web 组件开源一样,Lightning Out 允许您在 Lightning Platform 上运行 Lightning Web 组件。如果您的组件需要访问 Salesforce 数据,请使用 Lightning Out。如果您的组件不需要 Salesforce 数据或任何其他 Salesforce 功能,请使用 Lightning Web Components 开源进行开发。

LWC 开源和 Salesforce 平台上的 LWC 之间的差异

重要

开源存储库中的代码领先于 Lightning 平台上可用的代码。开源存储库中发布的大多数功能最终都会在 Lightning 平台上发布。

Salesforce 平台上的 Lightning Web 组件是 Lightning Web 组件:开源的托管版本。在 Salesforce 平台上工作时,您可以下载 LWC,按照自己的方式进行配置,在任何托管环境中部署应用程序,并选择何时升级。在 Salesforce 平台上工作时,Salesforce 会为所有客户管理 LWC 的配置、部署和升级。

LWC OSS 和平台上的 LWC 有不同的发布时间表。LWC 工程团队通常每周发布一次 LWC OSS,而 Salesforce 平台每年发布三次。由于这种差异,Salesforce 平台上的 LWC 版本比 LWC 的开源版本晚 3-6 个月。

开源 LWC 引擎与 Salesforce 平台上的 LWC 引擎相同。区别在于引擎在编译器级别和运行时的配置方式。

Salesforce 平台上的编译时差异

  • 在 Salesforce 平台上使用实验性 LWC API 受到限制,并引发 linting 错误。
    • 实验性模板 API:lwc:dynamic(已弃用并替换为 <lwc:component lwc:is={componentConstructor}>)
    • 实验性 JavaScript API:、、、、和buildCustomElementConstructorcreateElementswapComponentswapTemplateswapStyle
  • 将 LWC 模块推送到 Salesforce 平台时,将应用以下 linting 规则。
    • 强制执行 @salesforce/eslint-config-lwc/base 中的所有规则。ESLint 忽略这些规则的内联配置。
    • 禁止动态导入 ()。import('c/foo')
    • 禁止从 LWC 模块访问 Aura(例如,)。$A
  • @salesforce/*导入根据组织的元数据(例如,Apex 方法、标签和架构)进行验证。

Salesforce 平台上的运行时差异

  • 组件在启用的情况下运行。此模式允许支持 IE11,IE11 不实现影子 DOM。作为副作用,在页面级别注入的所有样式(例如,using from )都会泄漏到组件中。@lwc/synthetic-shadowloadStylelightning/platformResourceLoader
  • linkSVG 元素上的属性由 Locker 清理,以防止潜在的恶意脚本注入。xlink:href<use>
  • LWC 模块在 Lightning Locker 或 Lightning Web Security 中进行评估。除了应用于标准 Web 平台 API 的限制之外,Locker 和 Lightning Web Security 还为 LWC 组件添加了一些限制:
    • this.template.host总是返回 。null
    • 访问从模板检索到的组件时,始终返回 。shadowRootnull

API 版本控制

从 24 年冬季开始,LWC 支持自定义组件的版本控制。当组件指定版本时,该组件将取决于 Salesforce 版本的版本。组件的每个 HTML、CSS 和 JS 文件对应一个 API 版本。API 版本告诉 LWC 框架的行为与与该组件的 API 版本相对应的 Salesforce 版本的行为相同。

提示

虽然 Salesforce 平台上的 LWC 使用与 LWC 开源相同的代码库,但 Salesforce 平台上的 LWC 版本比开源版本晚 3-6 个月。LWC 开源遵循语义版本控制 (semver) 特征。请参阅 LWC 开源:LWC 版本控制。

升级组件的 API 版本

截至 24 年冬季,58.0 及更早版本的所有 API 版本对应于 58.0(23 年夏季)。如果将 API 版本设置为低于 58.0 的值,则 LWC 默认使用版本 58.0。

若要应用与更高 API 版本相关的 bug 修复和新功能,请升级组件的 API 版本。

注意

API 版本中的警告可能会在下一个 API 版本中成为错误。我们建议您测试每个版本,一次升级一个版本。

要升级组件 API 版本,请执行以下操作:

  1. 发布组件。修复 SFDX 控制台中的任何警告。
  2. 在沙盒中运行组件。修复浏览器 DevTools 控制台中的任何警告。
  3. 将组件的 *.js-meta.xml 文件中的 apiVersion 递增到下一个版本。
  4. 再次发布组件,并验证是否不再看到任何警告。

例如,如果将 apiVersion 值指定为 58.0,则组件的行为将继续与 API 版本 58.0 (Summer ’23) 中的行为相同。

注意

无论如何,自定义组件始终使用最新版本的 Lightning 数据服务和基本 Lightning 组件。apiVersion

组件级中断性变更

组件级重大更改适用于 Lightning Experience 和 Experience Builder 站点的自定义组件。要在组件中实施这些更改,请将 .apiVersion59.0

  • 小写所有 CSS 范围标记
  • 抛出错误而不是 parse5 HTML 错误的警告

组件级功能

在 Winter ’24 中,没有任何与值对应的新 LWC 要素。要查看最新 Salesforce 版本中 LWC 的更改,请参阅发行说明。除非另有说明,否则更改适用于所有值。apiVersionapiVersion

Salesforce 版本和 LWC API 版本

以下是 Salesforce 版本映射到 LWC API 版本的方式。

版本控制注意事项

使用 LWC API 版本控制时,请考虑以下准则。

  • 如果使用的版本早于已知最早的 LWC API 版本,则 LWC 将使用最早的已知版本。例如,如果已知最早的版本是 58.0,而您提供的是 57.0,则 LWC 将使用 58.0。apiVersion
  • 如果最新的 LWC 版本是 60.0,而您提供的是 61.0,则 LWC 将使用 60.0。
  • LWC API 版本控制不适用于 Aura 组件和 LWR。
  • LWC API 版本控制仅适用于 Lightning Experience 中包含该文件的自定义组件。*.js-meta.xml
  • LWR 中的自定义组件在未在 Lightning Experience 或 Experience Cloud 中运行时使用最新的 LWC API 版本。
  • 同一页面上或同一托管包中的组件可以具有不同的 API 版本。

支持的浏览器

Lightning Web 组件支持与 Lightning Experience 相同的浏览器。请参阅 Lightning Experience 支持的浏览器。

Salesforce 于 2023 年 1 月 1 日终止了 Lightning Experience 和 Salesforce Classic 中对 Internet Explorer 11 的支持。在 Winter ’24 之后,您将无法再使用 Internet Explorer 11 和其他旧版浏览器来查看或访问 Lightning Experience。如果您的代码使用了不受支持的浏览器之一,则可以将其删除。我们建议在受支持的浏览器的最新版本中访问 Lightning Experience。

浏览器扩展

虽然有许多第三方浏览器扩展可以个性化和增强您的 Salesforce 体验,但我们建议您查看 AppExchange 以获取来自 Salesforce 合作伙伴的浏览器扩展和应用程序,或查看组件库以获取符合您要求的基本组件。

Salesforce 不提供对第三方浏览器扩展的支持。使用第三方浏览器扩展程序的风险由您自行承担。Salesforce 无法阻止这些扩展程序访问您的 Salesforce 数据,也无法检测到有人试图访问您的数据。

具有 DOM 操作的浏览器扩展

一些第三方浏览器扩展会注入自定义脚本来操作 DOM。出于以下原因,我们不建议也不支持此类第三方浏览器扩展:

  • 操纵 DOM 的浏览器扩展(例如在 DOM 中插入或删除元素)可能会干扰 Lightning Experience 的稳定性并导致意外行为。
  • 不遵循 Salesforce 安全标准的浏览器扩展可能无法在 Lightning Experience 中正常工作。
  • Lightning Experience 的内部 DOM 结构可能会在将来的版本中更改,并且不保证与第三方浏览器扩展的兼容性。

如果您需要使用 DOM,请检查您是否可以通过 lightning/platformResourceLoader 改用 JavaScript 库。

支持的 JavaScript

要开发 Lightning Web 组件,请使用最新版本的 JavaScript。

LWC 支持与 Lightning Experience 相同的浏览器,即 Edge、Chrome、Firefox 和 Safari 的最新稳定版本。要为这些浏览器开发 Lightning Web 组件,您可以使用浏览器支持且 Lightning Locker 允许的任何 JavaScript 功能。

Lightning Locker 提供组件隔离和安全性,允许来自多个来源的代码使用安全、标准的 API 和事件机制执行和交互。若要编写与 Locker 兼容的代码,请使用 Locker 工具。

Lightning Web Security (LWS) 是 Lightning Web 组件的 Lightning Locker 的替代方案。LWS 旨在取代多个版本的 Lightning Locker。有关更多信息,请参阅 Lightning Web 安全。

提示

要学习 JavaScript(或者如果您想复习一下),请从现代 JavaScript 开发 Trailhead 模块开始。

本开发人员指南解释了如何开发 Lightning Web 组件,并记录了特定于 LWC 的 JavaScript 函数,例如用于处理 Salesforce 数据的电线适配器。

本开发人员指南不记录标准 JavaScript,也不教授 JavaScript 基础知识。标准 JavaScript 记录在 Mozilla 开发者网络 (MDN) JavaScript 参考中。如果你正在寻找一个函数的文档,请先尝试 MDN。例如,如果您要查找有关 的信息,请使用 MDN。addEventListener()

注意

Salesforce 于 2023 年 1 月 1 日终止了对 Internet Explorer 11 的支持。在 Winter ’24 之后,您将无法再使用 Internet Explorer 11 和其他旧版浏览器来查看或访问 Lightning Experience。

支持的 Salesforce 目标和工具

Lightning Web 组件支持与许多 Salesforce 目标和工具一起使用。开发组件时,请在组件的配置文件中指定其目标。

  • 闪电体验
  • Salesforce 移动应用程序
  • Lightning 应用程序生成器
  • Experience Builder 网站
  • 体验生成器
  • 独立应用程序
  • 实用栏
  • Lightning Experience 中的快速操作
  • 自定义选项卡
  • Gmail 和 Outlook 集成
  • 嵌入式服务聊天
  • 适用于 Visualforce 的 Lightning 组件
  • 闪电输出(测试版)
  • OmniScripts的
  • 第一代托管软件包
  • 第二代托管软件包
  • 解锁套餐
  • 非托管软件包
  • 更改集

有关更多信息,请参阅在 Salesforce Targets 中使用组件。

不支持将 Lightning Web 组件与这些工具一起使用。要将 Lightning Web 组件与这些工具一起使用,请将该组件包装在 Aura 组件中。

  • Chatter 扩展
  • 全球行动
  • 列表视图操作
  • 相关列表视图操作
  • 标准操作覆盖
  • URL 可寻址选项卡

支持的 Salesforce API

Lightning Web 组件支持与许多 Salesforce API 一起使用。您可以通过导入指定的命名空间模块来访问这些 API。并非列出的 Salesforce API 上的所有功能或端点都受支持。lightningCRM 分析 API

使用 CRM Analytics 数据集和仪表板。目前不支持 Analytics 镜头。请参阅 lightning/analyticsWaveApi 电线适配器和功能。

Einstein 发现 API

检索 Einstein Discovery 故事。目前不支持 Einstein 预测和模型。请参阅 lightning/analyticsSmartDataDiscoveryApi 线路适配器和功能。

EMP API

订阅流媒体频道并收听事件消息。请参阅 lightning/empApi 模块。

元数据 API

从组织部署或检索 Lightning Web 组件包。请参阅 LightningComponentBundle 元数据类型。

移动 SDK

在混合应用中运行 LWC。请参阅 Mobile SDK 开发指南。

Salesforce 控制台 API

使用工作区选项卡和子选项卡。请参阅 Workspace API。尚不支持导航项和实用程序栏 API。

Service Cloud 语音工具包 API

访问服务云语音功能。请参阅《Service Cloud Voice 实施指南》。

工具 API

使用工具 API 处理 Lightning Web 组件包。请参阅 LightningComponentBundle 和 LightningComponentResource 对象。

用户界面 API

使用记录、列表视图等。请参阅 Lightning 数据服务线路适配器和功能。

不支持将 Lightning Web 组件与这些工具一起使用。要将 Lightning Web 组件与这些工具一起使用,请将该组件包装在 Aura 组件中。

  • Lightning 控制台导航项 API
  • Lightning 控制台实用程序栏 API
  • 对话工具包 API
  • Omni 工具包 API

如何选择 Lightning Web 组件或 Aura

Lightning Web 组件比 Aura 组件性能更好,更易于开发。但是,在开发 Lightning Web 组件时,您可能还需要使用 Aura,因为 LWC 尚不支持 Aura 所做的一切。

您如何决定将哪些组件开发为 Lightning Web 组件,哪些组件开发为 Aura 组件?

始终选择 Lightning Web 组件,除非您需要不受支持的功能。

要使用不受支持的体验或功能或使用不受支持的界面,请开发一个 Lightning Web 组件,并将其包装在一个 Aura 组件中,该组件只需访问该体验、功能或界面。

注意

Lightning Web 组件不能包含 Aura 组件。当您开发 Lightning Web 组件时,其 DOM 子树必须完全由 Lightning Web 组件组成。

要使用不能用作 Lightning Web 组件的基本组件,您可能需要在 Aura 中开发所有内容(或几乎所有内容)。

API 生命周期终止政策

Salesforce 承诺自首次发布之日起至少 3 年内支持每个 API 版本。为了完善和提高 API 的质量和性能,超过 3 年的版本可能会被弃用或停用。

Salesforce 会在对该版本的支持结束前至少 1 年通知使用计划弃用的 API 版本的客户。

Lightning Web 组件 (LWC) 已于 19 年春季正式发布,并在 Salesforce API 版本 45.0 及更高版本中受支持。

自定义组件可以使用线路适配器来调用 Salesforce API,例如 Connect REST API 和 UI API。在相应的开发人员指南中查看 API 生命周期终止政策。

如果您请求任何资源或使用已停用的 API 版本中的操作,则 REST API 将返回错误代码。410:GONE

若要识别从旧 API 版本或不受支持的 API 版本发出的请求,请使用免费的 API 总使用量事件类型。

设置开发环境

使用适合您需求的工作流程开发 Lightning Web 组件。我们建议使用 Salesforce DX 工具,但您可能处于不支持这些工具的情况。您仍然可以使用自己喜欢的代码编辑器,并使用自己的工具部署到组织。但是,您无法在开发人员控制台中开发 Lightning Web 组件。

设置编辑器、Linter 和组织

无论您的开发工作流程是什么,您都必须设置代码编辑器、设置 linting 并配置组织才能看到组件的运行情况。

安装代码编辑器

要开发 Lightning Web 组件,请使用您喜欢的代码编辑器。

我们建议使用 Visual Studio Code,因为它的 Salesforce 扩展包提供了用于处理 Salesforce CLI、Lightning 组件框架、Apex 和 Visualforce 的强大功能。

如果您选择使用 Visual Studio Code,请安装它和 Salesforce 扩展包。

  • Visual Studio Code (VS 代码)
  • 适用于 Visual Studio Code 的 Salesforce 扩展包

我们还建议使用 Prettier 进行代码格式设置。VS Code 可以将 Prettier 安装为扩展。对于其他编辑器,请参阅 Prettier 文档。

注意

您无法在 Salesforce 开发人员控制台中开发 Lightning Web 组件。

设置 Linting

Linting 会在编译之前在编辑时发现代码中的错误。Linting 不仅仅是拼写检查,它还引导您远离反模式,转向良好的模式。Salesforce 创建了 ESLint 规则,以最大程度地减少 Lightning Web 组件编程错误。

如果您使用的是 Salesforce DX 项目,则无需手动设置 linting。请参阅在 Scratch 组织中开发 和 在非 Scratch 组织中开发。

如果您使用的不是 Salesforce DX 项目,请从命令行安装 ESLint 规则。有关说明,请参阅 github/salesforce/eslint-plugin-lwc 和 github/salesforce/eslint-config-lwc 存储库。

提示

linting 规则有三个配置级别。查看 eslint-config-lwc 自述文件,并选择符合您编码需求的配置。

如果您正在开发要在 Lightning Web Security 中运行的组件,请参阅安装 Lightning Web Security 的 ESLint 规则。

建立开发组织

注册 Developer Edition 组织。

  • Developer Edition 注册表单

您可以在临时组织和非临时组织中开发 Lightning Web 组件。若要创建临时组织,请将组织配置为开发人员中心。本地开发需要临时组织。

Salesforce CLI 在临时组织中的工作方式与在非临时组织中的工作方式略有不同。我们创建一个项目,并回顾使用 Salesforce DX 工具进行开发中的工作流程。

注意

从 23 年冬季开始,Lightning Web Security 默认在临时组织中启用。有关更多信息,请参阅在临时组织中启用和禁用 LWS。

在开发组织中启用调试模式

要更轻松地调试 JavaScript 代码,请在组织中启用调试模式。启用调试模式时,框架 JavaScript 代码不会缩小,因此更易于阅读和调试。调试模式还为某些警告和错误添加了更详细的输出。

仅对正在积极开发或调试 JavaScript 的用户启用调试模式。对于启用了 Salesforce 的用户来说,Salesforce 速度较慢。

  1. 在 Salesforce 中,从“设置”中输入“快速查找”框,然后选择“调试模式”。Debug Mode
  2. 在用户列表中,找到需要启用调试模式的任何用户。如有必要,请使用标准列表视图控件来筛选组织的用户。
  3. 启用要为其启用调试模式的用户旁边的选择复选框。
  4. 单击启用

使用 Salesforce DX 工具进行开发

我们建议使用 Salesforce DX 工具,以获得最紧密集成的开发体验。此工作流利用 Salesforce CLI、带有 Salesforce 扩展包的 VS Code 编辑器、Salesforce DX 项目以及临时或沙盒组织。

提示

要使用 Salesforce DX 工具设置您的开发人员环境并创建您的第一个组件,请前往 Trailhead 并完成快速入门:Lightning Web 组件项目。

开发移动组件

在开发 Lightning Web 组件时,不仅要检查桌面上的组件演示,还要检查移动设备上的组件演示,这一点很重要。要在虚拟移动设备上预览组件并在编码时查看更改,请使用 Salesforce CLI 移动扩展插件。然后下载并运行 Salesforce 移动应用程序的虚拟设备版本,以预览您的组件如何与 Salesforce 中的其他组件共存。

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

使用您自己的工具进行部署

不使用 Salesforce DX 工具?没关系!使用您自己的工具和元数据 API 部署 Lightning Web 组件。

有时,开发人员和发布管理团队使用不使用 Salesforce DX 工具的部署环境。例如,部署环境可能使用 Eclipse、多个存储库以及 ANT 或 GNU Make 等工具。或者,您的团队可能有一个既定的内部发布流程。虽然我们建议使用我们的工具,但您可以使用元数据以自己的方式部署组件。

要使用软件包和元数据 API 开发和部署 Lightning Web 组件,请按照下列步骤操作。

  1. 创建一个包含组件文件和包 .xml 清单文件的 zip 存档。Lightning Web 组件的元数据 API 类型为 LightningComponentBundle。
  2. 使用 ANT 迁移工具等工具或您自己的工具将组件部署到沙盒或临时组织。

重要

当您使用 Salesforce CLI 时,您可以在项目和目标临时组织之间进行更改跟踪。如果没有这些工具,您必须提出自己的解决方案。像 Github 这样的存储库会监控代码更改。沙盒支持对数据或字段的更改进行审核。

如果您使用的是 Prettier,请考虑添加一个 pre-commit 钩子,以确保您的代码格式正确。

使用 Lightning Web 组件进行开发的最佳实践

要创建安全高效的 Lightning Web 组件,请遵循 Salesforce 开发人员博客上的开发人员最佳实践列表:

  • 通过开发人员最佳实践清单提高一致性并提高开发人员技能
  • Lightning Web 组件性能最佳实践
  • 提高您的 LWC 技能 – 第 1 部分
  • 提高您的 LWC 技能 – 第 2 部分

探索 Trailhead 和示例代码

我们创建了 Trailhead 内容和 GitHub 存储库,以快速启动您的 Lightning Web 组件开发。

Lightning Web 组件配方

Lightning Web 组件的易于理解的代码示例集合。每个配方都演示了如何以尽可能少的代码行编写特定任务,同时遵循最佳实践。“查看源代码”链接会将你直接带到 GitHub 中的代码。

这些方法涵盖了所有基础知识:组合、状态管理、事件、数据访问和导航。《Lightning Web 组件开发人员指南》引用了此存储库中的许多方法。

若要开始,请克隆存储库并按照自述文件中的步骤操作。

Trailhead 项目和模块

在构建 Lightning Web 组件路径上获得创建 Lightning Web 组件的实践经验。

对 Lightning Web 组件进行故障排除,并在 Lightning Web 组件故障排除路径中了解最佳实践。

电动自行车示例应用程序

E-Bikes 示例应用程序演示了如何使用 Lightning Web 组件构建应用程序并与 Salesforce Experience 集成。E-Bikes是一家虚构的电动自行车制造商。该应用程序使用丰富的用户体验帮助电动自行车管理其产品和经销商订单。

安装 E-Bikes 示例应用程序。

运输发票示例

本附录提供了 Apex 应用程序的示例。这是一个比 Hello World 示例。

  • 运输发票演练
  • 装运发票示例代码
  1. 装运发票示例演练
  2. 装运发票示例代码

装运发票示例演练

本节中的示例应用程序包括混合了的传统 Salesforce 功能 与 Apex。Apex 的许多句法和语义特征,以及常见的习语,是 在此应用程序中进行了说明。

注意

装运发票示例需要自定义对象。您可以 您可以自行创建这些代码,或者将对象和 Apex 代码下载为非托管包。 Salesforce AppExchange。要获取组织中的示例资产,请安装 Apex 教程包。此套餐还 包含 Apex 快速入门的示例代码和对象。

场景

在此示例应用程序中,用户 创建新的装运发票或订单,然后将物料添加到发票中。总金额 对于订单,包括运费,是根据 在发票中添加或删除的项目。

数据和代码模型

此示例应用程序使用两个新的 对象:项目和Shipping_invoice。做出以下假设:

  • 项目 A 不能同时按顺序排列shipping_invoice1和shipping_invoice2。两个客户 无法获得相同的(物理)产品。
  • 税率为9.25%。
  • 运费为每磅 75 美分。
  • 一旦订单超过 100 美元,将应用运费折扣(免运费)。

Item 自定义对象中的字段包括:

名字类型描述
名字字符串项目的名称
价格货币商品价格
数量订单中的项目数
重量物品的重量,用于计算运费
Shipping_invoice大纲-细节 (shipping_invoice)与此项目关联的顺序

Shipping_invoice自定义对象中的字段包括:

名字类型描述
名字字符串装运发票/订单的名称
小计货币小计
总计货币总金额,包括税费和运费
航运货币收取的运费金额(假设每磅 0.75 美元)
运费折扣货币当小计金额达到 100 美元时,只适用一次
货币税额(假设9.25%)
总重量所有物品的总重量

此应用程序的所有 Apex 都包含在触发器中。此应用程序具有 以下触发器:

对象触发器名称运行时描述
项目插入后、更新后、删除后更新装运发票,计算总计和装运
Shipping_invoice运费折扣更新后更新装运发票,计算是否有装运折扣

以下是用户操作的一般流程以及触发器的运行时间:

购物车应用程序的用户操作和触发器流Flow of user action and triggers for shopping cart application

  1. 用户点击订单 |新建,命名装运发票,然后单击保存
  2. 用户单击“新建项目”,填写信息,然后单击“保存”。
  3. 计算触发器运行次数。“计算”触发器的一部分将更新装运发票。
  4. ShippingDiscount 触发器运行。
  5. 然后,用户可以添加、删除或更改发票中的项目。

在装运发票示例代码中,触发器和 列出了测试类。代码中的注释解释了该功能。

测试装运发票应用程序

在将应用程序作为包的一部分包含在内之前,必须将 75% 的代码覆盖 单元测试。因此,装运发票应用程序的一件是用于测试的类 触发器。测试类验证以下操作是否已成功完成:

  • 插入项目
  • 更新项目
  • 删除项目
  • 应用运费折扣
  • 输入不良的阴性测试

装运发票示例代码

以下触发器和测试类组成了装运发票 应用示例:

  • 计算触发器
  • ShippingDiscount 触发器
  • 测试类

计算触发器

trigger calculate on Item__c (after insert, after update, after delete) {

// Use a map because it doesn't allow duplicate values

Map<ID, Shipping_Invoice__C> updateMap = new Map<ID, Shipping_Invoice__C>();

// Set this integer to -1 if we are deleting
Integer subtract ;

// Populate the list of items based on trigger type
List<Item__c> itemList;
    if(trigger.isInsert || trigger.isUpdate){
        itemList = Trigger.new;
        subtract = 1;
    }
    else if(trigger.isDelete)
    {
        // Note -- there is no trigger.new in delete
        itemList = trigger.old;
        subtract = -1;
    }

// Access all the information we need in a single query 
// rather than querying when we need it.
// This is a best practice for bulkifying requests

set<Id> AllItems = new set<id>();

for(item__c i :itemList){
// Assert numbers are not negative.  
// None of the fields would make sense with a negative value

System.assert(i.quantity__c > 0, 'Quantity must be positive');
System.assert(i.weight__c >= 0, 'Weight must be non-negative');
System.assert(i.price__c >= 0, 'Price must be non-negative');

// If there is a duplicate Id, it won't get added to a set
AllItems.add(i.Shipping_Invoice__C);
}

// Accessing all shipping invoices associated with the items in the trigger
List<Shipping_Invoice__C> AllShippingInvoices = [SELECT Id, ShippingDiscount__c, 
                   SubTotal__c, TotalWeight__c, Tax__c, GrandTotal__c 
                   FROM Shipping_Invoice__C WHERE Id IN :AllItems];
                   
// Take the list we just populated and put it into a Map.  
// This will make it easier to look up a shipping invoice
// because you must iterate a list, but you can use lookup for a map, 
Map<ID, Shipping_Invoice__C> SIMap = new Map<ID, Shipping_Invoice__C>();

for(Shipping_Invoice__C sc : AllShippingInvoices)
{
    SIMap.put(sc.id, sc);
}

// Process the list of items
    if(Trigger.isUpdate)
    {
        // Treat updates like a removal of the old item and addition of the         
        // revised item rather than figuring out the differences of each field 
        // and acting accordingly.
        // Note updates have both trigger.new and trigger.old
        for(Integer x = 0; x < Trigger.old.size(); x++)
        {
            Shipping_Invoice__C myOrder;
            myOrder = SIMap.get(trigger.old[x].Shipping_Invoice__C);

            // Decrement the previous value from the subtotal and weight.
            myOrder.SubTotal__c -= (trigger.old[x].price__c * 
                                    trigger.old[x].quantity__c);
            myOrder.TotalWeight__c -= (trigger.old[x].weight__c * 
                                       trigger.old[x].quantity__c);
                
            // Increment the new subtotal and weight.
            myOrder.SubTotal__c += (trigger.new[x].price__c * 
                                    trigger.new[x].quantity__c);
            myOrder.TotalWeight__c += (trigger.new[x].weight__c * 
                                       trigger.new[x].quantity__c);
        }
        
        for(Shipping_Invoice__C myOrder : AllShippingInvoices)
        {
            
            // Set tax rate to 9.25%  Please note, this is a simple example.  
            // Generally, you would never hard code values.
            // Leveraging Custom Settings for tax rates is a best practice.  
            // See Custom Settings in the Apex Developer Guide 
            // for more information.
            myOrder.Tax__c = myOrder.Subtotal__c * .0925;
            
            // Reset the shipping discount
            myOrder.ShippingDiscount__c = 0;
    
            // Set shipping rate to 75 cents per pound.  
            // Generally, you would never hard code values.
            // Leveraging Custom Settings for the shipping rate is a best practice.
            // See Custom Settings in the Apex Developer Guide 
            // for more information.
            myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
            myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c + 
                                    myOrder.Shipping__c;
            updateMap.put(myOrder.id, myOrder);
         }
    }
    else
    { 
        for(Item__c itemToProcess : itemList)
        {
            Shipping_Invoice__C myOrder;
    
            // Look up the correct shipping invoice from the ones we got earlier
            myOrder = SIMap.get(itemToProcess.Shipping_Invoice__C);
            myOrder.SubTotal__c += (itemToProcess.price__c * 
                                    itemToProcess.quantity__c * subtract);
            myOrder.TotalWeight__c += (itemToProcess.weight__c * 
                                       itemToProcess.quantity__c * subtract);
        }
        
        for(Shipping_Invoice__C myOrder : AllShippingInvoices)
        {
            
             // Set tax rate to 9.25%  Please note, this is a simple example.  
             // Generally, you would never hard code values.
             // Leveraging Custom Settings for tax rates is a best practice.  
             // See Custom Settings in the Apex Developer Guide 
             // for more information.
             myOrder.Tax__c = myOrder.Subtotal__c * .0925;
             
             // Reset shipping discount
             myOrder.ShippingDiscount__c = 0;
    
            // Set shipping rate to 75 cents per pound.  
            // Generally, you would never hard code values.
            // Leveraging Custom Settings for the shipping rate is a best practice.
            // See Custom Settings in the Apex Developer Guide 
            // for more information.
            myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
            myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c + 
                                    myOrder.Shipping__c;
                                       
            updateMap.put(myOrder.id, myOrder);
    
         }
     }    
     
     // Only use one DML update at the end.
     // This minimizes the number of DML requests generated from this trigger.
     update updateMap.values();
}

运费折扣 触发

trigger ShippingDiscount on Shipping_Invoice__C (before update) {
    // Free shipping on all orders greater than $100
    
    for(Shipping_Invoice__C myShippingInvoice : Trigger.new)
    {
        if((myShippingInvoice.subtotal__c >= 100.00) && 
           (myShippingInvoice.ShippingDiscount__c == 0))
        {
            myShippingInvoice.ShippingDiscount__c = 
                         myShippingInvoice.Shipping__c * -1;
            myShippingInvoice.GrandTotal__c += myShippingInvoice.ShippingDiscount__c;
        }
    }
}

运输发票 测试

@IsTest
private class TestShippingInvoice{

    // Test for inserting three items at once
    public static testmethod void testBulkItemInsert(){
        // Create the shipping invoice. It's a best practice to either use defaults
        // or to explicitly set all values to zero so as to avoid having
        // extraneous data in your test.
        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
                          totalweight__c = 0, grandtotal__c = 0, 
                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);

        // Insert the order and populate with items
        insert Order1;
        List<Item__c> list1 = new List<Item__c>();
        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        list1.add(item1);
        list1.add(item2);
        list1.add(item3);
        insert list1;
        
        // Retrieve the order, then do assertions
        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
                  grandtotal__c, shippingdiscount__c 
                  FROM Shipping_Invoice__C 
                  WHERE id = :order1.id];
        
        System.assert(order1.subtotal__c == 75, 
                'Order subtotal was not $75, but was '+ order1.subtotal__c);
        System.assert(order1.tax__c == 6.9375, 
                'Order tax was not $6.9375, but was ' + order1.tax__c);
        System.assert(order1.shipping__c == 4.50, 
                'Order shipping was not $4.50, but was ' + order1.shipping__c);
        System.assert(order1.totalweight__c == 6.00, 
                'Order weight was not 6 but was ' + order1.totalweight__c);
        System.assert(order1.grandtotal__c == 86.4375, 
                'Order grand total was not $86.4375 but was ' 
                 + order1.grandtotal__c);
        System.assert(order1.shippingdiscount__c == 0, 
                'Order shipping discount was not $0 but was ' 
                + order1.shippingdiscount__c);
    }
    
    // Test for updating three items at once
    public static testmethod void testBulkItemUpdate(){

        // Create the shipping invoice. It's a best practice to either use defaults
        // or to explicitly set all values to zero so as to avoid having
        // extraneous data in your test.
        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
                          totalweight__c = 0, grandtotal__c = 0, 
                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);

        // Insert the order and populate with items.
        insert Order1;
        List<Item__c> list1 = new List<Item__c>();
        Item__c item1 = new Item__C(Price__c = 1, weight__c = 1, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item2 = new Item__C(Price__c = 2, weight__c = 2, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item3 = new Item__C(Price__c = 4, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        list1.add(item1);
        list1.add(item2);
        list1.add(item3);
        insert list1;
        
        // Update the prices on the 3 items
        list1[0].price__c = 10;
        list1[1].price__c = 25;
        list1[2].price__c = 40;
        update list1;
        
        // Access the order and assert items updated
        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
                  grandtotal__c, shippingdiscount__c 
                  FROM Shipping_Invoice__C 
                  WHERE Id = :order1.Id];

        System.assert(order1.subtotal__c == 75, 
                       'Order subtotal was not $75, but was '+ order1.subtotal__c);
        System.assert(order1.tax__c == 6.9375, 
                       'Order tax was not $6.9375, but was ' + order1.tax__c);
        System.assert(order1.shipping__c == 4.50, 
                       'Order shipping was not $4.50, but was ' 
                       + order1.shipping__c);
        System.assert(order1.totalweight__c == 6.00, 
                       'Order weight was not 6 but was ' + order1.totalweight__c);
        System.assert(order1.grandtotal__c == 86.4375, 
                       'Order grand total was not $86.4375 but was ' 
                       + order1.grandtotal__c);
        System.assert(order1.shippingdiscount__c == 0, 
                       'Order shipping discount was not $0 but was ' 
                       + order1.shippingdiscount__c);
    
    }
    
    // Test for deleting items
    public static testmethod void testBulkItemDelete(){

        // Create the shipping invoice. It's a best practice to either use defaults
        // or to explicitly set all values to zero so as to avoid having
        // extraneous data in your test.
        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
                          totalweight__c = 0, grandtotal__c = 0, 
                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);

        // Insert the order and populate with items
        insert Order1;
        List<Item__c> list1 = new List<Item__c>();
        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c itemA = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c itemB = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c itemC = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c itemD = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        list1.add(item1);
        list1.add(item2);
        list1.add(item3);
        list1.add(itemA);
        list1.add(itemB);
        list1.add(itemC);
        list1.add(itemD);
        insert list1;
        
        // Seven items are now in the shipping invoice. 
       // The following deletes four of them.
        List<Item__c> list2 = new List<Item__c>();
        list2.add(itemA);
        list2.add(itemB);
        list2.add(itemC);
        list2.add(itemD);
        delete list2;
        
        // Retrieve the order and verify the deletion
        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
                  grandtotal__c, shippingdiscount__c 
                  FROM Shipping_Invoice__C 
                  WHERE Id = :order1.Id];
        
        System.assert(order1.subtotal__c == 75, 
                      'Order subtotal was not $75, but was '+ order1.subtotal__c);
        System.assert(order1.tax__c == 6.9375, 
                      'Order tax was not $6.9375, but was ' + order1.tax__c);
        System.assert(order1.shipping__c == 4.50, 
                      'Order shipping was not $4.50, but was ' + order1.shipping__c);
        System.assert(order1.totalweight__c == 6.00, 
                      'Order weight was not 6 but was ' + order1.totalweight__c);
        System.assert(order1.grandtotal__c == 86.4375, 
                      'Order grand total was not $86.4375 but was ' 
                      + order1.grandtotal__c);
        System.assert(order1.shippingdiscount__c == 0, 
                      'Order shipping discount was not $0 but was ' 
                      + order1.shippingdiscount__c);
    }
    // Testing free shipping
    public static testmethod void testFreeShipping(){

        // Create the shipping invoice. It's a best practice to either use defaults
        // or to explicitly set all values to zero so as to avoid having
        // extraneous data in your test.
        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
                          totalweight__c = 0, grandtotal__c = 0, 
                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);

        // Insert the order and populate with items.
        insert Order1;
        List<Item__c> list1 = new List<Item__c>();
        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, 
                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, 
                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, 
                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
        list1.add(item1);
        list1.add(item2);
        list1.add(item3);
        insert list1;
        
        // Retrieve the order and verify free shipping not applicable
        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
                  grandtotal__c, shippingdiscount__c 
                  FROM Shipping_Invoice__C 
                  WHERE Id = :order1.Id];
        
        // Free shipping not available on $75 orders
        System.assert(order1.subtotal__c == 75, 
                      'Order subtotal was not $75, but was '+ order1.subtotal__c);
        System.assert(order1.tax__c == 6.9375, 
                      'Order tax was not $6.9375, but was ' + order1.tax__c);
        System.assert(order1.shipping__c == 4.50, 
                      'Order shipping was not $4.50, but was ' + order1.shipping__c);
        System.assert(order1.totalweight__c == 6.00, 
                      'Order weight was not 6 but was ' + order1.totalweight__c);
        System.assert(order1.grandtotal__c == 86.4375, 
                      'Order grand total was not $86.4375 but was ' 
                      + order1.grandtotal__c);
        System.assert(order1.shippingdiscount__c == 0, 
                      'Order shipping discount was not $0 but was ' 
                      + order1.shippingdiscount__c);
        
        // Add items to increase subtotal
        item1 = new Item__C(Price__c = 25, weight__c = 20, quantity__c = 1, 
                            Shipping_Invoice__C = order1.id);       
        insert item1;

        // Retrieve the order and verify free shipping is applicable
        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
                  grandtotal__c, shippingdiscount__c 
                  FROM Shipping_Invoice__C 
                  WHERE Id = :order1.Id];
        
        // Order total is now at $100, so free shipping should be enabled
        System.assert(order1.subtotal__c == 100, 
                      'Order subtotal was not $100, but was '+ order1.subtotal__c);
        System.assert(order1.tax__c == 9.25, 
                      'Order tax was not $9.25, but was ' + order1.tax__c);
        System.assert(order1.shipping__c == 19.50, 
                      'Order shipping was not $19.50, but was ' 
                      + order1.shipping__c);
        System.assert(order1.totalweight__c == 26.00, 
                      'Order weight was not 26 but was ' + order1.totalweight__c);
        System.assert(order1.grandtotal__c == 109.25, 
                      'Order grand total was not $86.4375 but was ' 
                      + order1.grandtotal__c);
        System.assert(order1.shippingdiscount__c == -19.50, 
                      'Order shipping discount was not -$19.50 but was ' 
                      + order1.shippingdiscount__c);
    }
    
     // Negative testing for inserting bad input
    public static testmethod void testNegativeTests(){

        // Create the shipping invoice. It's a best practice to either use defaults
        // or to explicitly set all values to zero so as to avoid having
        // extraneous data in your test.
        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
                          totalweight__c = 0, grandtotal__c = 0, 
                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);

        // Insert the order and populate with items. 
        insert Order1;
        Item__c item1 = new Item__C(Price__c = -10, weight__c = 1, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item2 = new Item__C(Price__c = 25, weight__c = -2, quantity__c = 1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = -1, 
                                    Shipping_Invoice__C = order1.id);
        Item__c item4 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 0, 
                                    Shipping_Invoice__C = order1.id);

        try{
            insert item1;
        }
        catch(Exception e)
        {
            system.assert(e.getMessage().contains('Price must be non-negative'), 
                         'Price was negative but was not caught');
        }
        
        try{
            insert item2;
        }
        catch(Exception e)
        {
            system.assert(e.getMessage().contains('Weight must be non-negative'), 
                         'Weight was negative but was not caught');
        }

        try{
            insert item3;
        }
        catch(Exception e)
        {
            system.assert(e.getMessage().contains('Quantity must be positive'), 
                         'Quantity was negative but was not caught');
        }
        
        try{
            insert item4;
        }
        catch(Exception e)
        {
            system.assert(e.getMessage().contains('Quantity must be positive'), 
                         'Quantity was zero but was not caught');
        }
    }
}