快速操作

快速操作使用户能够在 Salesforce 中执行更多操作。通过自定义快速操作,您可以方便地访问最重要的信息,从而使用户的导航和工作流程尽可能顺畅。快速操作可以在记录页面上调用自定义 Lightning Web 组件。

配置组件时,可以选择在模式窗口中显示它,也可以选择无头执行其自定义代码。例如,快速操作可以打开自定义 Lightning Web 组件,其中包含允许用户创建或更新记录上特定字段的表单。快速操作还可以执行导航到另一个页面或调度事件的 Lightning Web 组件代码。

注意

Lightning Web 组件仅支持作为 Lightning Experience 中的快速操作。Salesforce 移动应用程序不支持使用 Lightning Web 组件作为快速操作。

配置组件以执行快速操作

要使用 Lightning Web 组件作为快速操作,请定义组件的元数据。

屏幕快速操作和无头快速操作

LWC 快速操作有两种类型:屏幕快速操作和无头快速操作

  • 屏幕快速操作在模式窗口中显示组件。请参阅创建屏幕快速操作。
  • 无外设快速操作执行您在方法中提供的自定义代码。请参阅创建 Headless 快速操作。@api invoke()

注意

您只能将 Lightning Web 组件用作记录页面上的快速操作。您不能将 Lightning Web 组件用作全局快速操作。

在配置文件中定义组件元数据

组件的项目文件夹必须包含定义组件元数据值的 componentName.js-meta.xml 配置文件。若要将组件用作快速操作,请按照以下步骤配置文件。

  1. 在 中,添加为 a 以将 Lightning Web 组件指定为记录页面上的快速操作。targetslightning__RecordActiontarget
  2. 添加 并设置为 。targetConfigtargetslightning__RecordAction
  3. 设置为 或 选择快速操作类型。如果未指定 ,则快速操作默认为屏幕操作。actionTypeScreenActionActionactionType

此配置文件定义屏幕操作。

<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>52.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__RecordAction</target>
  </targets>
  <targetConfigs>
    <targetConfig targets="lightning__RecordAction">
      <actionType>ScreenAction</actionType>
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

此配置文件定义了无头操作。

<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>52.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__RecordAction</target>
  </targets>
  <targetConfigs>
    <targetConfig targets="lightning__RecordAction">
      <actionType>Action</actionType>
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

请参阅 XML 配置文件元素的完整列表。

在 Salesforce 设置中创建快速操作

在 Salesforce 设置中,通过“对象管理器”页面创建特定于对象的操作。然后将该操作添加到页面布局的 Salesforce Mobile 和 Lightning Experience 操作部分。请参阅 Salesforce 帮助中的快速操作。

注意

Salesforce 移动应用程序不支持使用 Lightning Web 组件作为快速操作。

创建屏幕快速操作

屏幕快速操作将显示在模式窗口中。提供您自己的标记或使用该组件,以实现基于 Lightning Design System 的一致用户界面。lightning-quick-action-panel

要使组件用作屏幕快速操作,请配置目标。请参阅配置组件以执行快速操作。

与记录页面上的其他 Lightning Web 组件不同,LWC 快速操作不会传入 .如果需要访问 ,请在代码中设置 的值。recordIdconnectedCallback()recordIdrecordId

_recordId;
set recordId(recordId) {
    if (recordId !== this._recordId) {
        this._recordId = recordId;

打开和关闭模式窗口

屏幕快速操作可在模式窗口中打开 Lightning Web 组件。要以编程方式关闭模式窗口,例如,要创建“取消”按钮,请生成调度自定义事件的 UI。从模块导入事件。CloseActionScreenEventlightning/actions

import { CloseActionScreenEvent } from "lightning/actions";

以下各节包含完整的代码示例。

注意

如果使用自定义页脚按钮构建屏幕快速操作,则按 X 仅关闭模式,则没有挂钩在关闭时执行其他逻辑。如果屏幕快速操作具有在“取消”时执行的逻辑,则在面板关闭时将绕过该逻辑。

使用 lightning-quick-action-panel 实现一致的 UI

要提供一致的 Salesforce UI,请将 Lightning Web 组件包装在 lightning-quick-action-panel 组件中,该组件提供与 Salesforce Lightning Design System 中的模式蓝图一致的页眉、正文和页脚。

<template>
  <lightning-quick-action-panel header="My action">
    Here's some content for the modal body.

    <div slot="footer">
      <lightning-button variant="neutral" label="Cancel"></lightning-button>
      <lightning-button variant="brand" label="Save" class="slds-m-left_x-small"></lightning-button>
    </div>
  </lightning-quick-action-panel>
</template>

在模态主体中创建表单

创建模态主体的一种方法是将组件与由组件填充的字段值一起使用。取消和提交按钮必须嵌套在组件中,因此使用此方法时不需要页脚槽。lightning-record-edit-formlightning-input-fieldlightning-record-edit-form

本示例创建一个窗体,用于填充不带页脚的 name 和 phone 字段。它呈现一个模态窗口,其标题包含文本“编辑字段操作”。lightning-record-edit-form

<template>
  <lightning-quick-action-panel header="Edit Fields Action">
    <lightning-record-edit-form
      record-id={recordId}
      object-api-name={objectApiName}
      onsuccess={handleSuccess}>
      <lightning-input-field field-name="Name"></lightning-input-field>
      <lightning-input-field field-name="Phone"></lightning-input-field>
      <lightning-button variant="neutral" label="Cancel"></lightning-button>
      <lightning-button variant="brand" class="slds-m-left_x-small" label="Save" type="submit">
      </lightning-button>
    </lightning-record-edit-form>
  </lightning-quick-action-panel>
</template>

当用户单击提交按钮时,将调用事件处理程序。处理程序使用该函数关闭模式窗口。handleSuccessCloseActionScreenEvent

import { LightningElement, api } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import { CloseActionScreenEvent } from "lightning/actions";

export default class QuickEditFormExample extends LightningElement {
  @api recordId;
  @api objectApiName;

  handleSuccess(e) {
    // Close the modal window and display a success toast
    this.dispatchEvent(new CloseActionScreenEvent());
    this.dispatchEvent(
      new ShowToastEvent({
        title: "Success",
        message: "Record updated!",
        variant: "success",
      }),
    );
  }
}

在页脚中创建带有按钮的自定义窗体

您可以使用 和 组件在模态主体中创建表单。使用此方法时,请使用组件的页脚槽来包含按钮。lightning-inputlightning-buttonlightning-quick-action-panel

此示例创建一个窗体,用于对联系人记录执行与上一示例类似的快速操作,并在页脚中使用按钮,因为它不使用记录窗体组件。该字段显示使用电线适配器的初始值。getRecord

<lightning-quick-action-panel header="Quick Contact Edit">
  <template lwc:if={contact.data}>
    <lightning-input
      label="First Name"
      value={firstname}
      class="slds-m-bottom_x-small">
    </lightning-input>
    <lightning-input
      label="Last Name"
      value={lastname}
      onchange={handleLastNameChange}
      class="slds-m-bottom_x-small"
      required>
    </lightning-input>
    <lightning-input
      label="Phone"
      type="tel"
      value={phone}
      class="slds-m-bottom_x-small">
    </lightning-input>
  </template>
  <div slot="footer">
    <lightning-button variant="neutral" label="Cancel" onclick={handleCancel}></lightning-button>
    <lightning-button
      variant="brand"
      class="slds-m-left_x-small"
      label="Save"
      type="submit"
      onclick={handleSubmit}
      disabled={disabled}>
    </lightning-button>
  </div>
</lightning-quick-action-panel>

如果“姓氏”字段为空,则“保存”按钮将被禁用。单击“保存”按钮将关闭模式窗口,如果保存成功,则显示 Toast。若要保存记录更改,请调用 updateRecord(recordInput, clientOptions)。

import { LightningElement, api, wire } from "lwc";
import { getRecord, getFieldValue } from "lightning/uiRecordApi";
import { updateRecord } from "lightning/uiRecordApi";
import { CloseActionScreenEvent } from "lightning/actions";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import FNAME_FIELD from "@salesforce/schema/Contact.FirstName";
import LNAME_FIELD from "@salesforce/schema/Contact.LastName";
import PHONE_FIELD from "@salesforce/schema/Contact.Phone";

const FIELDS = [FNAME_FIELD, LNAME_FIELD, PHONE_FIELD];

export default class QuickEditExample extends LightningElement {
  disabled = false;
  @api recordId;
  @api objectApiName;

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

  get firstname() {
    return getFieldValue(this.contact.data, FNAME_FIELD);
  }

  get lastname() {
    return getFieldValue(this.contact.data, LNAME_FIELD);
  }

  get phone() {
    return getFieldValue(this.contact.data, PHONE_FIELD);
  }

  handleCancel(event) {
    // Add your cancel button implementation here
    this.dispatchEvent(new CloseActionScreenEvent());
  }

  handleLastNameChange(event) {
    // Display field-level errors if last name field is empty.
    if (!event.target.value) {
      event.target.reportValidity();
      this.disabled = true;
    } else {
      this.disabled = false;
    }
  }

  handleSubmit(e) {
    // Add your updateRecord implementation

    // Close the modal window and display a success toast
    this.dispatchEvent(new CloseActionScreenEvent());
    this.dispatchEvent(
      new ShowToastEvent({
        title: "Success",
        message: "Record updated!",
        variant: "success",
      }),
    );
  }
}

获取有关主页的信息

您可以使用标准 LWC 功能获取有关当前页面的信息,包括来自导航服务的页面引用、记录 ID 和当前记录的对象 API 名称。

要返回页面引用,请从 lightning/navigation 导入。请参阅导航到页面、记录和列表。CurrentPageReference

若要获取记录 ID 和对象 API 名称,请公开 and 作为属性。请参阅使组件了解其记录上下文和使组件了解其对象上下文。recordIdobjectApiName

此示例显示当前记录的记录 ID 和对象 API 名称。它还返回当前页面引用,该引用描述当前页面及其状态。

<template>
  <p>These two fields are auto-populated based on the record context:</p>
  <p>RecordId: <i>{recordId}</i>, objectApiName: <i>{objectApiName}</i></p>
  <p>{pageRefString}</p>
</template>
import { LightningElement, api, wire } from "lwc";
import { CurrentPageReference } from "lightning/navigation";

export default class RecordContextAction extends LightningElement {
  @api recordId;
  @api objectApiName;

  @wire(CurrentPageReference)
  pageRef;

  get pageRefString() {
    return JSON.stringify(this.pageRef);
  }
}

创建 Headless 快速操作

无头快速操作在 Lightning Web 组件中执行自定义代码。与屏幕操作不同,无头操作不会打开模式窗口。

要使您的组件用作无外设快速操作,请配置目标。请参阅配置组件以执行快速操作。

与记录页面上的其他 Lightning Web 组件不同,LWC 快速操作不会传入 .如果需要访问 ,请在代码中设置 的值。recordIdconnectedCallback()recordIdrecordId

_recordId;
set recordId(recordId) {
    if (recordId !== this._recordId) {
        this._recordId = recordId;

实现 invoke()

在您的 Lightning Web 组件中,始终公开为无头快速操作的公共方法。每次触发快速操作时,都会执行该方法。invoke()invoke()

import { LightningElement, api } from "lwc";

export default class HeadlessSimple extends LightningElement {
  @api invoke() {
    console.log("Hi, I'm an action.");
  }
}

为您的 Lightning Web 组件创建一个空模板。

<template> </template>

若要防止在长时间运行的操作中多次并行执行快速操作,请添加内部布尔标志。

的返回类型为 。返回 a 会使方法异步,但返回的方法将被忽略。invoke()voidPromisePromise

此代码使用布尔标志来阻止双重执行,并使用 a 来等待完成。即使返回类型为 ,代码也会异步执行。Promisesleepvoid

import { LightningElement, api } from "lwc";

export default class HeadlessAsync extends LightningElement {
  isExecuting = false;

  @api async invoke() {
    if (this.isExecuting) {
      return;
    }

    this.isExecuting = true;
    await this.sleep(2000);
    this.isExecuting = false;
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

导航

要导航到 Lightning Experience 中的其他页面、记录或列表,请使用导航服务。

此示例导航到联系人主页。

import { LightningElement, api } from "lwc";
import { NavigationMixin } from "lightning/navigation";

export default class navigateToRecordAction extends NavigationMixin(LightningElement) {
  @api invoke() {
    this[NavigationMixin.Navigate]({
      type: "standard__objectPage",
      attributes: {
        objectApiName: "Contact",
        actionName: "home",
      },
    });
  }
}

请参阅导航到页面、记录和列表。

调度事件

您可以通过快速操作调度自定义事件。此示例使用模块提供的事件按顺序调度两个 Toast。lightning/platformShowToastEvent

import { LightningElement, api } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class DispatchEventAction extends LightningElement {
  @api async invoke() {
    let event = new ShowToastEvent({
      title: "I am a headless action!",
      message: "Hi there! Starting...",
    });
    this.dispatchEvent(event);

    await this.sleep(2000);

    event = new ShowToastEvent({
      title: "I am a headless action!",
      message: "All done!",
    });
    this.dispatchEvent(event);
  }

  sleep(ms) {
    // eslint-disable-next-line @lwc/lwc/no-async-operation
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

请参阅 Toast 通知和与事件通信。

将电子邮件创建为快速操作

从自定义组件中的按钮启动包含预填充内容的电子邮件编辑器。使用 和 组件创建 QuickAction (Global) Send Email 操作,该操作将打开包含预填充字段值的电子邮件草稿。lightning-navigationlightning-page-reference-utils

在组件的 HTML 文件中,使用 定义导航服务,使用 定义页面引用实用程序,使用 定义操作按钮。lightning-navigationlightning-page-reference-utilslightning-button

在此示例中,该函数在单击时触发,并使用主题和正文文本预填充电子邮件编辑器。handleClick

要在电子邮件编辑器中包含默认文本,请使用该函数。将结果添加到传递到有效负载的页面引用中的属性中。若要使用组件的输入填充电子邮件,请将字段值传递到函数中。encodeDefaultFieldValuesdefaultFieldValuesfieldOverrideencodeDefaultFieldValues

确保您在函数中指定的字段在“发送电子邮件”全局操作的布局中不是只读的。在此示例中,如果“HTML 正文”和“主题”字段为只读,则电子邮件草稿不包含这些字段的预填充文本。encodeDefaultFieldValues

在 HTML 文件中添加一个触发事件处理程序的按钮。

<div>
  <lightning-button
    variant="neutral"
    label="Send an Email"
    onclick={handleClick}>
  </lightning-button>
</div>

然后添加事件处理程序。

import { LightningElement } from "lwc";
import { NavigationMixin } from "lightning/navigation";
import { encodeDefaultFieldValues } from "lightning/pageReferenceUtils";

export default class EmailQuickAction2 extends NavigationMixin(LightningElement) {
  handleClick() {
    var pageRef = {
      type: "standard__quickAction",
      attributes: {
        apiName: "Global.SendEmail",
      },
      state: {
        recordId: "00QB000000BLjUrMAL",
        defaultFieldValues: encodeDefaultFieldValues({
          HtmlBody: "Pre-populated text for the email body.",
          Subject: "Pre-populated Subject of the Email",
        }),
      },
    };

    this[NavigationMixin.Navigate](pageRef);
  }
}

在独立 Aura 应用程序中使用组件

您可以在独立的 Aura 应用程序中使用自定义 Lightning Web 组件。独立应用程序在开发人员控制台中也称为 Lightning 应用程序。

引用 Lightning Web 组件的命名约定是 ,这与在 Aura 组件中使用 Lightning Web 组件的命名约定相同。<namespace:camelCaseComponentName>

这个独立的 Aura 应用程序使用默认命名空间中的 Lightning Web 组件。myComponentc

<!-- sampleApp.app -->
<aura:application>
  <c:myComponent />
</aura:application>

为 Outlook 和 Gmail 集成创建组件

创建 Lightning App Builder 中提供的自定义 Lightning Web 组件,以添加到 Outlook 和 Gmail 集成的电子邮件应用程序窗格中。

在组件的配置文件中,添加目标并设置为 。lightning__Inbox<isExposed>true

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <isExposed>true</isExposed>
    <apiVersion>46.0</apiVersion>
    <targets>
        <target>lightning__Inbox</target>
    </targets>
    <description>Sample Email Application Pane</description>
</LightningComponentBundle>

目标将属性添加到组件中。在组件的 JavaScript 文件中声明属性。此示例声明它在模板中使用的三个属性。lightning__Inbox

// sampleEmailAppPane.js

import { LightningElement, api } from "lwc";

export default class SampleEmailAppPane extends LightningElement {
  @api messageBody;
  @api subject;
  @api people;
}

访问组件模板中的属性。

<!-- sampleEmailAppPane.html -->

<template>
  <div>Sample Email Application Pane</div>

  <template for:each={people.from} for:item="from">
    <div key={from.email}>{from.email}</div>
  </template>

  <template for:each={people.to} for:item="to">
    <div key={to.email}>{to.email}</div>
  </template>

  <div>{subject}</div>

  <div>{messageBody}</div>
</template>

注意

通常,要允许用户在 Lightning App Builder 中设置组件的属性,您必须将该属性添加到组件的配置文件中。但是,除非要设置其默认值,否则无需向组件的配置文件添加属性。lightning__Inbox

日期

数据类型:对象

事件的日期。

dates: {
  start: 'value',
  end: 'value'
}

支持的来源:event

电子邮件

数据类型:数组

简单数组中收件人的电子邮件地址。如果您不关心地址是在“收件人”、“发件人”或“抄送”字段中,或者不关心收件人是哪种类型的与会者,请使用此属性。

["abc@salesforce.com", "def@salesforce.com"];

支持的来源: ,emailevent

位置

数据类型:字符串

事件的位置。

支持的来源:event

messageBody(消息正文)

数据类型:字符串

纯文本电子邮件的邮件正文。不保留 HTML 格式。

支持的来源: ,emailevent

模式

数据类型:字符串

访问模式。可能的值:、'view''edit'

支持的来源: ,emailevent

数据类型:对象

当前电子邮件或事件中收件人的电子邮件地址。人员属性的形状会根据源属性的值而变化。当该属性设置为 时,对象将包含这些元素。sourceemailpeople

{
  to: [
    {
      name: 'name',
      email: 'email'
    }
  ],
  cc: [
    {
      name: 'name',
      email: 'email'
    }
  ],
  from: [
    {
      name: 'senderName',
      email: 'senderEmail'
    }
  ]
}

当该属性设置为 时,对象将包含这些元素。sourceeventpeople

{
  requiredAttendees: [
    {
      name: 'attendeeName',
      email: 'email'
    }
  ],
  optionalAttendees: [
    {
      name: 'optAttendeeName',
      email: 'email'
    }
  ],
  organizer: [
    {
      name: 'organizerName',
      email: 'senderEmail'
    }
  ]
}

支持的来源: ,emailevent

数据类型:字符串

可能的值:、'email''event'

支持的来源: ,emailevent

主题

对象类型:字符串

电子邮件的主题。

支持的来源: ,emailevent

注意

若要确保自定义组件在电子邮件应用程序窗格中正确显示,请使其能够调整为可变宽度。请参阅使组件具有宽度感知能力。

在 Flow Builder 中自定义操作和屏幕组件 UI

开发一个自定义属性编辑器,当管理员在 Flow Builder 中配置自定义屏幕组件或可调用操作时,该编辑器可为其提供简化的 UI。自定义属性编辑器是一个 Lightning Web 组件,它提供用于输入输入值的自定义 UI。

将自定义属性编辑器与标准属性编辑器进行比较

如果没有自定义属性编辑器,当管理员在 Flow Builder 中配置自定义流屏幕组件或可调用操作时,UI 由组件输入值的文本框或组合框组成。自定义属性编辑器的 UI 可以由任何输入组件组成,并且可以使用自定义样式。此示例显示了一个没有自定义属性编辑器的自定义流屏幕组件。该组件显示将数据传递到流中的输入属性的文本框和组合框。

相反,请创建一个自定义属性编辑器,为管理员提供简化的体验。此示例显示了一个自定义流屏幕组件,该组件具有自定义属性编辑器,该编辑器使用自定义标签和滑块组件作为输入值。

示例:可调用操作的自定义属性编辑器

此示例创建一个可调用操作及其自定义属性编辑器。在 Flow Builder 中,管理员为“发送 HTML 电子邮件”可调用操作设置输入值。当用户运行流时,可调用操作会发送电子邮件。

此 Apex 类文件定义了可作为可调用操作运行的方法及其输入变量。注释标识可作为可调用操作运行的可调用方法。注释标识可调用方法使用的变量。sendEmails@InvocableMethod@InputVariable

可调用方法在修饰符中注册自定义属性编辑器。除非组织具有自定义命名空间,否则命名空间。如果组织具有自定义命名空间,请使用该命名空间注册自定义属性编辑器。在此示例中,自定义属性编辑器的名称为 。configurationEditorcc-html-email-editor

// HtmlEmailAction.cls
global class HtmlEmailAction {
    global class EmailActionRequest {
        @InvocableVariable
        global String senderName;

        @InvocableVariable
        global String replyToEmail;

        @InvocableVariable
        global String recipientName;

        @InvocableVariable
        global String sendToEmail;

        @InvocableVariable
        global String subject;

        @InvocableVariable
        global String htmlBody;
    }

    global class EmailActionResult {
        @InvocableVariable
        global Boolean isSuccess;

        @InvocableVariable
        global String errorMessage;
    }

    @InvocableMethod(label='Send HTML Email' configurationEditor='c-html-email-editor')
    global static List<EmailActionResult> sendEmails(List<EmailActionRequest> requests) {
        List<EmailActionResult> results = new List<EmailActionResult>();

        for(EmailActionRequest request : requests){
            results.add(sendEmail(request));
        }

        return results;
    }

    public static EmailActionResult sendEmail(EmailActionRequest request) {
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

        String[] sendToEmail = new String[]{ request.sendToEmail };
        mail.setToAddresses(sendToEmail);
        mail.setSenderDisplayName(request.senderName);
        mail.setReplyTo(request.replyToEmail);
        mail.setSubject(request.subject);
        mail.setHtmlBody(request.htmlBody);
        mail.setOptOutPolicy('FILTER');

        Messaging.SingleEmailMessage[] messages = new List<Messaging.SingleEmailMessage>();
        messages.add(mail);

        Messaging.SendEmailResult[] results = Messaging.sendEmail(messages);
        EmailActionResult emailActionResult = new EmailActionResult();

        for(Messaging.SendEmailResult result :results) {
            if(result.IsSuccess()) {
                emailActionResult.isSuccess = true;
            } else {
                emailActionResult.isSuccess = false;
                Messaging.SendEmailError[] errors = result.getErrors();
                if (errors.size() > 0 ){
                    emailActionResult.errorMessage = errors[0].getMessage();
                }
            }
        }

        return emailActionResult;
    }
}

这些 HTML、CSS、JavaScript 和配置文件定义操作的自定义属性编辑器。

HTML 模板定义 Flow Builder 中自定义属性编辑器的 UI。

<!--htmlEmailEditor.html-->
<template>
  <div class="slds-m-bottom_x-small">
    <h2 class="slds-text-heading_medium slds-p-around_xx-small lgc-bg-inverse">
      Sender Information
    </h2>

    <div class="slds-p-around_xx-small lgc-bg">
      <lightning-input
        type="text"
        label="Sender Name"
        placeholder="Enter sender name here..."
        value={senderName}
        onchange={handleSenderNameChange}
      >
      </lightning-input>

      <lightning-input
        type="email"
        label="Reply-To Email Address"
        placeholder="Enter reply-to email address here..."
        value={replyToEmail}
        onchange={handleReplyToEmailChange}
      >
      </lightning-input>
    </div>
  </div>

  <div class="slds-m-bottom_x-small">
    <h2 class="slds-text-heading_medium slds-p-around_xx-small lgc-bg-inverse">
      Recipient Information
    </h2>

    <div class="slds-p-around_xx-small lgc-bg">
      <lightning-input
        type="text"
        label="Recipient Name"
        placeholder="Enter recipient name here..."
        value={recipientName}
        onchange={handleRecipientNameChange}
      >
      </lightning-input>

      <lightning-input
        type="email"
        label="Send-To Email Address"
        placeholder="Enter send-to email address here..."
        value={sendToEmail}
        onchange={handleSendToEmailChange}
        required
      >
      </lightning-input>
    </div>
  </div>

  <div class="slds-m-top_small">
    <h2 class="slds-text-heading_medium slds-p-around_xx-small lgc-bg-inverse">Subject and Body</h2>

    <div class="slds-p-around_xx-small lgc-bg">
      <lightning-input
        type="text"
        label="Subject"
        placeholder="Enter subject here..."
        value={subject}
        onchange={handleSubjectChange}
      >
      </lightning-input>

      <div class="row">
        <lightning-textarea
          name="body"
          label="HTML Body"
          placeholder="Enter html body here..."
          value={htmlBody}
          onchange={handleHtmlBodyChange}
        >
        </lightning-textarea>
      </div>
    </div>
  </div>
</template>

此示例显示自定义属性编辑器 UI。

初始化自定义属性编辑器后,JavaScript 类会从 Flow Builder 接收流元数据的副本。当管理员在自定义属性编辑器中更改值时,自定义属性编辑器会调度一个事件以将更改传播回 Flow Builder。

注意

使用属性从流中捕获数据。使用事件在运行时报告对流的更改。@api

// htmlEmailEditor.js
import { LightningElement, api } from "lwc";
export default class HtmlEmailEditor extends LightningElement {
  @api
  inputVariables;

  get senderName() {
    const param = this.inputVariables.find(({ name }) => name === "senderName");
    return param && param.value;
  }

  get replyToEmail() {
    const param = this.inputVariables.find(({ name }) => name === "replyToEmail");
    return param && param.value;
  }

  get recipientName() {
    const param = this.inputVariables.find(({ name }) => name === "recipientName");
    return param && param.value;
  }

  get sendToEmail() {
    const param = this.inputVariables.find(({ name }) => name === "sendToEmail");
    return param && param.value;
  }

  get subject() {
    const param = this.inputVariables.find(({ name }) => name === "subject");
    return param && param.value;
  }

  get htmlBody() {
    const param = this.inputVariables.find(({ name }) => name === "htmlBody");
    return param && param.value;
  }

  @api validate() {
    const validity = [];
    if (
      !this.isValidEmailAddress(this.sendToEmail) ||
      !this.isValidEmailAddress(this.replyToEmail)
    ) {
      validity.push({
        key: "SendToAddress",
        errorString: "You have entered an invalid email format.",
      });
    }
    return validity;
  }

  isValidEmailAddress(email) {
    const emailRegex = /^\w+([\.-]?\w+)+@\w+([\.:]?\w+)+(\.[a-zA-Z0-9]{2,3})+$/;
    return emailRegex.test(email);
  }

  handleSenderNameChange(event) {
    this.handleChange(event, "senderName");
  }

  handleReplyToEmailChange(event) {
    this.handleChange(event, "replyToEmail");
  }

  handleRecipientNameChange(event) {
    this.handleChange(event, "recipientName");
  }

  handleSendToEmailChange(event) {
    this.handleChange(event, "sendToEmail");
  }

  handleSubjectChange(event) {
    this.handleChange(event, "subject");
  }

  handleHtmlBodyChange(event) {
    this.handleChange(event, "htmlBody");
  }

  handleChange(event, name) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const valueChangedEvent = new CustomEvent("configuration_editor_input_value_changed", {
        bubbles: true,
        cancelable: false,
        composed: true,
        detail: {
          name,
          newValue,
          newValueDataType: "String",
        },
      });
      this.dispatchEvent(valueChangedEvent);
    }
  }
}

Flow Builder 具有用于与自定义属性编辑器进行通信的 JavaScript 接口。此 JavaScript 类使用 和 接口。inputVariablesvalidate

初始化自定义属性编辑器后,从 Flow Builder 接收可调用操作中输入变量的值。inputVariables

数据结构包括每个输入变量的名称、值和数据类型。inputVariables

[
  {
    name: "senderName",
    value: "Test Inc",
    valueDataType: "String",
  },
];

方法(如 和 )获取每个输入变量,以便在自定义属性编辑器中使用。getget senderName()get replyToEmail()value

当管理员在 Flow Builder 的屏幕编辑器 UI 中单击“完成”时,Flow Builder 会在自定义属性编辑器中评估函数。如果函数返回 和 数据结构,则屏幕编辑器会显示错误数,并阻止管理员在屏幕编辑器中保存更改。validatekeyerrorString

注意

Flow Builder 仅显示错误数。若要显示错误字符串,请在方法中编写代码。有关更多信息,请参见自定义属性编辑器 JavaScript 接口。validate

当管理员在自定义属性编辑器中输入输入值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleChangeconfiguration_editor_input_value_changed

下面是 的配置文件。htmlEmailEditor

<!--htmlEmailEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <!--isExposed can be true or false-->
    <isExposed>true</isExposed>
</LightningComponentBundle>

示例:屏幕组件的自定义属性编辑器

此示例为显示卷的自定义流屏幕组件创建自定义属性编辑器。管理员使用卷组件的自定义属性编辑器中的滑块设置卷。当用户运行流程时,流程屏幕会显示管理员设置的音量级别。

以下 HTML、JavaScript 和配置文件定义自定义流屏幕组件。volume

<!--volume.html-->
<template>
  <div>
    <h1 class="slds-text-heading_medium">Volume</h1>
  </div>

  <div class="slds-p-top_xxx-small">
    <p>
      Your selected volume is:
      <lightning-formatted-number value={volume}></lightning-formatted-number>
    </p>
  </div>
</template>

JavaScript 类定义一个公共属性。volume

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

export default class Volume extends LightningElement {
  @api volume;
}

要在 Flow Builder 中公开公共属性,请在配置文件中定义它:。该特性确定属性是否可以接收来自流的输入。本示例中使用的默认值为 。若要使属性仅可用于输入或输出,请将该特性设置为 或 。volume<property name="volume" type="Integer"/>roleinputAndOutputroleinputOnlyoutputOnly

在配置文件中,使用属性注册自定义属性编辑器:。除非组织具有自定义命名空间,否则请使用命名空间。如果组织具有自定义命名空间,请使用该命名空间。configurationEditor<targetConfig targets="lightning__FlowScreen" configurationEditor="c-volume-editor">c

<!-- volume.js-meta.xml-->
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>49.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__FlowScreen</target>
  </targets>
  <masterLabel>Volume</masterLabel>
  <targetConfigs>
    <targetConfig targets="lightning__FlowScreen" configurationEditor="c-volume-editor">
      <property name="volume" type="Integer"/>
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

以下 HTML、JavaScript 和配置文件定义组件的自定义属性编辑器。volume

自定义属性编辑器的 HTML 模板定义 UI,该 UI 使用滑块基础 Lightning Web 组件。

<!--volumeEditor.html-->
<template>
  <div class="slds-p-around_xx-small">
    <lightning-slider label="Volume" step="10" value={volume} onchange={handleChange}>
    </lightning-slider>
  </div>
</template>

初始化自定义属性编辑器时,其 JavaScript 类会从 Flow Builder 接收流元数据的副本。当管理员在自定义属性编辑器中进行更新时,它会调度一个事件以将更改传播回 Flow Builder。

注意

使用属性从流中捕获数据。使用事件在运行时报告对流的更改。@api

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

export default class VolumeEditor extends LightningElement {
  _inputVariables = [];

  @api
  get inputVariables() {
    return this._inputVariables;
  }

  // Set a field with the data that was stored from the flow.
  // This data includes the public volume property of the custom volume
  // component.
  set inputVariables(variables) {
    this._inputVariables = variables || [];
  }

  // Get the value of the volume input variable.
  get volume() {
    const param = this.inputVariables.find(({ name }) => name === "volume");
    return param && param.value;
  }

  @api
  validate() {
    const volumeCmp = this.template.querySelector("lightning-slider");
    const validity = [];
    if (this.volume < 0 || this.volume > 100) {
      volumeCmp.setCustomValidity("The slider range is between 0 and 100.");
      validity.push({
        key: "Slider Range",
        errorString: "The slider range is between 0 and 100.",
      });
    } else {
      volumeCmp.setCustomValidity("");
    }
    volumeCmp.reportValidity();
    return validity;
  }

  handleChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const valueChangedEvent = new CustomEvent("configuration_editor_input_value_changed", {
        bubbles: true,
        cancelable: false,
        composed: true,
        detail: {
          name: "volume",
          newValue,
          newValueDataType: "Number",
        },
      });
      this.dispatchEvent(valueChangedEvent);
    }
  }
}

Flow Builder 具有用于与自定义属性编辑器进行通信的 JavaScript 接口。此 JavaScript 类使用 和 接口。inputVariablesvalidate

此示例定义了 的 getter 和 setter。初始化自定义属性编辑器时,setter 从 Flow Builder 接收屏幕组件中公共属性的值。在此示例中,我们使用将流元数据存储在字段中的约定,但您可以根据需要命名该字段。inputVariablesinputVariables_inputVariables

中的数据结构包括每个输入变量的名称、值和数据类型。_inputVariables

[
  {
    name: "volume",
    value: "10",
    valueDataType: "Number",
  },
];

该方法获取用于卷 UI 的属性。volumevaluevolume

当管理员在 Flow Builder 的屏幕编辑器 UI 中单击“完成”时,Flow Builder 会评估每个自定义属性编辑器中的函数。如果函数返回 和 数据结构,则 Flow Builder 会显示错误数,并阻止管理员在屏幕编辑器中保存更改。validatekeyerrorString

注意

Flow Builder 仅显示错误数。若要显示错误字符串,请编写代码。此示例使用组件的函数显示自定义错误消息。使用 interface 方法。lightning-slidersetCustomValidity()validate

当管理员在自定义属性编辑器中输入 volume 的值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleChangeconfiguration_editor_input_value_changed

这是 的配置文件。volumeEditor

<!--volumeEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>49.0</apiVersion>
  <!--isExposed can be true or false-->
  <isExposed>true</isExposed>
</LightningComponentBundle>

示例:可调用操作的通用 SObject 输入

此示例为使用泛型 sObject 输入参数的可调用操作创建自定义属性编辑器。可调用操作是可以添加到流程中的 Apex 方法。在 Flow Builder 中,管理员使用选择列表字段来设置可调用操作的输入参数:记录变量的对象、记录变量和用于存储输出的对象。当用户运行此示例的流时,可调用操作将存储记录集合中的第一条记录。

此 Apex 类文件定义了可作为可调用操作运行的方法及其输入变量。注释标识可以作为可调用操作运行的方法。注释标识可调用方法使用的变量。selectRecord@InvocableMethod@InvocableVariable

可调用方法在修饰符中注册自定义属性编辑器。除非组织具有自定义命名空间,否则命名空间。如果组织具有自定义命名空间,请使用该命名空间注册自定义属性编辑器。在此示例中,自定义属性编辑器的名称为 。configurationEditorcc-select-record-editor

// SelectRecordAction.cls
public with sharing class SelectRecordAction {
    @InvocableMethod(configurationEditor='c-select-record-editor')
    public static List <Results> selectRecord(List<Requests> requestList) {
        List<SObject> inputCollection = requestList[0].inputCollection;
        //Store the first input record for output
        SObject outputMember = inputCollection[0];
        Results response = new Results();
        response.outputMember = outputMember;
        List<Results> responseWrapper= new List<Results>();
        responseWrapper.add(response);
        return responseWrapper;
    }

public class Requests {
    @InvocableVariable(label='Object for Input' description='Records for Input')
    public List<SObject> inputCollection;
    }

public class Results {
    @InvocableVariable(label='Object for Storing Output' description='Records for Output')
    public SObject outputMember;
    }
}

这些 HTML、CSS、JavaScript 和配置文件定义操作的自定义属性编辑器。

HTML 模板定义 Flow Builder 中自定义属性编辑器的 UI。

<!--selectRecordEditor.html-->
<template>
  <lightning-combobox
    name="inputType"
    label="Object for Record Variable"
    value={inputType}
    placeholder="Select object..."
    options={typeOptions}
    onchange={handleInputTypeChange}
  >
  </lightning-combobox>

  <lightning-combobox
    name="outputType"
    label="Object for Storing Output"
    value={outputType}
    placeholder="Select object..."
    options={typeOptions}
    onchange={handleOutputTypeChange}
  >
  </lightning-combobox>

  <lightning-combobox
    name="inputValue"
    label="Record Variable"
    value={inputValue}
    placeholder="Select record variable..."
    options={valueOptions}
    onchange={handleValueChange}
  >
  </lightning-combobox>

  <div class="slds-p-bottom_medium"></div>
</template>

此示例显示自定义属性编辑器 UI。

初始化自定义属性编辑器后,JavaScript 类会从 Flow Builder 接收流元数据的副本。当管理员在自定义属性编辑器中更改值时,自定义属性编辑器会调度一个事件以将更改传播回 Flow Builder。

注意

使用属性从流中捕获数据。使用事件在运行时报告对流的更改。@api

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

export default class SelectRecordEditor extends LightningElement {
  @api
  inputVariables;

  @api
  genericTypeMappings;

  @api
  builderContext;

  get inputValue() {
    const param = this.inputVariables.find(({ name }) => name === "inputCollection");
    return param && param.value;
  }

  get inputType() {
    const type = this.genericTypeMappings.find(({ typeName }) => typeName === "T__inputCollection");
    return type && type.typeValue;
  }

  get outputType() {
    const type = this.genericTypeMappings.find(({ typeName }) => typeName === "U__outputMember");
    return type && type.typeValue;
  }

  get typeOptions() {
    return [
      { label: "Account", value: "Account" },
      { label: "Case", value: "Case" },
      { label: "Lead", value: "Lead" },
    ];
  }

  get valueOptions() {
    const variables = this.builderContext.variables;
    return variables.map(({ name }) => ({
      label: name,
      value: name,
    }));
  }

  handleInputTypeChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const typeChangedEvent = new CustomEvent(
        "configuration_editor_generic_type_mapping_changed",
        {
          bubbles: true,
          cancelable: false,
          composed: true,
          detail: {
            typeName: "T__inputCollection",
            typeValue: newValue,
          },
        },
      );
      this.dispatchEvent(typeChangedEvent);
    }
  }

  handleOutputTypeChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const typeChangedEvent = new CustomEvent(
        "configuration_editor_generic_type_mapping_changed",
        {
          bubbles: true,
          cancelable: false,
          composed: true,
          detail: {
            typeName: "U__outputMember",
            typeValue: newValue,
          },
        },
      );
      this.dispatchEvent(typeChangedEvent);
    }
  }

  handleValueChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const valueChangedEvent = new CustomEvent("configuration_editor_input_value_changed", {
        bubbles: true,
        cancelable: false,
        composed: true,
        detail: {
          name: "inputCollection",
          newValue,
          newValueDataType: "reference",
        },
      });
      this.dispatchEvent(valueChangedEvent);
    }
  }
}

Flow Builder 具有用于与自定义属性编辑器进行通信的 JavaScript 接口。此 JavaScript 类使用 、 和 接口。inputVariablesbuilderContextgenericTypeMappings

初始化自定义属性编辑器后,从 Flow Builder 接收可调用操作中输入变量的值。inputVariables

数据结构包括每个输入变量的名称、值和数据类型。inputVariables

[
  {
    name: "inputType",
    value: "Account",
    valueDataType: "Account",
  },
];

该方法获取每个输入变量的值,以便在自定义属性编辑器中使用。get inputValue()

该接口接收输入变量的值,这些变量是 Flow Builder 中可调用操作中的通用 sObject 数据类型。genericTypeMappings

数据结构包括每个输入的名称和值。必须与使用方法中的注释定义的泛型 sObject 输入的名称匹配,例如 。 在输入名称前面加上,并自动在输出名称前面加上。是通用 sObject 输入的特定值,例如 。typeName@InvocableVariable’T__inputCollection’T__U__typeValueAccount

[
  {
    typeName: "T__inputCollection",
    typeValue: "Account",
  },
];

和方法获取每个输入参数的值,该参数是用于自定义属性编辑器的泛型 sObject 数据类型。get inputType()get outputType()

该方法获取每个对象输入选项的标签和值,以便在自定义属性编辑器中使用。get typeOptions()

该接口提供有关流中元素和资源的数据。builderContext

数据结构包括流中的元素和资源。builderContext

该方法使用变量中的数据作为可调用操作输入参数的自定义属性编辑器上的输入值选项。get valueOptions()

当管理员在自定义属性编辑器中为记录变量的对象输入值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleInputTypeChangeconfiguration_editor_generic_type_mapping_changed

当管理员在自定义属性编辑器中输入用于存储输出的对象的值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleOutputTypeChangeconfiguration_editor_generic_type_mapping_changed

当管理员在自定义属性编辑器中输入“记录变量”的值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleValueChangeconfiguration_editor_input_value_changed

下面是 的配置文件。selectRecordEditor

<!--selectRecordEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <!--isExposed can be true or false-->
    <isExposed>true</isExposed>
</LightningComponentBundle>

示例:屏幕组件的通用 SObject 输入

此示例为使用泛型输入值的自定义流屏幕组件创建自定义属性编辑器。管理员使用选择列表字段为记录变量的对象和记录变量设置屏幕组件的输入值。当用户运行此示例的流时,屏幕组件将显示记录集合中的第一条记录。sObject

以下 HTML、JavaScript 和配置文件定义自定义流屏幕组件。displayRecord

<!--displayRecord.html-->
<template>
  <p>Record Name: {inputValue.Name}</p>
</template>

JavaScript 类定义一个公共属性。inputValue

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

export default class DisplayRecord extends LightningElement {
  @api
  inputValue;
}

要在 Flow Builder 中公开公共属性,请在配置文件中定义它。若要将属性定义为泛型数据类型,请定义子标记。子标记扩展泛型数据类型。的属性引用子标记的属性。例如,如果 ,则 .子标记的属性必须位于大括号内。要将属性定义为泛型集合数据类型,请追加 ,例如 。inputValuesObjectpropertyTypesObjecttypeinputValuenamepropertyTypepropertyType name="T"property type="{T}"typepropertysObject[]property type="{T[]}"

该特性确定属性是否可以接收来自流的输入。缺省值为 。若要使属性仅可用于输入或输出,请将该特性设置为 或 。roleinputAndOutputroleinputOnlyoutputOnly

在配置文件中,使用该特性注册自定义属性编辑器。configurationEditor

除非组织具有自定义命名空间,否则请使用命名空间。如果组织具有自定义命名空间,请使用该命名空间。c

<!-- displayRecord.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>50.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__FlowScreen</target>
  </targets>
  <masterLabel>Display Record</masterLabel>
  <targetConfigs>
    <targetConfig targets="lightning__FlowScreen" configurationEditor="c-display-record-editor">
      <propertyType name="T" extends="SObject" label="Input Type" description="Generic sObject data type used for input sObject properties" />
      <property name="inputValue" type={T} label="input value" role="inputOnly" />
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

以下 HTML、JavaScript 和配置文件定义组件的自定义属性编辑器。displayRecord

自定义属性编辑器的 HTML 模板定义了 UI,该 UI 使用组合框基础 Lightning Web 组件。

<!--displayRecordEditor.html-->
<template>
  <lightning-combobox
    name="inputType"
    label="Object for Record Variable"
    value="{inputType}"
    placeholder="Select object..."
    options="{typeOptions}"
    onchange="{handleInputTypeChange}"
  >
  </lightning-combobox>

  <lightning-combobox
    name="inputValue"
    label="Record Variable"
    value="{inputValue}"
    placeholder="Select record variable..."
    options="{valueOptions}"
    onchange="{handleValueChange}"
  >
  </lightning-combobox>
</template>

初始化自定义属性编辑器时,其 JavaScript 类会从 Flow Builder 接收流元数据的副本。当管理员在自定义属性编辑器中进行更新时,它会调度一个事件以将更改传播回 Flow Builder。

注意

使用属性从流中捕获数据。使用事件在运行时报告对流的更改。@api

// displayRecordEditor.js
import { LightningElement, api } from 'lwc';

export default class DisplayRecordEditor extends LightningElement {
  @api
  inputVariables;

  @api
  genericTypeMappings;

  @api
  builderContext;

  get inputValue() {
    const param = this.inputVariables.find(({ name }) => name === 'inputValue');
    return param && param.value;
  }

  get inputType() {
    const type = this.genericTypeMappings.find(
      ({ typeName }) => typeName === 'T'
    );
    return type && type.typeValue;
  }

  get typeOptions() {
    return [
      { label: 'Account', value: 'Account' },
      { label: 'Case', value: 'Case' },
      { label: 'Lead', value: 'Lead' },
    ];
  }

  get valueOptions() {
    const variables = this.builderContext.variables;
    return variables.map(({ name }) => ({
      label: name,
      value: name,
    }));
  }

  handleInputTypeChange(event) {
    if (event && event.detail) {
    const newValue = event.detail.value;
    const typeChangedEvent = new CustomEvent(
      'configuration_editor_generic_type_mapping_changed',
      {
        bubbles: true,
        cancelable: false,
        composed: true,
        detail: {
          typeName: 'T',
          typeValue: newValue
        },
      }
    );
    this.dispatchEvent(typeChangedEvent);
    }
  }

  handleValueChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const valueChangedEvent = new CustomEvent(
        'configuration_editor_input_value_changed',
        {
          bubbles: true,
          cancelable: false,
          composed: true,
          detail: {
            name: 'inputValue',
            newValue,
            newValueDataType: 'reference',
          },
        }
      );
    this.dispatchEvent(valueChangedEvent);
    }
  }
}

Flow Builder 具有用于与自定义属性编辑器进行通信的 JavaScript 接口。此 JavaScript 类使用 、 和 接口。inputVariablesbuilderContextgenericTypeMappings

初始化自定义属性编辑器后,从 Flow Builder 接收屏幕组件中公共属性的值。inputVariables

数据结构包括每个输入变量的名称、值和数据类型。inputVariables

[
  {
    name: "inputType",
    value: "Account",
    valueDataType: "Account",
  },
];

获取要在自定义属性编辑器中使用的属性的值。get inputValue()inputValue

该接口从 Flow Builder 接收输入变量的值,这些变量是屏幕组件中的泛型数据类型。genericTypeMappingssObject

数据结构包括每个输入的名称和值。是泛型输入的名称;例如。是泛型输入的特定值;例如。typeNamesObject'T'typeValuesObjectAccount

[
  {
    typeName: "T",
    typeValue: "Account",
  },
];

获取要在自定义属性编辑器中使用的属性的数据类型。get inputType()inputValue

该方法获取每个对象输入选项的标签和值,以便在自定义属性编辑器中使用。get typeOptions()

该接口提供有关流中元素和资源的数据。builderContext

{
  actionCalls: [],
  apexPluginCalls: [],
  constants: [],
  formulas: [],
  recordCreates: [],
  recordDeletes: [],
  recordLookups: [],
  recordUpdates: [],
  screens: [],
  stages: [],
  textTemplates: [],
  variables: []
}

该方法使用来自 的数据作为屏幕组件输入的自定义属性编辑器上的输入值选项。get valueOptions()variables

当管理员在自定义属性编辑器中为记录变量的对象输入值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleInputTypeChangeconfiguration_editor_generic_type_mapping_changed

当管理员在自定义属性编辑器中输入“记录变量”的值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleValueChangeconfiguration_editor_input_value_changed

这是 的配置文件。displayRecordEditor

<!--displayRecordEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>50.0</apiVersion>
  <!--isExposed can be true or false-->
  <isExposed>true</isExposed>
</LightningComponentBundle>

示例:可调用操作的 SObject 输入

此示例创建一个自定义属性编辑器组件,该组件以使用 Contact 对象输入参数的可调用操作命名。可调用操作是可以添加到流程中的 Apex 方法。在 Flow Builder 中,管理员使用文本字段来设置可调用操作的联系人输入参数。当用户运行此示例的流时,可调用操作会存储一个新联系人。createContactActionEditor

此 Apex 类文件定义了可作为可调用操作运行的方法及其输入变量。注释标识可以作为可调用操作运行的方法。注释标识可调用方法使用的变量。createContact@InvocableMethod@InvocableVariable

可调用方法在修饰符中注册自定义属性编辑器组件。除非组织具有自定义命名空间,否则组件命名空间。如果组织具有自定义命名空间,请使用该命名空间注册自定义属性编辑器组件。在此示例中,组件的名称为 。configurationEditorcc-create-contact-action-editor

//CreateContactAction.cls
global class CreateContactAction {
    global class CreateContactRequest {

        @InvocableVariable
        global Contact contact;
    }

    global class CreateContactResult {
        @InvocableVariable
        global Boolean isSuccess;

        @InvocableVariable
        global String errorMessage;

        @InvocableVariable
        global String contactId;
   }

    @InvocableMethod(label='Create Contact' configurationEditor='c-create-contact-action-editor')
    global static List<CreateContactResult> createContact(List<CreateContactRequest> requests) {
        List<CreateContactResult> results = new List<CreateContactResult>();
            for(CreateContactRequest request : requests){
        results.add(insertContact(request));
        }
        return results;
    }

    public static CreateContactResult insertContact(CreateContactRequest request) {
        List<Contact> contactList = new List<Contact>();
        contactList.add(request.contact);

        Database.SaveResult[] srList = Database.insert(contactList, true);

        CreateContactResult contactResult = new CreateContactResult();
            for(Database.SaveResult sr: srList) {
                if (sr.isSuccess()) {
                    contactResult.isSuccess = sr.isSuccess();
                    contactResult.contactId = sr.getId();
                }
                else {
                    for(Database.Error err : sr.getErrors()) {
                        contactResult.errorMessage = err.getStatusCode() + ': ' + err.getMessage();
                    }
                }
            }

    return contactResult;
    }
}

下面是 的配置文件。CreateContactAction

<!--CreateContactAction.cls-meta.xml-->

<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="urn:metadata.tooling.soap.sforce.com" fqn="CreateContactAction">
    <apiVersion>53.0</apiVersion>
    <status>Active</status>
</ApexClass>

这些 HTML、CSS、JavaScript 和配置文件定义操作的自定义属性编辑器组件。createContactActionEditor

组件的 HTML 模板定义了 Flow Builder 中自定义属性编辑器的 UI。

<!--createContactActionEditor.html-->

<template>
  <div class="slds-m-top_small">
    <h2 class="slds-text-heading_medium slds-p-around_xx-small lgc-bg-inverse">
      Contact Primary Information
    </h2>

    <lightning-input
      label="First Name"
      value={defaultContact.FirstName}
      onchange={handleFirstNameChange}
    >
    </lightning-input>

    <lightning-input
      label="Last Name"
      value={defaultContact.LastName}
      onchange={handleLastNameChange}
    >
    </lightning-input>
  </div>

  <div class="slds-m-top_small">
    <h2 class="slds-text-heading_medium slds-p-around_xx-small lgc-bg-inverse">Contact Source</h2>

    <lightning-input
      label="European Country Code"
      value={defaultContact.European_Country_Code__c}
      onchange={handleCountryCodeChange}
    >
    </lightning-input>
  </div>
</template>

此示例显示自定义属性编辑器 UI。

初始化自定义属性编辑器组件时,其 JavaScript 类会从 Flow Builder 接收流元数据的副本。当管理员在自定义属性编辑器中更改值时,自定义属性编辑器组件会调度一个事件以将更改传播回 Flow Builder。

注意

使用属性从流中捕获数据。使用事件在运行时报告对流的更改。@api

//createContactActionEditor.js

import { LightningElement, api } from "lwc";

export default class CreateContactActionEditor extends LightningElement {
  @api
  inputVariables;

  defaultContact = {
    attributes: {
      type: "Contact",
    },
    European_Country_Code__c: "FRA",
  };

  get contact() {
    const param = this.inputVariables.find(({ name }) => name === "contact");
    return param && param.value;
  }

  handleFirstNameChange(event) {
    this.defaultContact["FirstName"] = event.detail.value;
    this.handleContact();
  }

  handleLastNameChange(event) {
    this.defaultContact["LastName"] = event.detail.value;
    this.handleContact();
  }

  handleCountryCodeChange(event) {
    this.defaultContact["European_Country_Code__c"] = event.detail.value;
    this.handleContact();
  }

  handleContact() {
    const newValue = JSON.stringify(this.defaultContact);
    const valueChangedEvent = new CustomEvent("configuration_editor_input_value_changed", {
      bubbles: true,
      cancelable: false,
      composed: true,
      detail: {
        name: "contact",
        newValue,
        newValueDataType: "SObject",
      },
    });
    this.dispatchEvent(valueChangedEvent);
  }
}

Flow Builder 具有用于与自定义属性编辑器进行通信的 JavaScript 接口。此 JavaScript 类使用该接口。inputVariables

初始化自定义属性编辑器后,从 Flow Builder 接收可调用操作中输入变量的值。缺省值为 type 和 设置。当您对 sObject 数据类型的输入使用文本值时,请指定该值,例如 。inputVariablesEuropean_Country_Code__ctypeContact

数据结构包括每个输入变量的名称、值和数据类型。inputVariables

[{
    name: 'contact',
    value: '{
        "attributes": {
            "type": "Contact"
        },
        "FirstName" : "",
        "LastName": "",
        "European_Country_Code__c" : "FRA"
    }',
    valueDataType: 'SObject'
}]

该方法获取输入变量的值,以便在自定义属性编辑器中使用。get contact()

当管理员在自定义属性编辑器中输入输入值时,该方法会将事件调度到 Flow Builder。Flow Builder 接收事件并更新流中的值。handleContactconfiguration_editor_input_value_changed

下面是 的配置文件。createContactActionEditor

<!---createContactActionEditor.js-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>53.0</apiVersion>
    <!--isExposed can be true or false-->
    <isExposed>true</isExposed>
</LightningComponentBundle>

自定义属性编辑器 JavaScript 接口

自定义属性编辑器使用这些 JavaScript 函数与 Flow Builder 进行通信。

输入变量

该接口提供有关 FlowScreenField 元数据的数据。inputVariables

_inputVariables = [];

@api
get inputVariables() {
    return this._inputVariables;
}

// Set a field with the data that was stored from the flow.
set inputVariables(variables) {
    this._inputVariables = variables || [];
}

中的数据结构包括每个输入变量的名称、值和数据类型。_inputVariables

[{
    name: 'volume',
    value: '10',
    valueDataType: 'Number'
}]

有关流元数据的更多信息,请参阅流元数据 API。

builder上下文

该接口提供有关流中元素和资源的数据。例如,和数据结构包括输入变量的名称和类型。在 Flow Builder 中,管理员可以将有关其他元素和资源的数据用作屏幕组件输入属性的自定义属性编辑器的输入。builderContextscreensactionCalls

从界面传递到自定义属性编辑器的数据结构包括流中的元素和资源。builderContext

{
    actionCalls: [],
    apexPluginCalls: [],
    constants: [],
    formulas: [],
    recordCreates: [],
    recordDeletes: [],
    recordLookups: [],
    recordUpdates: [],
    screens: [],
    stages: [],
    textTemplates: [],
    variables: []
}

有关流元数据的更多信息,请参阅流元数据 API。

在以下示例中,开发人员创建自定义流屏幕组件及其自定义属性编辑器。在 Flow Builder 中,管理员使用组合框设置电子邮件的发件人姓名。当用户运行流程时,流程屏幕会显示电子邮件的选定名称。

以下 HTML、JavaScript 和配置文件定义自定义流屏幕组件。

<!--htmlEmail.html-->
<template>
  <p>Selected sender's name is: {senderName}</p>
</template>

此 JavaScript 文件定义了一个公共属性。senderName

// htmlEmail.js

import { LightningElement, api } from "lwc";

export default class HTMLEmail extends LightningElement {
  @api senderName;
}

此配置文件注册自定义属性编辑器。

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>
    <masterLabel>HTMLEmail</masterLabel>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen" configurationEditor="c-html-email-editor">
            <property name="senderName" type="String" role="inputOnly" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

以下 HTML、JavaScript 和配置文件定义屏幕组件的自定义属性编辑器。

此 HTML 文件定义 Flow Builder 中自定义属性编辑器的 UI。UI 显示一个组合框基础 Lightning Web 组件。

<!--htmlEmailEditor.html-->
<template>
  <div class="slds-p-around_xx-small">
    <lightning-combobox
      name="senderName"
      label="Sender Display Name"
      value={senderName}
      placeholder="Select Sender"
      options={options}
      onchange={handleChange}
    >
    </lightning-combobox>

    <p class="slds-p-top_xx-small">Selected sender's name is: {senderName}</p>
  </div>
</template>

初始化自定义属性编辑器时,JavaScript 类会从 Flow Builder 接收流元数据的副本。如果管理员在自定义属性编辑器中进行更新,则会调度一个事件以将更改传播回 Flow Builder。

// htmlEmailEditor.js
import { LightningElement, api } from "lwc";
export default class HtmlEmailEditor extends LightningElement {
  @api
  inputVariables;

  @api
  builderContext;

  get senderName() {
    const param = this.inputVariables.find(({ name }) => name === "senderName");
    return param && param.value;
  }

  get options() {
    const variables = this.builderContext.variables;
    return variables.map(({ name, value }) => ({
      label: name,
      value: value.stringValue,
    }));
  }

  handleChange(event) {
    if (event && event.detail) {
      const newValue = event.detail.value;
      const valueChangedEvent = new CustomEvent("Configuration_editor_input_value_changed", {
        bubbles: true,
        cancelable: false,
        composed: true,
        detail: {
          name: "senderName",
          newValue,
          newValueDataType: "String",
        },
      });
      this.dispatchEvent(valueChangedEvent);
    }
  }
}

该方法返回有关使用接口存储的输入变量的信息。该方法返回使用接口方法存储的合并字段列表。自定义属性编辑器将合并字段显示为屏幕组件输入属性的输入值。senderNamesenderNameinputVariablesoptionsbuilderContext

元素信息

该接口提供有关调用自定义属性编辑器的自定义流屏幕组件或自定义操作的数据。使用此接口可以消除屏幕组件或操作的不同实例的歧义。elementInfo

此示例将流元数据存储在 中,但您可以随意命名该字段。_elementInfo

_elementInfo = {};
@api
get elementInfo() {
    return this._elementInfo;
}

// Set a local variable with the data that was stored from flow.
set elementInfo(info) {
    this._elementInfo = info || {};
}

中的数据结构包括流元数据中流元素的 API 名称和类型。_elementInfo

{
    apiName: 'slider',
    type: 'Screen'
}

type 的有效值为:

  • Screen– 屏幕元素
  • Action– 动作元素

驗證

使用该界面在自定义属性编辑器中执行自定义验证。当流程管理员在 Flow Builder 的屏幕编辑器中单击“完成”时,Flow Builder 会评估每个自定义属性编辑器中的函数。如果函数返回 和 数据结构,则屏幕编辑器会显示错误数。validatevalidatekeyerrorString

注意

Flow Builder 仅显示错误数。编写代码以在自定义属性编辑器中显示错误字符串。

此示例将数据存储在字段中,但您可以使用任何字段名称。为了在自定义属性编辑器中显示错误字符串,我们用于查询、设置和报告错误字符串。validitysliderCmp

@api
validate() {
    const sliderCmp = this.template.querySelector('lightning-slider');
    const validity = [];
    if (this.volume < 0 || this.volume > 100) {
        sliderCmp.setCustomValidity('The slider range is between 0 and     100.');
        validity.push({
            key: 'Slider Range',
            errorString: 'The slider range is between 0 and 100.',
        });
    } else {
        sliderCmp.setCustomValidity('');
    }
    sliderCmp.reportValidity();
    return validity;
}

genericTypeMappings

数据结构包括每个输入的名称和值。是泛型输入的名称;例如。是泛型输入的特定值;例如。genericTypeMappingstypeNamesObject‘T__param1’typeValuesObjectAccount

[{
    typeName: 'T__param1',
    typeValue: 'Account'
}]

事件类型

要向 Flow Builder 报告输入值更改,请从自定义属性编辑器的函数中调度事件。设置为 和 true。handleChangebubblescomposed

事件类型的有效值为:

  • configuration_editor_input_value_changed– 一种事件类型,在输入值更改时调度。
  • configuration_editor_input_value_deleted– 删除输入值时调度的事件类型。
  • configuration_editor_generic_type_mapping_changed– 一种事件类型,在通用输入值更改时调度。sObject

这些属性定义用于报告更改的输入。detail

  • name– 屏幕组件的 JavaScript 类中的输入变量。
  • newValue– 输入的新值。
  • newValueDataType– 输入的新数据类型。
  • typeName– 屏幕组件的 JavaScript 类或可调用方法的 Apex 类中的泛型输入。要在屏幕组件的自定义属性编辑器中引用 ,请引用子标记的属性。该值必须位于大括号内,例如。要将属性定义为泛型集合数据类型,请追加 ,例如 。要在自定义属性编辑器中引用可调用操作的 ,请在输入名称和输出名称前面加上 ,例如 。sObjecttypeNamepropertyTypenameproperty type="{T}"sObject[]property type="{T[]}"typeNameT__U__T__param1
  • typeValue– 通用输入或输出的特定值。sObject

例如,当管理员在自定义属性编辑器中输入值 for 时,它会调度一个事件。Flow Builder 接收事件并更新流中的值。volume

handleChange(event) {
    if (event && event.detail) {
        const newValue = event.detail.value;
        const valueChangedEvent = new CustomEvent(
            'configuration_editor_input_value_changed', {
            bubbles: true,
            cancelable: false,
            composed: true,
            detail: {
                name: 'volume',
                newValue,
                newValueDataType: 'Number'
            }
            }
        );
        this.dispatchEvent(valueChangedEvent);
    }
}

流中支持的数据类型

并非所有自定义 Lightning Web 组件数据类型在流程中都受支持。您只能在流程和自定义 Lightning Web 组件之间映射这些类型及其关联的集合类型。

流数据类型Lightning Web 组件数据类型有效值
Apex(流量标签:Apex-Defined)自定义 Apex 类定义字段的 Apex 类。Apex 类中支持的数据类型包括 Boolean、Integer、Long、Decimal、Double、Date、DateTime 和 String。每种数据类型都支持单个值和列表。@AuraEnabled
布尔布尔真实值:true1等效表达式False 值:false0等效表达式
货币整数数值或等效表达式
日期日期"YYYY-MM-DD"或等效表达式
DateTime(流标签:日期/时间)日期时间"YYYY-MM-DDThh:mm:ssZ"或等效表达式
数值或等效表达式
Multipicklist(流标签:Multi-Select Picklist)字符串字符串值或使用分号分隔格式的等效表达式:“Blue;绿;黄色”
选择列表字符串字符串值或等效表达式
sObject(流标签:记录)特定对象的 API 名称,例如 Account 或 Case键值对或等效表达式的映射。流记录值仅映射到类型为特定对象的属性。例如,客户记录变量只能映射到类型为 Account 的属性。流不支持 Object 数据类型。
字符串(流标签:文本)字符串字符串值或等效表达式

Flow 

Flow Builder 允许管理员使用流自动执行业务流程。了解如何开发和配置 Lightning Web 组件,以用作 Flow Builder 中操作和屏幕组件的流屏幕组件和自定义 UI。

创建流屏组件

正如管理员在 Lightning App Builder 中配置记录页面的用户体验一样,他们使用流屏幕组件为流用户执行相同的操作。每个屏幕都由一个或多个屏幕组件组成。

为流屏幕配置组件

要在流程屏幕中使用自定义 Lightning Web 组件,请将一些元数据添加到组件的配置文件中。

componentName.js-meta.xml 文件定义组件的元数据值,包括用于流屏幕的组件的设计配置。

  • 若要使组件在流屏幕中可用,请添加目标。lightning__FlowScreen
  • 若要向组件添加输入字段,请添加属性。targetConfig
  • 若要将属性限制为 或 ,请使用该特性。例如,如果某个属性限制为 ,则用户无法从 Lightning 记录页面设置其值。如果未指定属性,则默认值允许输入和输出。inputOnlyoutputOnlyroleoutputOnlyrole

请参阅 XML 配置文件元素的完整列表。

此示例组件有 5 个流屏幕输入字段。该属性设置为 。startDateinputOnly

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>47.0</apiVersion>
  <isExposed>true</isExposed>
  <masterLabel>Best Component Ever</masterLabel>
  <description>This is a demo component.</description>
  <targets>
    <target>lightning__FlowScreen</target>
  </targets>
  <targetConfigs>
    <targetConfig targets="lightning__FlowScreen">
      <property name="startDate" label="Start Date" type="Date" role="inputOnly" />
      <property name="account" label="Account Chosen" type="@salesforce/schema/Account" />
      <property name="annualRevenue" label="Annual Revenue" type="Integer" role="outputOnly" />
      <property name="name" label="Account Name" type="String" />
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

验证自定义流屏幕组件的用户输入

向自定义屏幕组件添加验证。

要使用 Flow 的验证功能,请在组件的 JavaScript 文件中创建一个调用的函数。validate()

// sampleValidate.js
@api
validate() {
  if(/* true conditions */) {
    return { isValid: true };
    }
  else {
    // If the component is invalid, return the isValid parameter
    // as false and return an error message.
    return {
      isValid: false,
      errorMessage: '/*A message that explains what went wrong.*/'
    };
  }
}

注意

箭头函数表达式不适用于 的组件中的验证。targetlightning__FlowScreen

在自定义 Lightning Web 组件中嵌入流程

嵌入来自任何 Lightning Web 组件的屏幕流,以自定义完成行为、设置自定义样式或从 Lightning Web 组件启动流程。

在自定义 Lightning Web 组件中创建和启动流程

创建并启动从任何 Lightning Web 组件到您的屏幕流。

lightning-flow表示 Lightning 运行时中的流程面试。若要在组件中创建流,请将组件的属性设置为要使用的流的 API 名称。要使用此组件,请先使用 Salesforce Flow Builder 构建流程。该组件包括导航按钮“后退”、“下一步”、“暂停”和“完成”。lightning-flowflow-api-name

注意

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

此示例创建并启动“调查客户”流。

<!-- myComponent.html -->
<template>
  <lightning-flow flow-api-name="Survey_customers"> </lightning-flow>
</template>

您可以通过将属性设置为输入值数组来提供面试的初始输入。flow-input-variables

此示例通过传入流程的初始值来创建和启动面试。它使用事件处理程序处理面试中的更改。onstatuschange

<!-- myComponent.html -->
<template>
  <lightning-flow
    flow-api-name={flowName}
    flow-input-variables={inputVariables}
    onstatuschange={handleStatusChange}>
  </lightning-flow>
</template>
// myComponent.js
import { LightningElement } from "lwc";

export default class MyComponent extends LightningElement {
  opportunityId;
  accountId;
  customerLocation;

  get flowName() {
    if (this.customerLocation === "USA") {
      flowname = "Survey_USA";
    } else {
      flowname = "Survey_General";
    }
  }

  get inputVariables() {
    return [
      {
        // Match with the input variable name declared in the flow.
        name: "OpportunityID",
        type: "String",
        // Initial value to send to the flow input.
        value: this.opportunityId,
      },
      {
        // Match with the input variable name declared in the flow.
        name: "AccountID",
        type: "String",
        // Initial value to send to the flow input.
        value: this.accountId,
      },
    ];
  }

  handleStatusChange(event) {
    if (event.detail.status === "FINISHED") {
      // set behavior after a finished flow interview.
    }
  }
}

使用注意事项

该组件仅支持该属性的活动流。lightning-flowflow-api-name

该事件返回这些参数。onstatuschange

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

参数类型描述
活动阶段对象[]流中 $Flow.ActiveStages 变量的当前值。在 API 版本 42.0 及更高版本中可用。
当前阶段对象流中 $Flow.CurrentStage 变量的当前值。在 API 版本 42.0 及更高版本中可用。
flowTitle (流标题)字符串流的标签。
帮助文本字符串当前屏幕的帮助文本。在 API 版本 42.0 及更高版本中可用。
GUID字符串面试的 GUID。在 API 版本 42.0 及更高版本中可用。
输出变量对象[]流输出变量的当前值。
地位字符串面试的状态。流程面试的有效状态为:STARTED:面试成功开始。PAUSED:采访成功暂停。FINISHED:屏幕完成的流程面试。FINISHED_SCREEN:没有屏幕的流的面试已完成,组件显示默认屏幕,其中包含以下消息:您的流已完成。ERROR:出了点问题,面试失败了。

自定义流的完成行为

每个流组件都包括导航按钮“后退”、“下一步”、“暂停”和“完成”,这些按钮在流中导航。默认情况下,当流程完成时,组件会重新加载新面试的第一个屏幕。若要自定义流程完成时发生的情况,请在状态为 FINISHED 时为操作添加事件处理程序。onstatuschange

设置流输入变量

在自定义组件中嵌入流时,通过初始化流的变量为流提供更多上下文。若要在组件中创建流,请将组件的属性设置为要使用的流的 API 名称。lightning-flowlightning-flowflow-api-name

要使用此组件,请先使用 Salesforce Flow Builder 构建流程。该组件包括导航按钮“后退”、“下一步”、“暂停”和“完成”。

注意

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

为流创建输入变量

在组件的 Javascript 文件中,创建一个映射列表,并将该列表传递给属性。flow-input-variables

注意

您只能在面试开始时设置变量,并且您设置的变量必须允许输入访问。如果引用的变量不允许输入访问,则会忽略设置该变量的尝试。

对于您设置的每个变量,请提供变量的名称、类型和值。对于 type (类型),请使用 API 名称作为流数据类型。例如,对于记录变量,请使用 SObject,对于文本变量,请使用 String。

{
  name : "varName",
  type : "flowDataType",
  value : valueToSet
},
{
  name : "varName",
  type : "flowDataType",
  value : [ value1, value2 ]
}, ...

JavaScript 文件中的此方法返回一个数字变量、一个日期集合变量和几个记录变量的值数组。Flow Builder 中的 Record 数据类型与此处的 SObject 相对应。

// myComponent.js
get inputVariables() {
  return [
    { name: 'numVar', type: 'Number', value: 30 },
    { name: 'dateColl', type: 'String', value: [ '2016-10-27', '2017-08-01' ] },
    // Sets values for fields in the account record (sObject) variable.
    // Id uses the value of the accountId property. Rating uses a string.
    { name: 'account', type: 'SObject', value: {
      'Id': this.accountId,
      'Rating': 'Warm'
    }},
    // Set the contact record (sObject) variable to the value of the contact property.
    // We're assuming the property contains the entire sObject for a contact record.
    { name: 'contact', type: 'SObject', value: this.contact }
  ]
}

示例:将帐户数据传递到流

此示例组件通过 Apex 控制器获取帐户。Apex 控制器通过 JavaScript 文件中的线路将数据传递到流的记录变量。

<!-- myComponent.html -->
<template>
  <lightning-flow flow-api-name="myFlow" flow-input-variables={inputVariables}> </lightning-flow>
</template>
// AccountController.apex
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static Account getAccount() {
        return [SELECT Id, Name, LastModifiedDate, FROM Account LIMIT 1];
    }
}
// myComponent.js
import { LightningElement, track, wire } from "lwc";
import getAccount from "@salesforce/apex/AccountController.getAccount";

export default class MyComponent extends LightningElement {
  @track
  inputVariables = [];

  @wire(getAccount)
  getAccountFromApex({ error, data }) {
    if (error) {
      console.log("Failed to get account data.");
    } else if (data) {
      this.inputVariables = [
        {
          name: "account",
          type: "SOjbect",
          value: data,
        },
      ];
    }
  }
}

从流输出变量中获取值

在组件中嵌入流时,可以显示或引用流的变量值。lightning-flow

要使用此组件,请先使用 Salesforce Flow Builder 构建流程。该组件包括导航按钮“后退”、“下一步”、“暂停”和“完成”。

注意

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

使用 onstatuschange 事件处理程序

若要从流的输出变量中获取值,请使用事件处理程序。输出变量以数组形式返回。该变量必须允许输出访问。如果引用的变量不允许输出访问,则会忽略获取该变量的尝试。onstatuschange

示例:显示流的输出变量

此示例获取 和 的流输出变量,并将其显示在模板中。accountNamenumberOfEmployees

<!-- myComponent.html -->
<template>
  <p>{accountName}</p>
  <p>{numberOfEmployees}</p>
  <lightning-flow flow-api-name="myFlow" onstatuschange={handleStatusChange}> </lightning-flow>
</template>
// myComponent.js
import { LightningElement, track, wire } from "lwc";

export default class MyComponent extends LightningElement {
  accountName;
  numberOfEmployees;

  handleStatusChange(event) {
    if (event.detail.status === "FINISHED") {
      const outputVariables = event.detail.outputVariables;
      for (let i = 0; i < outputVariables.length; i++) {
        const outputVar = outputVariables[i];
        if (outputVar.name == "accountName") {
          this.accountName = outputVar.value;
        } else {
          this.numberOfEmployees = outputVar.value;
        }
      }
    }
  }
}

控制流程面试完成后发生的情况

通过将流嵌入到自定义组件中,可以确定流完成时发生的情况。lightning-flow

要使用此组件,请先使用 Salesforce Flow Builder 构建流程。该组件包括导航按钮“后退”、“下一步”、“暂停”和“完成”。

注意

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

使用 lightning-flow 嵌入流

默认情况下,当流程用户单击“完成”时,将开始新的面试,并且用户将再次看到流程的第一个屏幕。通过将流嵌入到自定义组件中,可以使用事件处理程序来调整流完成时发生的情况。lightning-flowonstatuschange

  • 若要重定向到其他页面,请使用导航服务。
  • 要控制自动启动的流程完成时发生的情况,请检查FINISHED_SCREEN状态。
<!-- myComponent.html -->
<template>
  <lightning-flow flow-api-name="myFlow" onstatuschange={handleStatusChange}> </lightning-flow>
</template>
// myComponent.js
handleStatusChange(event) {
    if(event.detail.status === 'FINISHED') {
        // Redirect to another page in Salesforce., or
        // Redirect to a page outside of Salesforce., or
        // Show a toast, or something else
    }
}

示例:在流程完成后导航到记录

此示例使用该方法将用户重定向到在流中创建的案例。 lightning/navigationNavigationMixin.Navigate

// myComponent.js
import { LightningElement } from "lwc";
import { NavigationMixin } from "lightning/navigation";

export default class MyComponent extends NavigationMixin(LightningElement) {
  navigateToRecord(recordId) {
    this[NavigationMixin.Navigate]({
      type: "standard__recordPage",
      attributes: {
        recordId,
        actionName: "view",
      },
    });
  }

  handleStatusChange(event) {
    if (event.detail.status === "FINISHED") {
      const outputVariables = event.detail.outputVariables;
      for (let i = 0; i < outputVariables.length; i++) {
        const outputVar = outputVariables[i];
        if (outputVar.name === "redirect") {
          this.navigateToRecord(outputVar.value);
        }
      }
    }
  }
}

恢复流程面试

您可以通过将组件嵌入到自定义 LWC 组件中来自定义用户恢复访谈的方式和位置。lightning-flow

要使用此组件,请先使用 Salesforce Flow Builder 构建流程。该组件包括导航按钮“后退”、“下一步”、“暂停”和“完成”。

注意

如果您的流程具有自定义 Lightning Web 组件或 Aura 组件,则无法在使用 Lightning Web Runtime 的 Experience Cloud 站点上使用。lightning-flow

处理暂停的面试

默认情况下,用户可以从其主页上的“暂停的访谈”组件中恢复他们暂停的访谈。若要自定义用户恢复访谈的方式和位置,请将组件嵌入到自定义 LWC 组件中,并将访谈 ID 传递到属性中。lightning-flowflow-interview-id

此示例说明如何恢复面试或开始新的面试。当用户单击联系人记录中的“调查客户”时,该组件将执行其中一项操作。lightning-flow

  • 如果用户对“调查客户”流有任何暂停的访谈,则该组件将恢复第一个访谈。lightning-flow
  • 如果用户对调查客户流没有任何暂停的访谈,则该组件将启动一个新访谈。lightning-flow
<!-- myComponent.html -->
<template>
  <lightning-flow flow-api-name={flowName} flow-interview-id={pausedInterviewId}>
  </lightning-flow>
</template>

示例:使用 Apex 恢复或开始新的面试

此 Apex 控制器通过执行 SOQL 查询来获取暂停访谈的列表。如果没有暂停的访谈,则查询将返回 null 值,然后组件将启动新的访谈。如果查询返回至少一个采访,则该组件将恢复该列表中的第一个采访。

// InterviewsController.apex
public class InterviewsController {
    @AuraEnabled(cacheable=true)
    public static String getPausedId() {
        // Get the ID of the running user.
        String currentUser = UserInfo.getUserId();
        // Find all of that user’s paused interviews for the Survey customers flow.
        List<FlowInterview> interviews =
             [ SELECT Id FROM FlowInterview WHERE CreatedById = :currentUser AND
                 InterviewLabel LIKE '%Survey customers%'];
        if (interviews == null || interviews.isEmpty()) {
            return null; // early out
        }
        // Return the ID for the first interview in the list.
        return interviews.get(0).Id;
    }
}

如果 Apex 控制器返回采访 ID,则传递到该属性。如果 Apex 控制器返回空访谈 ID,则组件通过将流名称传递给属性来启动新的访谈。pausedInterviewIdflow-interview-idflow-api-name

// myComponent.js
import { LightningElement, wire } from "lwc";
import getPausedId from "@salesforce/apex/InterviewsController.getPausedId";

export default class MyComponent extends LightningElement {
  flowName;
  pausedInterviewId;

  @wire(getPausedId)
  getPausedInterviewId({ error, data }) {
    if (error) {
      // start a new interview since no interview id was returned.
      this.flowName = "Survey_customers";
    } else if (data) {
      // resume the flow with the returned interview id.
      this.pausedInterviewId = data;
    }
  }
}

流屏组件的运行时注意事项

为流程配置组件时,请查看这些注意事项,以定义属性和流程运行时行为。

  • 处理 null 默认输入值。如果流程管理员未为自定义屏幕组件设置默认输入值,则流程生成器会在组件运行时传递一个值。若要在运行时设置默认值,请添加处理程序。nullnull
  • 通知运行时属性更改。Lightning Web 组件使用事件来报告从组件到流程的更改。为了通知流运行时属性更改,组件会触发事件。例如,在满足条件时使用条件可见性呈现屏幕组件时,或者将输出属性的值映射到流变量时,请使用此事件。有关代码示例,请参见。FlowAttributeChangeEventlightning-flow-support

设置屏幕流组件的反应性

屏幕流反应性意味着使屏幕流组件更改影响另一个同一屏幕组件。

在 Flow 构建器中添加和配置自定义 Lightning Web 组件之前,请创建自定义 LWC。

如果您计划构建反应式屏幕组件或使用支持的组件,则需要了解每个组件的注意事项。反应性包括:

  • 确定哪个屏幕组件是源,哪个组件是反应式组件。
  • 了解每组组件的不同交互。

示例:标准流量组件到标准流量组件

使用标准 Flow 组件是实现反应性的最简单方法。

使用标准 Flow 组件,您可以添加组件,为每个组件提供 API 名称和标签(如果需要),标识源和反应式组件,然后保存并运行流。

要使 Name 组件的 First Name 字段响应并显示来自 Text 组件的所有文本输入,请执行以下操作:

  1. 在流程中,添加组件和组件,并为每个组件提供标签。TextName
  2. 在组件的 First Name’s source 字段中,选择组件作为源。源可以是同一类型的任何组件,在本例中,Text 组件的类型和 Name 组件的 First Name 字段的类型为 String。NameText
  3. 保存并运行 Flow,然后在组件的输入中键入 .键入每个字符后,“名字”字段会立即更新。TextThis is Reactivity

示例:标准流组件到自定义 Lightning Web 组件

让您的自定义组件对标准 Flow 组件中的更改做出反应。

要创建自定义组件并将其添加到 Flow Builder 中,请执行以下操作:colorName

  1. 添加“文本”组件和“自定义”组件,并为每个组件提供标签。colorName
  2. 在 colorName 的属性中,选择组件作为“颜色名称”字段的源。Text

对文本输入所做的任何更改都会自动反映在自定义 Lightning Web 组件 .例如,在文本输入中输入洋红色会导致 LWC 显示具有相同颜色 magenta 样式的颜色名称。colorName

<!-- colorName.html -->
<template>
  <p>
    Color name has the same color :
    <span style={colorStyle}>{color}</span>
  </p>
</template>
// colorName.js
import { LightningElement, api } from "lwc";

export default class colorName extends LightningElement {
  @api color;

  get colorStyle() {
    return "color:" + this.color;
  }
}
// colorName.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
 <apiVersion>52.0</apiVersion>
 <isExposed>true</isExposed>
 <masterLabel>Color Name</masterLabel>
 <description>Reactive Color Name</description>
 <targets>
     <target>lightning__FlowScreen</target>
 </targets>
<targetConfigs>
      <targetConfig targets="lightning__FlowScreen">
          <property name="color" label="Name of Color" type="String" />
      </targetConfig>
</targetConfigs>
</LightningComponentBundle>

示例:将 Lightning Web 组件自定义为标准流组件

使自定义文本组件对标准 Flow 组件做出反应。

  1. 使用示例中引用的装饰器创建公共字段。@apicustomText.js
  2. 在文件中添加具有示例中引用的适当类型的目标配置。.js-meta.xmlcustomText.js-meta.xml
  3. 添加附加了事件处理程序的输入字段以存储用户输入。
  4. 在事件处理程序中,触发组件中定义的事件。事件名称必须与公共字段的名称匹配,才能使反应性起作用。由于 Flow Runtime 的性质,setter 方法在触发事件后调用,并在反应性导致对公共字段的更改时调用该方法。FlowAttributeChangeflow-supportFlowAttributeChangeFlowAttributeChange
  5. 创建自定义组件后,将该组件添加到带有标签的 Flow 构建器中。
  6. 若要使 Text 组件对自定义组件做出反应,请将 Text 组件上的默认值源配置为自定义组件的公共属性。
<!-- customText.html -->
<template>
  <lightning-input type="text" label="This is custom text" onchange={handleInputChange}>
  </lightning-input>
</template>
// customText.js
import LightningElement, api, track } from 'lwc';
import { FlowAttributeChangeEvent } from 'lightning/flowSupport';

export default class customText extends LightningElement {
   @track _input;

   @api
   set customTextValue(input) {
       if (input) {
           this._input = input;
       }
   };
   get customTextValue() {
       return this._input;
   }

   handleInputChange(event) {
       const attributeChangeEvent = new FlowAttributeChangeEvent('customTextValue', event.target.value);
       this.dispatchEvent(attributeChangeEvent);
   }
}
// customText.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
 <apiVersion>52.0</apiVersion>
 <isExposed>true</isExposed>
 <masterLabel>Custom Text</masterLabel>
 <description>Custom Text Example</description>
 <targets>
     <target>lightning__FlowScreen</target>
 </targets>
<targetConfigs>
      <targetConfig targets="lightning__FlowScreen">
          <property name="customTextValue" label="Custom Text Value" type="String" />
      </targetConfig>
</targetConfigs>
</LightningComponentBundle>

示例:自定义 Lightning Web 组件到自定义 Lightning Web 组件

使自定义 Lightning Web 组件对另一个自定义组件做出反应。

此示例假定组件和组件都已添加到 Flow Builder 中。该组件用于控制组件。customTextcolorNamecustomTextcolorName

  1. 添加组件和组件,并为每个组件提供标签。customTextcolorName
  2. 在组件的 Name of Color 字段中,选择组件作为源。colorNamecustomText

对组件所做的任何更改都会自动反映在 中的颜色公共字段中。例如,输入输入会导致显示具有相同颜色样式的颜色名称,即洋红色。customTextcolorNamemagentacustomTextcolorName

屏幕流中反应性的最佳做法

确保您的 Lightning Web 组件在 Flow 运行时引擎中很好地集成,并按预期工作以实现 Screen Flow 反应性。

状态管理和反应性

这些部分介绍了在屏幕流中具有反应性的状态管理,包括:

  • LWC 状态管理与 Aura 有何不同
  • 状态管理在新的 LWC Flow Runtime 中的工作原理
  • 为屏幕流构建 LWC 时要遵循的状态管理和事件最佳实践

Aura 与 LWC 中的状态管理

Aura 组件和 LWC 的状态管理遵循不同的范式。在 Aura 组件中,由它们所属的组件(包括外部组件)查看和修改。LWC 被设计为在内部和外部状态之间具有明确的分离。声明的变量只能由 LWC 的父级修改。没有注释的变量和带有注释的变量被视为内部状态,可以在定义它们的 LWC 中更改。<aura:attributes />@api@api@track

屏幕流中的 LWC 状态管理

Flow 运行时遵循 LWC 状态管理设计原则。当它渲染的 LWC 组件不修改其自身属性时,它效果最佳。相反,组件通过触发请求更改,这是必不可少的,原因有几个。@apiFlowAttributeChangeEvent

  • 允许其父级控制其属性的组件更容易重用,因为它们允许应用程序控制状态,而不是组件自己的内部业务逻辑。@api
  • 通过触发属性更改事件,Flow 运行时可以准确了解组件状态。Flow 运行时可以控制组件间的交互,例如条件字段可见性,这是反应性的一部分。

使用 LWC linter 规则是确保组件不会修改属性的绝佳方法。在开发环境中设置使用 LWC 的 linter 规则的 linter,特别是确保强制执行该设置。@api

流导航时的状态管理

为了在 Flow Runtime 版本之间保持一致的行为,当用户单击“下一步”或“完成”按钮时,Flow Runtime 会通过提取每个自定义 LWC 组件的当前属性状态来收集值,然后再移动到下一个屏幕或完成流程。此机制可确保不符合规定设计模式的组件在最新的 Flow Runtime 版本中继续工作。强烈建议避免依赖此行为。@api

下面是一个简单的文本输入组件示例,该组件演示了触发而不是更新 LWC 的属性:FlowAttributeChangeEvent@api

@api textValue;

handleTextInputChange(event) {
  const event = new FlowAttributeChangeEvent('textValue', event.target.value);
  this.dispatchEvent(event);
}

不建议使用以下示例。

//Don’t do this
@api textValue;

handleTextInputChange(event) {
  this.textValue = event.target.value;
}

创建 FlowAttributeChangeEvent 实例

该事件使 LWC 能够通知运行时其当前状态。它是 Flow Runtime 状态管理机制的一部分。每当用户交互导致需要更新属性的修改时,都会触发这些事件。FlowAttributeChangeEvent@api

从模块导入。FlowAttributeChangeEventlightning/flowSupport

import { FlowAttributeChangeEvent } from "lightning/flowSupport";

接下来,您可以创建事件的实例。

const attributeChangeEvent = new FlowAttributeChangeEvent(apiParameterName, updatedValue);

FlowAttributeChangeEvent包括两个参数。

  • apiParameterName– 要修改的 API 属性的名称。
  • updatedValue– Flow 属性的更新值。

将字符串传递给与 JavaScript 和文件中显示的 API 属性匹配的字符串。apiParameterName.js-meta.xml

例如,请注意 how 在所有 3 个地方都是一致的:exampleApiParameterName

  • 在 LWC 的文件中声明属性:.js-meta.xml<property name="exampleApiParameterName" label="Example Api Parameter" .../>
  • 在 LWC 的文件中声明 API 属性:.js@api exampleApiParameterName;
  • 构造实例:FlowAttributeChangeEventconst changeEvent = new FlowAttributeChangeEvent("exampleApiParameterName", "newValue");

传递 以匹配文件中声明的数据类型。updatedValue.js-meta.xml

  • 将属性声明为 Record:const changeEvent = new FlowAttributeChangeEvent("exampleApiParameterName", { StringFieldName: "stringValue", BooleanFieldName: false, NumberFieldName: 42, DateTimeFieldName: "2022-12-15T23:35:24.000Z", });
  • 将属性声明为字符串:const changeEvent = new FlowAttributeChangeEvent("exampleApiParameterName", "newValue");
  • 将属性声明为数字:const changeEvent = new FlowAttributeChangeEvent("exampleApiParameterName", 6);
  • 将属性声明为布尔值:const changeEvent = new FlowAttributeChangeEvent("exampleApiParameterName", true);

触发 FlowAttributeChangeEvent 事件

构造实例后,可以使用该方法触发事件。FlowAttributeChangeEventthis.dispatchEvent(eventToFire)

const attributeChangeEvent = new FlowAttributeChangeEvent("apiPropertyName", 10);
this.dispatchEvent(attributeChangeEvent);

FlowAttributeChangeEvent 最佳实践

请遵循这些示例并避免有问题的用法,以减少流程版本升级后的意外行为。这些最佳实践使您能够更轻松地维护组件。FlowAttributeChangeEvent

  • 在事件处理程序或在事件处理程序中调用的方法中触发事件。
  • 将事件的 value 参数限制为以下数据类型:String、number、boolean、JSON(用于记录类型)。
  • 确保事件的 value 参数的 Flow 数据类型与 LWC 属性的数据类型匹配。@api
  • 在适当的情况下,通过使用 get/set 模式来响应对属性的更改。@api

此示例演示如何使用 getter 和 setter 对属性的更新进行计数,而不是在触发属性更改事件之前递增计数器。这两种模式都是有效的,但它们获得的结果略有不同。@api

@api textValue;
textValueToRender;
changeCounter = 0;

get textValue() {
  return this.textValueToRender;
}

// Due to the nature of the Flow Runtime, this setter method
// is called after the FlowAttributeChangeEvent fired below. It also
// is invoked when reactivity leads to a change to the textValue property.
set textValue(newTextValue) {
  this.changeCounter++;
  this.textValueToRender = newTextValue;
}

handleTextInputChange(event) {
  const event = new FlowAttributeChangeEvent('textValue', event.target.value);
  this.dispatchEvent(event);
}

在这种情况下,每当用户在文本输入中输入更改以及 Flow Runtime 确定属性已更改时,都会递增。这种方法考虑了组件初始化和通过反应性进行的跨组件交互。使用中间变量呈现来自 set 方法的更新。在此示例中,填充此角色。this.changeCountertextValuetextValueToRender

下面的代码示例在触发属性更改事件之前递增计数器。此方案不使用 get/set 模式。

@api textValue;
changeCounter = 0;

handleTextInputChange(event) {
  this.changeCounter++;
  const event = new FlowAttributeChangeEvent('textValue', event.target.value);
  this.dispatchEvent(event);
}

在这种情况下,每当用户在文本输入中输入更改时,都会递增。当反应性更改此字段的值时,计数器不会更新。this.changeCounter

使用无注释或属性来维护可修改的本地组件状态。@track

下面的示例演示一个颜色选取器,该选取器向用户显示输入文本框和一些色板。颜色选取器组件返回到流中,因此只需要注释。and 成员仅是内部成员,不需要任何装饰器。colorcolor@apiselectedSwatchIdinputValue

//colorPicker.js

@api color;

inputValue;
selectedSwatchId;


ALL_SWATCHES =[{
  'id': 1,
  'color': 'red'
}, {
  'id': 2,
  'color': 'blue'
}, {
  'id': 3,
  'color': 'green'
}, {
  'id': 4,
  'color': 'yellow'
}];

set color(newColor) {
  // Set the content of the input text to the color value:
  this.inputValue = newColor;

  // If the color matches a swatch, highlight it in the UI:
  const foundSwatch = this.ALL_SWATCHES.find(swatch => swatch.color === newColor);
  this.selectedSwatchId = foundSwatch && foundSwatch.id;
}

get color() {
  return this.inputValue;
}

handleSwatchSelected(event) {
  // Select get the swatch data for the clicked swatch:
  const selectedSwatch = this.ALL_SWATCHES.find(swatch => swatch.id === event.target.dataset.id);
  const changeEvent = new FlowAttributeChangeEvent('color', selectedSwatch.color);
  this.dispatchEvent(changeEvent);
}

handleInputValueChange(event) {
  this.inputValue = event.target.value;
  const changeEvent = new FlowAttributeChangeEvent('color', this.inputValue);
  this.dispatchEvent(changeEvent);
}

使用 get 方法将多个属性组合在一起,为视图构造派生变量。@api

//Salutation.js
@api firstName;
@api lastName;
@api ageName;

get salutation() {
  return `Hello ${this.firstName} ${this.lastName}, you are ${this.age} years old.`;
}
<!-- Salutation.html -->
<template>
  <div>{salutation}</div>
</template>

在参与内部组件状态管理所需的派生成员变量的每个属性的 set 方法中应用相同的逻辑,就像在比上一个示例更复杂的方案中一样。此逻辑可确保对任何贡献属性所做的更改都会导致值正确呈现,而不管设置属性的顺序如何。@api

//Dropdown.js
set optionLabels(newOptionLabels) {
  this.internalOptionLabels = newOptionLabels;
  this.options = this.computeOptions();
}

set optionValues(newOptionValues) {
  this.internalOptionValues = newOptionValues;
  this.options = this.computeOptions();
}

set value(newValue) {
  this.internalValue = newValue;
  this.options = this.computeOptions();
}

handleSelect(event) {
  const selectedValue = event.srcElement.value;
  const isAlreadySelected = this.options.some(option => {
    return option.isSelected && option.value === selectedValue;
  });

  if (!isAlreadySelected) {
    const attributeChange = new FlowAttributeChangeEvent('value', selectedValue);
    this.dispatchEvent(attributeChange);
  }
}

computeOptions() {
  if (this.internalOptionLabels.length !== this.internalOptionValues.length) {
    return [];
  }

  return this.internalOptionLabels.map((label, index) ==> {
    const value = this.internalOptionValues[index];
    const isSelected = this.internalValue === value;
    return {
      label,
      value,
      isSelected
    };
  });
}

不要修改属性。而是开火。@apiFlowAttributeChangeEvents

不要在构造后修改参数。默认情况下,实例是组合和冒泡的。FlowAttributeChangeEventFlowAttributeChangeEvent

const changeEvent = new FlowAttributeChangeEvent("prop", "value");
changeEvent.bubbles = false; // Don’t do this
changeEvent.composed = false; // Don’t do this

避免同时发生射击和事件,因为它可能导致争用条件。我们永远无法保证您的 Lightning Web 组件在导航过程开始时有时间呈现更新的值。FlowAttributeChangeEventsFlowNavigationXxx

派生属性最佳实践

在流组件中,派生属性是其值由同一组件的其他属性确定的属性。该值可能与一个或多个其他属性相关联。例如,数据表组件的参数是派生属性,因为它的值由属性的值确定。具体而言,参数的值是属性的第一个元素的值。firstSelectedRowselectedRowsfirstSelectedRowselectedRows

创建包含派生属性的流屏幕组件时,请遵循此处概述的最佳实践,以确保组件在响应式框架中正常运行。通过这些最佳做法,您可以避免与以下方面相关的问题:

反应性链

当对一个组件的属性进行更改导致对其他组件的属性进行级联更改时,就会发生反应链。例如,用户选择数据表组件中的一行,这将更改组件的属性值,从而更改 Name 组件的属性。对 Name 组件的更改会更改 Text 组件的属性。firstSelectedRowfirstNamevalue

在重新访问的屏幕上保留值

当管理员配置屏幕时,您可以选择在用户离开时流是否保留用户指定的值,然后使用“上一个”或“下一个”按钮返回到屏幕。

若要确保派生属性在具有反应性组件的流屏幕中按预期方式运行,请遵循以下准则:

  1. 务必在驱动更改的属性的方法中为派生属性触发事件。此外,在组件的方法中触发派生属性的事件,例如:FlowAttributeChangeEventsetFlowAttributeChangeEventconnectedCallback// 'Driving' attribute set method set stringToCount(newStringToCount) { this._stringToCount = newStringToCount; // Fire an event for 'derived' attribute const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this._stringToCount.length); this.dispatchEvent(lengthChangeEvent); } connectedCallback() { // Fire an event for 'derived' attribute here as well const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this.\_stringToCount.length); this.dispatchEvent(lengthChangeEvent); }
  2. 不要在方法中省略事件。 您必须将事件包含在两个位置。当流执行驱动更改的属性的 in 方法时,组件不在 DOM 中,事件基本上无处可去。在方法中添加事件可确保当流将组件添加到 DOM 时,流运行时可以使用该事件。FlowAttributeChangeEventconnectedCallbackFlowAttributeChangeEventFlowAttributeChangeEventsetconnectedCallback
  3. 不要在方法中省略事件。 触发驱动更改的属性的 in 方法可确保将更改应用于驱动更改的属性和派生属性。因此,包含自定义组件的反应性链可以正常工作。FlowAttributeChangeEventsetFlowAttributeChangeEventset
  4. 不要通过使用来延迟触发方法内部来省略该方法。 延迟可确保当流首次调用该方法时,组件位于 DOM 中。但是,延迟还意味着组件在流初始化屏幕后触发事件。因此,流运行时始终覆盖保留的值,并否决重新访问的值的设置。connectedCallbackPromise.resolve().then(...)FlowAttributeChangeEventsetFlowAttributeChangeEventsetset stringToCount(newStringToCount) { this._stringToCount = newStringToCount; const lengthChangeEvent = new FlowAttributeChangeEvent('stringLength', this._stringToCount.length); // This code overwrites preserved values. Don't do this. Promise.resolve().then(() => { this.dispatchEvent(lengthChangeEvent); }); }

导航更改事件最佳实践

除了 之外,您的 Lightning Web 组件还可以触发导航事件以在屏幕之间移动、暂停或完成流程。要使用导航事件,请从以下位置导入这些函数:FlowAttributeChangeEventlightning/flowSupport

  • FlowNavigationNextEvent
  • FlowNavigationBackEvent
  • FlowNavigationPauseEvent
  • FlowNavigationFinishEvent

触发 FlowNavigationEvent

此代码片段介绍如何导入和触发导航事件。当该示例使用 时,相同的结构适用于每个可用的导航目标。FlowNavigationNextEvent

import { FlowNavigationNextEvent } from "lightning/flowSupport";

export default class FlowExample extends LightningElement {
  handleCustomNavigationButtonClick() {
    const nextEvent = new FlowNavigationNextEvent();
    this.dispatchEvent(nextEvent);
  }
}

导航事件允许您的 Lightning Web 组件包含其他控制机制。对最终用户交互做出反应时触发导航事件。使用导航事件时,请参阅以下准则:

  • 在事件处理程序(或事件处理程序调用的方法中)触发导航事件。
  • 不要在事件处理程序之外触发导航事件,因为这会导致糟糕的用户体验。
  • 不要在生命周期处理程序(如 和)中触发导航事件。renderedCallbackconnectedCallback
  • 将跳过屏幕的逻辑放在 Flow Decision Nodes 中,而不是放在屏幕的生命周期中。
  • 不要在虚拟屏幕中执行导航,因为这可能会导致 Flow 性能不佳。
  • 如果您遇到以下情况:由于数据尚不可用于 Flow Decision Nodes,因此必须在屏幕的初始化阶段完成导航,请在 Idea Exchange 中发布升级请求

在 Salesforce Targets 中使用组件

了解如何在不同的目标中使用 Lightning Web 组件,包括 Lightning Experience、Salesforce 移动应用程序和 Experience Builder 站点。

查看组织中的 Lightning 组件列表

在设置中,查看组织中 Lightning Web 组件和 Aura 组件的列表。

在“设置”中,输入“快速查找”框,然后选择“Lightning 组件”。在此页面中,您可以:Lightning Components

  • 查看组织中的 Lightning Web 组件和 Aura 组件。
  • 查看组件详细信息。单击组件名称或标签。
  • 在开发者控制台中打开 Aura 组件。单击组件名称,然后单击 Developer Console。

注意

要开发 Lightning Web 组件,请使用 Salesforce CLI 和您选择的代码编辑器。请参阅设置开发环境。

在 AppExchange上分发组件

作为 ISV 或 Salesforce 合作伙伴,您可以打包组件并将其分发给 AppExchange(Salesforce 商店)上的其他 Salesforce 用户和组织。

将组件添加到托管包

托管包可确保应用程序和其他资源完全可升级。

要创建和使用受管软件包,请使用 Developer Edition 组织并注册名称空间前缀。托管包在组件名称中包含命名空间前缀,可防止安装程序组织中的命名冲突。

注意

自定义 Lightning Web 组件无法引用从托管软件包安装的 Lightning Web 组件。请参阅组件命名空间。

要将组件添加到包中,请参阅 Salesforce 帮助中的将组件添加到包中。

将组件添加到包中时,将自动包含该组件引用的其他组件以及导入的任何资源。有关详细信息,请参阅@salesforce模块。@salesforce/*

发布包后,组件名称将被锁定,但包开发人员仍然可以在组件的配置文件中编辑这些值。

  • apiVersion
  • description
  • isExposed(只能更改为falsetrue)
  • masterLabel
  • targetConfigs
  • targets

如果 is ,包开发人员可以从组件中删除配置和 public () 属性。isExposedfalsetargets@api

如果是,并且组件位于已发布的托管包中,则包开发人员无法从组件中删除配置或 public () 属性。即使目标或公共属性是在包的最近发布之后添加的,也会强制执行此限制。isExposedtruetargets@api

从托管包中删除组件

发布托管包后,可以决定重构包并删除组件。您有责任让客户了解您删除的任何组件的潜在影响。在升级程序包的发行说明中,列出已删除的所有自定义组件,并通知客户任何必要的操作。

注意

若要在打包组织中启用组件删除,请在合作伙伴社区中记录案例。

要从托管包中删除组件,请执行以下操作:

  1. 在“设置”中,输入“快速查找”框。Lightning Components
  2. 选择 Lightning 组件
  3. 单击要删除的组件的 Del

注意

当您从软件包中删除 Lightning Web 组件时,该组件将在订阅者安装升级后的软件包后保留在订阅者的组织中。如果需要,订阅者组织的管理员可以删除该组件。

当您删除值为 的 Lightning Web 组件时,我们建议采用两阶段过程,以确保已删除的组件不依赖于包中的其他项目。isExposedtrue

第一阶段:删除引用。

  1. 编辑要删除的 Lightning Web 组件,以删除对其他 Lightning Web 组件的所有引用。
  2. 上传新的包版本。
  3. 将第一阶段升级推送给订阅者。

第二阶段:删除过时的组件。

  1. 从软件包中删除 Lightning Web 组件。
  2. (可选)删除其他相关组件和类。
  3. 上传新的包版本。
  4. 将第二阶段升级推送给订阅者。

使用托管包中的组件

托管包可以为您的组织提供有用的功能。某些托管软件包包括托管的 Lightning Web 组件。通过 AppExchange 安装托管软件包后,您可以通过在自定义组件中使用托管软件包来引用托管软件包中包含的托管 Lightning Web 组件 (LWC)。

重要

托管包的作者必须同时使用 Locker 和 LWS 进行测试,以确保其组件在这两种环境中都能正常工作。我们建议先在沙盒中安装和测试托管软件包,然后再将其安装到生产组织中。

在自定义组件中使用托管 LWC

托管软件包中的 Lightning Web 组件具有与您自己的命名空间不同的命名空间。我们建议您参考有关托管包的文档。

在自定义组件中使用托管包中的 LWC 时,请使用烤肉串大小写(短划线分隔)。同样,您在 LWC HTML 模板中使用的全局属性必须使用 kebab 大小写。

例如,如果托管包在命名空间中包含 LWC,并且托管组件名称为 ,则可以像这样引用该组件。lwc_partneraccount-verification

<!-- myComponent.html -->
<template>
  <lwc_partner-account-verification attribute-name="value"> </lwc_partner-account-verification>
</template>

在 Salesforce Target 中使用托管 LWC

托管 LWC 可用于不同的 LWC 目标,您可以使用相应的托管包文档进行验证。例如,如果在自定义 LWC 中使用托管 LWC,则可以通过更新配置标记在各种目标中显示托管 LWC。<targets>

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>58.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__RecordPage</target>
    <target>lightning__AppPage</target>
    <target>lightning__HomePage</target>
  </targets>
</LightningComponentBundle>

Lightning 应用程序生成器
使用点击式工具开发可用于为 Lightning Experience 和 Salesforce 移动应用程序快速构建自定义页面的组件。

为 Lightning 应用程序生成器配置组件

在 Lightning 应用程序生成器的 Lightning 页面上使用自定义 Lightning Web 组件之前,需要执行几个步骤。

1. 在配置文件中定义组件元数据

componentName.js-meta.xml 文件定义组件的元数据值,包括用于 Lightning 应用程序生成器的组件的设计配置。编辑配置文件以:

  • 使您的组件可在 Lightning 应用程序生成器和托管软件包中使用。
  • 定义您的组件可用于哪些类型的 Lightning 页面。
  • 配置组件的属性。
  • 设置组件支持的对象。

请参阅 XML 配置文件元素的完整列表。

此示例配置文件使该组件可用于所有 Lightning 页面类型,但限制仅对帐户、商机和仓库对象的记录页面支持。该组件具有为记录页面定义的一组属性,而不是为应用程序和主页定义的属性。

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>47.0</apiVersion>
  <isExposed>true</isExposed>
  <masterLabel>Best Component Ever</masterLabel>
  <description>This is a demo component.</description>
  <targets>
    <target>lightning__RecordPage</target>
    <target>lightning__AppPage</target>
    <target>lightning__HomePage</target>
  </targets>
  <targetConfigs>
    <targetConfig targets="lightning__RecordPage">
      <property name="prop1" type="String" />
      <objects>
        <object>Account</object>
        <object>Opportunity</object>
        <object>Warehouse__c</object>
      </objects>
    </targetConfig>
    <targetConfig targets="lightning__AppPage, lightning__HomePage">
      <property name="prop2" type="Boolean" />
    </targetConfig>
  </targetConfigs>
</LightningComponentBundle>

注意

定义组件属性时,请确保在文件和配置文件中为其分配了相同的类型(String、Integer、Boolean)。如果分配的类型不匹配,则配置文件中的类型分配优先。.js

2. 选项:将 SVG 资源添加到组件包中

要在 Lightning App Builder 中将 SVG 资源作为组件的自定义图标包含在内,请将其添加到组件的文件夹中。它必须被命名为 。每个文件夹只能有一个 SVG。componentName.svg

在 Lightning 应用程序构建器中配置用于动态交互的组件

管理员和开发人员都需要使动态交互正常工作,但 keystone 是一个自定义源组件,用于定义要触发的事件的属性。将事件元数据和属性添加到源组件,以将事件公开给 Lightning 应用程序构建器,然后管理员可以在其中配置事件的目标和交互。

只有 Lightning Web 组件可以是源组件,但任何组件(Aura 或 LWC)都可以成为目标。若要公开源组件中的事件,请在其文件中触发标准 JavaScript CustomEvent。若要使事件可被发现,请在组件文件中使用特定于动态交互的元数据,类似于生成器属性的公开方式。.jsjs-meta.xml

注意

有关动态交互的概述,请参阅 Salesforce 帮助。

使用动态交互公开事件

动态交互使用两个子标签,您可以将这两个子标签放入源组件包的 componentName.js-meta.xml 文件中以公开事件。targetConfig

  • event
  • schema

注意

这些子标签仅适用于目标 .targetConfiglightning__AppPage

下面是帐户列表源组件的示例配置文件。它包括一个事件,其架构包括文件中定义的 和。js-meta.xmlitemselectedapiNamerecordId.js

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>52.0</apiVersion>
  <isExposed>true</isExposed>
  <masterLabel>Account List</masterLabel>
  <targets>
    <target>lightning__AppPage</target>
  </targets>
  <targetConfigs>
    <targetConfig targets="lightning__AppPage">
      <property name="apiName" type="String"/>
      <property name="listViewApiName" type="String"/>
      <event name="itemselected" label="Item Selected" description="This event fires when an item is selected.">
        <schema>
          {
            "type": "object",
            "properties": {
              "recordId": {
                "type": "string",
                "title": "Record ID"
                "description": "Enter an 18-digit record ID."
          },
              "apiName": {
                "type": "string"
              }
            }
          }
        </schema>
      </event>
    </targetConfig>
  </targetConfigs>
  <description>meta xml description</description>
</LightningComponentBundle>

.js-meta.xml 文件的注意事项

  • 动态交互仅使用架构和架构。架构中的所有其他项都将被忽略。typeproperties
  • 唯一支持的属性特性是 、 和 。typetitledescription
  • 在事件架构中,支持的唯一有效属性类型是 String、Integer 和 Boolean。
  • 不会针对文件验证标记元数据。event.js

使用 Lightning 应用程序生成器的提示

在创建要在 Lightning 应用程序生成器中公开的组件时,请牢记这些准则。

组件提示

  • 使组件填充其显示区域的宽度(包括边距)的 100%。请参阅使组件具有宽度感知能力。
  • 如果组件需要交互,请在 Lightning 应用程序生成器中提供适当的占位符行为。
  • 不要显示空白框。想想其他网站是如何工作的。例如,Facebook 在实际的源项从服务器返回之前显示源的轮廓。大纲改进了用户对 UI 响应能力的感知。
  • 如果组件依赖于触发的事件,请为其指定在事件触发之前显示的默认状态。
  • 使用 Salesforce Design System 设置组件样式。

配置文件提示

  • 使用配置文件中的标记为组件设置友好名称。masterLabel
  • 使用配置文件向 Lightning 应用程序生成器公开属性。
  • 我们强烈建议在所有组件的配置文件中包含支持的外形规格信息。
  • 为所有属性指定一个具有友好显示名称的标签。
  • 提供说明以解释预期数据和任何准则,例如数据格式或预期值范围。描述文本在 Lightning 应用程序生成器的属性编辑器中显示为工具提示。
  • 为所需的属性提供默认值。将具有必需属性且没有默认值的组件添加到应用程序构建器时,该组件将显示为无效,这是一种糟糕的用户体验。
  • 对任何公开的属性使用基本支持的类型(字符串、整数、布尔值)。
  • 定义组件属性时,请确保在文件和配置文件中为其分配了相同的类型(String、Integer、Boolean)。如果分配的类型不匹配,则配置文件中的类型分配优先。.js
  • 指定整数属性的 min 和 max 属性,以控制接受值的范围。
  • 字符串属性可以为数据源提供一组预定义值,允许属性将其配置公开为选择列表。
  • 如果在 Lightning 页面或托管软件包中使用组件,我们会阻止您进行可能破坏组件的破坏性更改。这些破坏性变化的一些示例是:
    • 如果组件正在该对象的记录页面上使用,则删除该对象标签。
    • 如果组件正在该类型的页面上使用,则删除页面类型支持。
    • 如果组件正在 Lightning 页面上使用,则更改 min 或 max 属性值。

局限性

  • Lightning 应用程序构建器不支持地图、对象或 java:// 复杂类型。
  • 在 Lightning 页面上使用组件后,您只能增加该组件支持的外形规格,而不能减小它们。
  • 拉取刷新不适用于 Salesforce 移动应用程序中的自定义 Lightning Web 组件。

Lightning Web 安全

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

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

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

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

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

Lightning Web Security 的工作原理

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

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

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

通过虚拟化实现隔离

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

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

浏览器的虚拟化

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

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

虚拟化效果

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

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

注意

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

失真

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

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

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

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

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

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

Lightning Web Security 与 Lightning Locker 的比较

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

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

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

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

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

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

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

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

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

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

Lightning Web Security 支持哪些组件

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

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

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

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

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

提示

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

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

何时启用 Lightning Web安全

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

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

注意

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

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

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

在组织中启用 Lightning Web安全

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

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

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

重要

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

要在组织中使用 Lightning Web Security:

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

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

重要

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

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

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

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

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

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

Lightning Web Security 的工具支持

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

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

安装 Lightning Web Security 的 ESLint 规则

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

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

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

注意

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

添加对 Lightning Web Security 的 ESLint 支持

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

注意

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

在 VS Code 中使用 ESLint 规则

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

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

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

针对项目运行基本规则

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

在 Lightning Web Security 控制台中评估 JavaScript

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

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

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

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

注意

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

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

代码控制台 (1)

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

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

评价结果 (3)

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

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

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

提示

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

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

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

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

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

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

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

提示

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

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

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

注意

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

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

使用仅限 LWS 的功能

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

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

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

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

警告

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

启用 LWS 进行调试

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

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

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

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

启用 LWS 时进行调试的限制

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

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

Lightning Web Security 中的故障排除

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

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

确定是否启用了 LWS

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

添加代码以触发 LWS 失真

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

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

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

navigator.serviceWorker === undefined;

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

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

在浏览器控制台中检测 LWS

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

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

提示

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

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

搜索字符串sandbox.evaluateInSandbox

Aura 组件

搜索字符串$A.lockerService.createForDefNext

LWS 的延迟启用或禁用

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

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

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

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

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

在浏览器中禁用缓存

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

在 Salesforce 中禁用安全缓存

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

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

警告

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

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

属性评估与 LWS 相同undefined

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

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

未定义的值在全局范围内

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

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

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

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

注意

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

undefined 值由函数参数传递

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

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

提示

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

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

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

//module in namespace A

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

//module in namespace B

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

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

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

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

注意

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

变异对象的替代方案

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

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

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

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

函数参数的突变替代方案

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

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

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

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

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

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

类实例属性的突变替代项

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

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

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

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

可能的解决方案是:

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

具有异步函数的静态资源

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

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

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

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

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

标准 JavaScript 对象的自定义属性

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

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

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

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

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

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

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

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

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

Map 对象限制

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

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

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

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

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

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

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

本地存储限制

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

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

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

注意

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

LWS 不允许的 Aura 端点

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

这些 JavaScript API 会受到影响。

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

被 LWS 阻止的 Salesforce 全局变量

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

Lightning Web 组件无权访问

  • $A
  • Aura
  • Sfdc
  • sforce

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

LWS 的第三方库注意事项

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

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

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

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

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

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

第三方库设置严格模式

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

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

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

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

Experience Builder 站点和 LWS

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

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

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

LWS 对 Aura 组件的局限性

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

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

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

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

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

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

  • async/await
  • 动态导入

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

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

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

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

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

提示

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

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

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

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

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

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

$A在 LWC 中被阻止

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

被 LWS 阻止的分析库

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

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

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

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

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

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

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

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

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

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

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

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

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

在 LWS 中访问 iframe 内容

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

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

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

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

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

允许的 URL 协议方案

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

LWS 消毒

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

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

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

LWS Sanitizer 的 HTML 允许列表

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

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

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

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

消毒后,它变成:

<div></div>

LWS 的 SVG 允许列表

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

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

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

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

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

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

清理后,此代码变为:

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

SVG 元素中的静态资源

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

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

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

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

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

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

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

内容安全策略概述

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

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

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

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

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

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

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

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

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

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

浏览器支持

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

注意

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

查找 CSP 冲突

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

此消息是示例冲突。

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

更严格的 CSP 限制

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

更严格的 CSP 有什么作用?

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

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

重要

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

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

更严格的 CSP 会影响:

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

更严格的 CSP 不会影响:

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

注意

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

开发安全代码

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

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

Lightning Locker 的安全性

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

JavaScript 严格模式强制执行

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

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

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

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

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

DOM 访问遏制

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

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

注意

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

安全包装纸

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

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

可用:Lightning Web 组件和 Aura 组件

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

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

可用:Lightning Web 组件和 Aura 组件

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

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

可用:Lightning Web 组件和 Aura 组件

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

可用:Lightning Web 组件和 Aura 组件

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

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

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

可用:Lightning Web 组件

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

不支持这些 API。

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

SecureTemplate

可用:Lightning Web 组件

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

不支持这些 API。

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

注意

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

eval() 函数受 Lightning Locker 限制

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

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

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

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

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

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

提示

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

允许的 MIME 类型

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

Lightning Locker 允许这些 MIME 类型。

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

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

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

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

提示

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

带有 Lightning Locker 的第三方 Web 组件

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

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

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

对 Salesforce 全局变量的访问受限

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

Lightning Web 组件无权访问

  • $A
  • Aura
  • Sfdc
  • sforce

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

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

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

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

注意

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

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

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

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

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

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

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

使用 Lightning Locker 运行的 querySelector API

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

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

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

Lightning Locker工具

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

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

储物柜控制台

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

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

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

选择组织的 Locker API 版本

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

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

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

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

注意

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

使用更衣室 API 版本的位置

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

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

注意

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

Locker API 版本更改

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

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

更改组织的 Locker API 版本

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

使用 RefreshView API 刷新组件数据

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

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

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

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

Lightning 数据服务支持 RefreshView API。

使用 RefreshView API 的限制

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

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

RefreshView API 用户体验

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

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

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

对于应用触发的刷新:

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

使用 RefreshView API

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

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

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

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

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

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

重要

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

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

启用了 LWS 的组织示例

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

启用了 Lightning Locker 的组织示例

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

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

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

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

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

使用 RefreshEvent 发出刷新信号

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

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

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

注册以接收 RefreshEvent

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

重要

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

注意

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

启用了 LWS 的组织示例

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

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

启用了 Lightning Locker 的组织示例

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

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

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

注意

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

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

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

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

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

RefreshView API 示例

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

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

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

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

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

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

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

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

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

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

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

考虑

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

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

从 Apex 调用 API

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

注意

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

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

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

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

调用 Apex 方法

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

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

重要

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

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

Apex限制

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

导入 Apex 方法

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

语法

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

用法

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

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

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

向 Lightning Web 组件公开 Apex 方法

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

语法

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

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

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

注意

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

用法

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

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

提示

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

将 Apex 方法连接到 Lightning Web 组件

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

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

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

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

将 Apex 方法连接到属性

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

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

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

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

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

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

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

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

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

使用动态参数连接 Apex 方法

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

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

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

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

重要

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

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

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

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

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

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

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

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

提示

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

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

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

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

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

Apex 方法采用一个对象 。CustomWrapper

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

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

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

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

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

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

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

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

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

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

将 Apex 方法连接到函数

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

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

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

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

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

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

export default class ApexWireMethodToFunction extends LightningElement {
  contacts;
  error;

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

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

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

命令式调用 Apex 方法

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

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

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

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

调用 Apex方法

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

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

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

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

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

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

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

使用参数调用 Apex 方法

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

// apexImperativeMethodWithParams.js

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

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

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

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

提示

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

将值传递给 Apex

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

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

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

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

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

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

  val;

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

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

Apex 操作的批处理

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

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

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

Boxcar 请求中的操作限制

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

Apex 方法结果的客户端缓存

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

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

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

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

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

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

提示

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

强制调用方法时刷新缓存

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

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

export default class Example extends LightningElement {
    @api recordId;

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

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

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

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

使用@wire时刷新缓存

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

注意

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

在 Apex 类中使用延续

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

先决条件

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

注意

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

使用 Continuation 对象

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

示例:具有延续的 Apex 类

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

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

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

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

@AuraEnabled延续的注释

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

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

缓存注意事项

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

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

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

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

可缓存设置行为

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

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

延续示例

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

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

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

下面是组件的 JavaScript 文件。

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

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

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

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

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

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

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

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

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

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

延续限制

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

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

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

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

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

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

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

保护 Apex 类

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

注意

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

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

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

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

强制执行共享规则

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

public with sharing class SharingClass {
    // Code here
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public with sharing class ExpenseControllerStripped {

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

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

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

         return expenses;
    }
}

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

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

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

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

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


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

    for (String fieldToCheck : expenseAccessFields) {

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

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

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

使用Wire服务获取数据

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

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

注意

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

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

了解Wire服务

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

重要

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

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

提示

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

线路服务语法

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

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

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

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

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

提示

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

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

注意

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

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

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

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

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

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

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

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

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

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

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

提示

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

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

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

export default class Record extends LightningElement {
  @api recordId;

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

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

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

export default class Record extends LightningElement {
  @api recordId;

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

导入复合字段的引用

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

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

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

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

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

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

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

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

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

提示

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

导入限制

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

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

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

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

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

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

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

paField = "Account.myCustomField__pc";

外部对象

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

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

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

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

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

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

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

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

const fields = [REVENUE_FIELD];

export default class WireGetValue extends LightningElement {
  @api recordId;

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

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

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

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

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

用@wire装饰房产

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

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

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

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

export default class Record extends LightningElement {
  @api recordId;

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

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

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

{ data: undefined, error: undefined }

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

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

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

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

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

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

用@wire装饰函数

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

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

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

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

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

获取记录数据

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

提示

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

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

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

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

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

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

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

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

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

注意

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

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

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

export default class WireGetRecordDynamicContact extends LightningElement {
  @api recordId;

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

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

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

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

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

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

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

注意

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

处理 Lightning 数据服务中的错误

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

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

错误值

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

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

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

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

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

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

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

export default class WireGetRecordDynamicContact extends LightningElement {
  @api recordId;

  @track error;

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

车身有效载荷

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

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

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

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

注意

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

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

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

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

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

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

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

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

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

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

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

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

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

显示多行文本输入字段。

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

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

示例:创建自定义表单

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

注意

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

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

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

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

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

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

const namefields = [FIRSTNAME_FIELD, LASTNAME_FIELD, SALUTATION_FIELD];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  @wire(getRecord, { recordId: "$recordId", fields: FIELDS })
  contact;

  get street() {
    return this.contact.data.fields.MailingStreet.value;
  }

  get city() {
    return this.contact.data.fields.MailingCity.value;
  }

  get state() {
    return this.contact.data.fields.MailingState.value;
  }

  get country() {
    return this.contact.data.fields.MailingCountry.value;
  }

  get postal() {
    return this.contact.data.fields.MailingPostalCode.value;
  }
}