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 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。
Lightning Locker 阻止使用第三方 Web 组件,以防止 Salesforce 平台上的安全风险。
Web 组件是自定义元素。若要定义自定义元素,必须使用 API。但是,此 API 允许您按标记名称全局注册组件。全局注册标记名称存在安全风险,因为攻击者可以创建任何已注册自定义元素的实例,并可能获得对敏感信息的访问权限。Lightning Locker 的包装器阻止了创建自定义 Web 组件的方法。customElements.defineSecureWindowcustomElements
Locker API 查看器工具中的包装器列表显示不受支持。SecureWindowcustomElements
启用 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
选择 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()
无论是用户驱动还是应用调用,在不重新加载整个页面的情况下同步数据的能力都是一项关键的用户体验要求。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 要求,可以由用户驱动,也可以由应用调用。
用户触发的刷新的典型流程为:
Lightning Web 组件显示一个按钮(或其他用户界面控件),用于启动一个进程,该进程将组件中显示的数据与其源同步。
点击按钮时,按钮组件将调度一个事件。RefreshEvent
向 RefreshView API 注册的最近级别的容器组件将收到 ,停止其传播。RefreshEvent
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.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());
}
}
//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);
});
}
}
根据安全策略,由 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 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
];
}
}
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
];
}
}
// 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
];
}
}
// 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);
}
}
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; }
}
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
];
}
}
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
];
}
}
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();
}
}
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); }
<!-- 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>
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);
}
}
若要进行长时间运行的标注,请定义一个返回对象的 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; }
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;
}
}
// 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];
}
}
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];
}
}
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
此示例使用 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];
}
}
要访问对象和字段 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";
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";
// 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";
有关与 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
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts(String accId) {
return [
SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
FROM Contact
WHERE AccountId = :accId
WITH SECURITY_ENFORCED
];
}
}
//CaseController.cls
public with sharing class CaseController {
@AuraEnabled(cacheable=true)
public static list<Account> getNewCasesForAccounts(){
return [SELECT Name, (
SELECT Id, CaseNumber, Status FROM Cases WHERE toLabel(Status) = 'New')
FROM Account WHERE Id IN (SELECT AccountId FROM Case)];
}
}
在 JavaScript 文件中,用于调用 Apex 方法。使用键定义包含子项的行。包含子项的行显示带有 V 形按钮,用于切换子项。myTreeGrid.js@wire_children
import { LightningElement, api } from "lwc";
export default class FieldValueCreateExample extends LightningElement {
myValue = "My Account Name";
overrideValue(event) {
this.myValue = "My New Name";
}
}
// contactListItem.js
import { LightningElement, api } from "lwc";
export default class ContactListItem extends LightningElement {
@api contact;
selectHandler(event) {
// Prevents the anchor element from navigating to a URL.
event.preventDefault();
// Creates the event with the contact ID data.
const selectedEvent = new CustomEvent("selected", { detail: this.contact.Id });
// Dispatches the event.
this.dispatchEvent(selectedEvent);
}
}
想象一下,一个事件是从组件中的元素调度出来的。在组件的影子 DOM 中,是 .但是对于包含组件中元素的侦听器来说,是 ,因为该元素无法看到影子 DOM。divc-todo-itemEvent.targetdivpc-todo-appEvent.targetc-todo-itempc-todo-item
<c-todo-app>
#shadow-root
<div>
<p>Your To Do List</p>
</div>
<c-todo-item>
#shadow-root
<div>
<p>Go to the store</p>
</div>
</c-todo-item>
</c-todo-app>
Event.target调度事件的元素。每个组件的内部 DOM 都封装在一个影子 DOM 中。阴影边界是常规 DOM(也称为轻量级 DOM)和阴影 DOM 之间的线。如果事件冒泡并越过阴影边界,则 value of 的值将更改为表示与侦听器位于同一作用域中的元素。事件重定向可保留组件封装,并防止暴露组件的内部。Event.target例如,单击侦听器始终接收作为目标,即使单击发生在元素上也是如此。<my-button>my-buttonbutton<!-- myButton.html --> <template> <button>{label}</button> </template>
Event.currentTarget当事件遍历 DOM 时,此属性始终引用事件处理程序附加到的元素。
Event.composedPath()当事件遍历 DOM 时,将在其上调用侦听器的事件目标的数组。
若要将数据传达给不在同一影子树中的元素,请使用 .在这些情况下,不起作用,因为侦听器看不到真正的目标。(当事件冒泡 DOM 时,如果它越过影子边界,则 的值会更改以匹配侦听器的范围。事件被重新定位,因此侦听器无法看到调度事件的组件的影子树。event.detailevent.target.*Event.target
不要包含非基元 from 或 in 。这些值被包裹在只读膜中。在 IE 11 中,当穿过 LWC 到 Aura 桥时,只读膜丢失,这意味着可能会发生突变。@api@wiredetail事件传播
使用配置了 和 的事件,因为它们的中断最小。这些事件不会通过 DOM 冒泡,也不会越过影子边界。bubbles: falsecomposed: false
不建议使用冒泡和组合事件,因为它们会冒泡整个 DOM 树,跨越阴影边界,并通过父组件(除非停止)。如果使用 和 配置事件,则事件类型将成为组件公共 API 的一部分。它还强制使用组件及其所有祖先将事件作为其 API 的一部分包含在内。这是一个需要注册的大型 API 合约。如果使用此配置,则事件类型应全局唯一。bubbles: truecomposed: true
在 LWC 应用程序中使用第三方 Web 组件。第三方 Web 组件在 LWC 模板中呈现为本机 Web 组件。尽管您可以使用 iframe 或 在 LWC 模板文件中使用第三方 Web 组件,但我们建议使用 将第三方 Web 组件呈现为 LWC 模板中的本机 Web 组件。lwc:dom="manual"lwc:external
注意
Salesforce 不提供对第三方 Web 组件的支持。演示使用第三方 Web 组件的文档和示例不构成对第三方 Web 组件的认可。我们建议您查看第三方 Web 组件文档,了解使用情况信息。
第三方 Web 组件可以是各种 JavaScript 框架,也可以是用纯 vanilla JS 编写的框架。MDN Web 组件是可与 LWC 框架一起使用的第三方 Web 组件的示例。同样,webcomponents.org 提供了基于自定义元素和影子 DOM 标准的 Web 组件的集合。
如果您使用的是第三方 JavaScript 库,请参阅改用第三方 JavaScript 库。
实现第三方 Web 组件
在使用第三方 Web 组件之前,我们建议您检查 AppExchange 中是否有第三方 LWC 应用程序或组件,并安装具有所需功能的托管软件包。或者,检查基本组件是否适合您的要求。
在自定义元素中使用标记是可选的。您可以使用 创建标签。您不能在 LWC HTML 模板中使用嵌套标记。<template><template>document.createElement("template");<template>
定义自定义元素
在 LWC 中,只有在使用第三方 Web 组件时才需要自定义元素。在 LWC 中实现第三方 Web 组件时,我们建议您参考该组件的文档以获取使用信息。
第三方 Web 组件包含自定义元素定义,该定义扩展了 HTMLElement 类。自定义元素定义描述如何显示元素以及添加或删除元素时要执行的操作。
class MyCustomElement extends HTMLElement {
constructor() {
super();
/* custom element created */
}
connectedCallback() {
/* element is added to document */
}
disconnectedCallback() {
/* element is removed from the document */
}
static get observedAttributes() {
return [
/* array of attribute names to monitor for changes */
];
}
attributeChangedCallback(name, oldValue, newValue) {
/* one of attributes listed above is modified */
}
adoptedCallback() {
/* element is moved to a new document */
}
}
/* register the element */
customElements.define("my-custom-element", MyCustomElement);
第三方 Web 组件行为在生命周期回调中描述,例如 in 或 。connectedCallback()disconnectedCallback()
呈现第三方 Web 组件后,将忽略属性更改。要观察属性并确保第三方 Web 组件呈现您的更改,请使用静态 getter 和方法。当观察到的属性发生更改时,回调将运行。若要处理属性数据的序列化和反序列化,请使用 getter 和 setter,如本示例所示。observedAttributes()attributeChangedCallback()attributeChangedCallback()
class extends HTMLElement {
static observedAttributes = ["myAttr"];
attributeChangedCallback(attr, oldVal, newVal) {
if (attrName === "myAttr") {
this.shadow.getElementById("myElement").myAttr = newVal === "true";
}
}
set myAttr(bool) {
this.setAttribute("myAttr", bool.toString());
}
get myAttr() {
return this.getAttribute("myAttr") === "true";
}
}
请参阅使用属性更改递增按钮的示例。
使用属性传递数据
要使用属性,请使用 getter 和 setter。
class extends HTMLElement {
_message = 'Hello';
set message(value) {
this._message = value;
}
get message() {
return this._message;
}
}
未注册的自定义元素呈现为本机接口的实例,该实例在不添加任何属性或方法的情况下进行扩展。然后,浏览器将外部组件视为本机组件,与 a 或 .HTMLUnknownElementHTMLElementspandiv
对于已注册的组件,引擎会呈现关联的第三方 Web 组件,并将升级推迟到浏览器。
有关更多信息,请参见 HTML 规范:在创建元素后升级元素。
此外,请考虑第三方 Web 组件上的这些升级行为。
如果未升级第三方 Web 组件,LWC 会在挂载和更新时设置其属性。
如果存在延迟升级,则设置属性而不是属性。
升级后,如果属性存在,则设置该属性而不是该属性。
将样式追加到自定义元素(测试版)
第三方 Web 组件可以包含在影子 DOM 或轻量级 DOM 中创建的自定义元素。对于影子 DOM,开放模式比封闭模式更广泛地使用。要了解您正在使用的第三方 Web 组件是否正在使用特定的影子模式,请参阅 Web 组件文档。或者,查看 Web 组件源并查找方法。使用轻量级 DOM 的 Web 组件不使用该方法。attachShadow({mode: 'closed|open'})attachShadow()
这是 Lightning Web 组件所独有的。实现它以创建一个错误边界组件,该组件捕获其树中所有后代组件中的错误。它捕获在后代的生命周期挂钩中或在 HTML 模板中声明的事件处理程序期间发生的错误。您可以对错误边界组件进行编码,以记录堆栈信息并呈现替代视图,以告知用户发生了什么以及下一步要做什么。errorCallback()
<!-- child.html -->
<template>
<span
>Tabbing to the custom element moves focus to the input, skipping the component itself.</span
>
<br /><input type="text" />
</template>
// coolButton.js
import { LightningElement } from "lwc";
export default class CoolButton extends LightningElement {
static delegatesFocus = true;
}
使用可实现以下情况。delegatesFocus
使用 将焦点添加到本机按钮 HTML 元素。coolButton.focus()
如果单击阴影 DOM 中的某个节点,并且该节点不是可聚焦区域,则第一个可聚焦区域将变为焦点。这类似于单击标签并将焦点跳转到输入时。
当影子 DOM 中的节点获得焦点时,除了焦点元素之外,CSS 选择器还会应用于主机。:focus
注意
不要使用 with,因为它会偏离焦点顺序。tabindexdelegatesFocus
移动就绪组件
Lightning Web 组件支持在桌面和移动设备上使用组件。在桌面上,支持各种 Web 浏览器。在移动设备上,支持的环境包括 Salesforce 分布式移动应用程序。这两种环境之间的差异不仅仅是屏幕尺寸;有新的限制,也有移动端的扩展功能。若要构建在移动体验中表现良好的组件,请遵循我们的移动就绪组件指南。