之前的几节都是基于前台变量进行相关的操作和学习,我们在项目中不可避免的需要获取数据以及进行DML操作。之前的内容中也有提到wire注解,今天就详细的介绍一下对数据进行查询以及DML操作以及Wire Service相关的知识。

一. LDS

学习LDS以前可以先看一下aura中LDS的概念salesforce lightning零基础学习(六)Lightning Data Service(LDS)。针对LWC中的LDS和aura中的功能原理很像,区别可能是语法和标签的区别。所以这里对LDS不做过多的描述,直接展开标签的用法。

LWC 封装了3个最基础的组件去和数据进行交互。分别是lightning-record-form / lightning-record-edit-form / lightning-record-view-form。和aura的用法也类似,lightning-record-form可以作为view/edit视图使用,lightning-record-edit-form针对edit视图使用并可以进行最大可能的自定义UI,lightning-record-view-form针对view视图使用并可以进行最大可能的自定义UI。

他们可以实现最基础的交互,如果他们标准功能满足不了,我们需要更加的自定义的功能,需要使用@wire 去指定LDS 的wire adapter。(封装在lightning/ui*Api中)

1. lightning-record-form

当我们只有ID,希望根据当前的用户显示当前用户对应的page layout布局的内容。我们便可以使用 lightning-record-form标签了,此标签遵循着FLS关系,用户只能看到自己可见的字段。此标签有三个模式:
view: 以output field展示,针对有权限编辑的字段,会显示编辑的按钮,当编辑某个值以后会显示save/cancel 按钮。

read-only:和上面区别为不显示可编辑按钮。

edit:以输入框进行展示,然后显示save/cancel按钮。

myComponentWithRecord 展示了 lightning:record-form的view的demo。

myComponentWithRecord.html:使用lightning-record-form展示account的detail数据,layout-type选择的是compact,mode为view。

1 <template>
2     <lightning-record-form
3         record-id={recordId}
4         object-api-name="Account"
5         layout-type="Compact"
6         mode="view">
7     </lightning-record-form>
8 </template> 

myComponentWithRecord.js:声明一个public的变量,名称固定为recordId。

1 // myComponent.js
2 import { LightningElement, api } from 'lwc';
3 export default class MyComponent extends LightningElement {
4     @api recordId;
5 }

myComponentWithRecord.js-meta.xml:配置当前component只允许用到record page中,并且只有account的record page可以选择此component。下面的内容我们会详细的讲解如何配置此xml文件,以及各个标签的作用。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="myComponentWithRecord">
 3     <apiVersion>46.0</apiVersion>
 4     <isExposed>true</isExposed>
 5     <targets>
 6         <target>lightning__RecordPage</target>
 7     </targets>
 8     <targetConfigs>
 9         <targetConfig targets="lightning__RecordPage">
10             <property name="recordId" type="String"></property>
11             <objects>
12                 <object>Account</object>
13             </objects>
14         </targetConfig>
15     </targetConfigs>
16 </LightningComponentBundle>

配置完以后我们只需要找到一个account,更改page layout或者app builder中针对account的lightning record page拖拽此component即可显示。

上面的demo中,我们在lightning-record-form中声明了一些简单的属性,除了上述的属性以外,此标签还有很多可选择的属性。所有属性如下:

  • record-type-id: record type的ID,此属性用于当前的object有多个record type并且我们不想创建default的record type情况,这时我们需要传递 record type id;
  • mode: view/edit/readonly。当我们没有指定ID的情况下,则这个类型默认是edit,即要创建一个object的记录;当有id情况下,默认是view。
  • layout-type: Compact / Full. 用来指定当前的layout展现的形式。当我们新建记录时,即record id为空的情况下,layout-type只能渲染成Full.
  • record-id: 需要展示/操作的记录ID,如果此属性为空,则代表要新建一条记录;
  • object-api-name: 当前想要操作的object的API name,此属性是必填属性
  • columns: 表单中的列数,通常lightning:record-form不需要设置;
  • fields: 如果我们不想通过layout-type展示,我们可以设置此fields选项,去按照我们的要求显示指定的字段信息。当然lightning:record-form不建议使用此属性,如果想要自定义显示字段,我们可以考虑用lightning:record-view-form以及lightning:record-edit-form去实现read/edit模式。
  • density:设置label以及field在表单中的排列样式。有三个值: compact / comfy / auto.其中auto是default的值。

除了上述的属性以外,因为lightning-record-form有edit mode,所以它还拥有一些方法(以下仅用于edit mode,readonly不可用):

  • load:当LDS加载数据完成后会调用此事件,此事件有一个返回的参数是detail,我们可以通过event.detail获取相关的内容;
  • submit:当form表单提交了改变了的data时会自动触发此事件,此事件有一个可传入的参数fields,此参数用来指定要操作的字段集合;
  • success:当form表单提交执行成功以后会自动触发此事件,此事件有一个返回的参数是detail;
  • error:当form表单提交执行失败以后会自动触发此事件,返回的参数有detail;
  • cancel:当form表单没有提交点击cancel以后会自动触发此事件。

这几个事件在某种情况下还是有一定联系的。

  • 当我们执行submit事件以后,在没有错误的情况下,会先执行load事件,执行成功以后会执行success事件,当执行完success事件以后会再一次load事件。 submit -> load -> success -> load。
  • 当我们执行submit事件以后,如果有错误会执行error事件。 submit -> error。
  • 当我们执行cancel事件以后,将会执行load事件。cancel -> load。
  • 当我们执行完cancel事件以后,页面的cancel/submit按钮会隐藏,可编辑字段会展示编辑的图标,当我们对某个字段进行编辑时,会执行load事件。

下面通过一个demo更好的了解edit功能。

myComponentWithRecordEdit.html:展示一个edit模式的lightning-record-form,并针对这些标准的事件设置相关的handler。

 1 <template>
 2     <lightning-record-form
 3         record-id={recordId}
 4         object-api-name={objectApiName}
 5         fields={fields}
 6         columns="2"
 7         mode="edit"
 8         onsubmit={handleSubmit}
 9         onsuccess={handleSuccess}
10         onerror={handleError}
11         oncancel={handleCancel}
12         onload={handleLoad}
13         >
14     </lightning-record-form>
15 </template>

myComponentWithRecordEdit.js:设置相关的handler逻辑,头部我们可以看到import salesforce/lightning相关的内容。这个我们在后续会以refrence内容详细说明。这里还有event.preventDefault()方法。当我们捕获submit 事件并以编程方式提交表单,这种情况我们需要使用event.preventDefault方法去取消事件的默认行为,否则会进行重复的表单提交。

 1 /* eslint-disable no-console */
 2 import { LightningElement, api } from 'lwc';
 3 import { ShowToastEvent } from 'lightning/platformShowToastEvent';
 4
 5 import NAME_FIELD from '@salesforce/schema/Account.Name';
 6 import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
 7 import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
 8
 9 export default class MyComponentWithRecordEdit extends LightningElement {
10     // The record page provides recordId and objectApiName
11     @api recordId;
12     @api objectApiName;
13
14     fields = [NAME_FIELD, REVENUE_FIELD, INDUSTRY_FIELD];
15
16     handleLoad(event) {
17         console.log('execute handle load');
18     }
19
20     handleSubmit(event){
21         console.log('execute handle submit');
22         event.preventDefault();       // stop the form from submitting
23         const fields = event.detail.fields;
24         fields.LastName = 'My Custom Last Name'; // modify a field
25         this.template.querySelector('lightning-record-form').submit(fields);
26     }
27
28     handleSuccess(event) {
29         console.log('execute handle success');
30         const evt = new ShowToastEvent({
31             title: "Account Operated",
32             message: "Record ID: " + event.detail.id,
33             variant: "success"
34         });
35         this.dispatchEvent(evt);
36     }
37
38     handleError(event) {
39         console.log('execute handle error');
40         const evt = new ShowToastEvent({
41             title: "Account Operated",
42             message: event.detail.message,
43             variant: "error"
44         });
45         this.dispatchEvent(evt);
46     }
47
48     handleCancel(event) {
49         console.log('execute handle cancel')
50         const evt = new ShowToastEvent({
51             title: "Account canceled",
52             variant: "cancel"
53         });
54         this.dispatchEvent(evt);
55     }
56 }

效果展示:

1) 点击Save更新数据操作

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

2) 当Save后有error情况

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

 2. lightning-record-view-form

lightning-record-form功能确实比较好用,但是如果用户想要显示指定的字段并且希望字段以指定的顺序进行显示只读的pagelayout时,使用lightning-record-form便无法实现了,这个时候我们需要使用lightning-record-view-form搭配lightning-output-field用来实现按照自己的要求展示一个或者多个字段。显示时,我们通常搭配grid一起使用按需展现多行多列效果。grid使用可以参考:https://www.lightningdesignsystem.com/utilities/grid/

 此组件有以下的属性可供选择:

  • record-id:当前要显示记录的记录ID,此字段必填;
  • object-api-name: 当前object的API 名称,此字段必填;
  • density:设置label以及field在表单中的排列样式。

除上述属性以外,lightning-record-view-form支持load事件,可用参数为data,存储的是记录的数据。详见上面的demo。下面的demo为使用此标签实现只读的数据。

 myComponentWithRecordView.html:通过引入lightning-record-view-form,然后配合lightning-output-field展示信息,这里展示的是一行四列的内容布局。

 1 <template>
 2     <lightning-record-view-form
 3                 record-id={recordId}
 4                 object-api-name="Account">
 5         <div class="slds-grid">
 6             <div class="slds-col">
 7                 <lightning-output-field field-name="Name"></lightning-output-field>
 8             </div>
 9             <div class="slds-col">
10                 <lightning-output-field field-name="Phone"></lightning-output-field>
11             </div>
12             <div class="slds-col">
13                 <lightning-output-field field-name="AnnualRevenue"></lightning-output-field>
14             </div>
15             <div class="slds-col">
16                 <lightning-output-field field-name="Industry"></lightning-output-field>
17
18             </div>
19         </div>
20     </lightning-record-view-form>
21 </template>

myComponentWithRecordView.js

1 import { LightningElement, api } from 'lwc';
2
3 export default class MyComponentWithRecordView extends LightningElement {
4     @api recordId;
5 }

显示效果:

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

 3. lightning-record-edit-form

lightning-record-form可以实现create/edit功能,和view的情况一样,当用户想要深度自定义时,lightning-record-form显然达不到需求,这个时候我们就需要 lightning-record-edit-form。
此组件通常和lightning-input-field一起用,用来显示需要编辑的字段。我们针对布局中偶尔可能需要显示只读字段,我们可以使用lightning-output-field以及lightning-formatted-name一起搭配使用。
此组件支持的方法和lightning-record-form基于edit模式下差不太多,同lightning-record-form一样,如果想要创建记录,只需要record-id为空即可。

lightning-record-form与lightning-record-edit-form使用不同的地方可以整理两点:

1) lightning-record-form有cancel事件,lightning-record-edit-form没有,需要使用lightning-button展示。针对cancel按钮的默认处理方式也不同,针对lightning-record-form点击cancel以后会默认恢复view的状态,针对lightning-record-edit-form不可以,我们需要针对字段调用reset方法才可以;
2) lightning-record-edit-form 需要使用lightning-button并且type为submit才可以正常提交表单,lightning-record-form不需要。

 下面通过一个demo进行更好的了解。

recordEditFormSample.html

 1 <template>
 2     <lightning-record-edit-form
 3         record-id={recordId}
 4         object-api-name={objectApiName}
 5         onsubmit={handleSubmit}
 6         onload={handleLoad}
 7         onsuccess={handleSuccess}
 8         onerror={handleError}
 9         >
10         <lightning-messages></lightning-messages>
11         <lightning-input-field field-name="Name"></lightning-input-field>
12         <lightning-input-field field-name="Industry"></lightning-input-field>
13         <lightning-input-field field-name="AnnualRevenue"></lightning-input-field>
14         <div class="slds-m-top_medium">
15             <lightning-button class="slds-m-top_small" label="Cancel" onclick={handleReset}></lightning-button>
16             <lightning-button class="slds-m-top_small" type="submit" label="Save Record"></lightning-button>
17         </div>
18     </lightning-record-edit-form>
19 </template>

recordEditFormSample.js

 1 /* eslint-disable no-console */
 2 import { LightningElement, api } from 'lwc';
 3 import { ShowToastEvent } from 'lightning/platformShowToastEvent';
 4 export default class RecordEditFormSample extends LightningElement {
 5
 6     @api recordId;
 7     @api objectApiName;
 8
 9     handleSubmit(event) {
10         event.preventDefault();       // stop the form from submitting
11         const fields = event.detail.fields;
12         if(fields.Industry === null || fields.Industry === '') {
13             const evt = new ShowToastEvent({
14                 title: "Account Operated Failed",
15                 message: "Account Industry cannot be blank",
16                 variant: "error"
17             });
18             this.dispatchEvent(evt);
19             return;
20         }
21         this.template.querySelector('lightning-record-edit-form').submit(fields);
22     }
23
24     handleLoad(event) {
25         console.log('execute load');
26     }
27
28     handleSuccess(event) {
29         const evt = new ShowToastEvent({
30             title: "Account Operated Success",
31             message: "Record is :" + event.detail.id,
32             variant: "success"
33         });
34         this.dispatchEvent(evt);
35     }
36
37     handleError(event) {
38         const evt = new ShowToastEvent({
39             title: "Account Operated Failed",
40             message: event.detail.message,
41             variant: "error"
42         });
43         this.dispatchEvent(evt);
44     }
45
46     handleReset(event) {
47         const inputFields = this.template.querySelectorAll(
48             'lightning-input-field'
49         );
50         if (inputFields) {
51             inputFields.forEach(field => {
52                 field.reset();
53             });
54         }
55      }
56 }

效果展示

1) 包含错误的情况展示

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

2. 正常保存的展示

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

 二. Wire Service

从上面内容可以看到,LDS已经很强大了,但是针对LDS处理不了的情况呢,比如获取父表信息,对数据中的内容进行格式化处理,这些可能标准功能很难达到或者达不到,这种我们便需要wire adapter去对LDS进行增强。

1. 使用封装的函数进行LDS增强

我们在组件中使用@wire标签在javascript中去获取数据,这些数据由lightning/ui*Api 模块的一个wire adapter获取。

wire adapter有很多强大的功能,比如getRecord / getFieldValue(record, field)。 我们在代码中经常会看到 import salesforce/xxx 以及 import lightning/ui*Api/xxx,我们会在下一节LWC博客中详细的讲解。

我们称wire service在某种程度上是reactive的,原因是它提供了一个reactive的变量,我们使用$符号声明在变量前面,当这个变量改变以后,wire service将会获取一个新的版本的数据,从而重新渲染component。

我们基于三个步骤使用wire service。

1 import { adapterId } from 'adapterModule';
2 @wire(adapterId, adapterConfig)
3 propertyOrFunction;

adapterId: wire adapter的标识符。比如我们需要用到lightning/uiRecordApi中的getRecord,那getRecord为这里的adapterId;
adapterModule:当前这个标识符封装在哪个适配器模块中,lwc封装了好多的wire adapter 标识符,他们在以下的adapterModule中:lightning/uiListApi / lightning:uiObjectInfoApi / lightning:uiRecordApi
adapterConfig:针对wire adapter特定的配置对象信息。配置对象的属性值可以是字符串,也可以通过@salesforce/schema方式引入的表和字段信息。salesforce比较推荐后者;
propertyOrFunction:一个私有变量或者方法,这个变量或者方法从wire service通过配置信息获取到最终的数据。如果这个是一个变量声明了wire,返回的结果为data property以及error property,如果这个是一个方法声明了wire,这个方法返回的结果包含data property 以及error property。

概念看起来比较绕,通过一个demo(getRecord)便可以更好的了解。
https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.reference_wire_adapters_record

getRecord wire adapter在lightning/uiRecordApi模块下,所以第一个步骤确定为:
import {getRecord} from 'lightning/uiRecordApi'

adapterConfig 针对getRecord有两个可以的配置项。

1) { recordId: string, fields: string[], optionalFields?: string[]}

2){ recordId: string, layoutTypes: string[], modes?: string[], optionalFields?: string[]}

  • recordId代表想要获取的记录的ID
  • fields / layoutTypes这两个必须要有一个存在。fields代表当前想要查询的字段,官方建议我们使用@salesforce/schema方式获取相关的metadata信息。这里需要注意的是,如果当前的上下文用户没有针对字段的访问权限,将会报错。
  • layoutTypes: compact/full这两个取值,因为pagelayout可能会有改变,所以针对要求固定字段的情况下,使用上述fields方式。如果要跟随page layout方式,可以选择此方式
  • modes: 当选择了layoutTypes以后,我们可以选择modes,可选择的值为Create/Edit/View。
  • optionalFields和fields功能类似,区别为如果引入一个上下文用户没有访问权限的字段,使用此参数不会报错,没有权限的字段对应的值不会返回而已。

demo用于获取account的name并对name进行每个字母都大写处理:

 RecordViewFormWithWireService.js:使用wire adapter中的getRecord获取相关metadata的value,然后进行format处理。salesforce建议我们获取metadata命名采用object_field_name方式,当然这个是规范,不是规则。这里之所以对wiredAccount获取值部分添加data不为undefined原因是当我们加载数据时,最开始wiredAccount为{},所以get accountName方法会挂掉提示undefined信息,取Account Name值有两种方式,一种是通过各种点的方式取到,另一个是通过wire service封装的getFieldValue方法获取。

 1 /* eslint-disable no-console */
 2 import { LightningElement,wire,api,track } from 'lwc';
 3 import { getRecord,getFieldValue } from 'lightning/uiRecordApi';
 4 import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';
 5 import ACCOUNT_INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
 6 import ACCOUNT_ANNUAL_REVENUE from '@salesforce/schema/Account.AnnualRevenue';
 7 const ACCOUNT_FIELDS = [ACCOUNT_NAME_FIELD,ACCOUNT_INDUSTRY_FIELD,ACCOUNT_ANNUAL_REVENUE];
 8 export default class RecordViewFormWithWireService extends LightningElement {
 9     @api recordId;
10
11     @wire(getRecord,{recordId:'$recordId',fields:ACCOUNT_FIELDS})
12     wiredAccount;
13
14     get accountName() {
15         // console.log(JSON.stringify(this.wiredAccount));
16         // console.log('xx');
17         // if(this.wiredAccount.data !== undefined) {
18         //     return this.wiredAccount.data.fields.Name.value.toUpperCase();
19         // }
20         // return '';
21         return this.wiredAccount.data != undefined ? getFieldValue(this.wiredAccount.data,ACCOUNT_NAME_FIELD).toUpperCase() : '';
22     }
23 }

  recordViewFormWithWireService.html

1 <template>
2     {accountName}
3 </template>

结果展示:

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

上面的wire service中只简单的描述了getRecord的用法以及顺带描述了getFieldValue,LWC提供了很多的wire adapter function,下一节的博客会有详细的说明。当我们使用了wire adapter增强LDS以后,可以做到更强的功能,比如获取父对象字段值,进行字段值的format。但是我们想要更复杂的操作,比如对数据进行filter,获取子数据信息,那我们就得需要访问apex获取数据了。下面内容为通过apex获取数据。

2. 和后台apex方法交互

有两种方式可以调用apex方法,一种是wire方式直接调用,另外一种通过指定的命令方式。下面对这两种方式进行简单的介绍。

两种方式的第一步均需要引入这个需要调用的apex

1 import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';

apexMethodName—用来识别引入的apex方法的名称。这个名称通常和方法名称相同并且后期引用均使用此名称。
apexMethodReference—引用的apex中的method的名称
Classname—当前的method所在的apex class的名称
Namespace—当前的namespace,默认为c.如果是c的情况下可以自动省略

比如我们在 ContactController中有一个方法名字是getContactList,则我们的apexMethodName默认应该命名为 getContactList,
apexMethodRefernce为getContactList,Classname为ContactController,Namespace为c。如下所示:
import getContactList from '@salesforce/apex/ContactController.getContactList';

我们在aura项目中,如果js中调用apex中的方法要求当前的方法声明为@AuraEnabled,同样使用LWC也要求后台的apex方法需要声明为@AuraEnabled,并且方法要求static & (public / global)
当我们针对的是获取数据,没有DML操作情况下,我们可以声明方法为cacheable=true去提升客户端的性能。如果当前的数据存在DML操作,不是纯粹的取数据,则不应该声明cacheable=true。

我们针对和apex交互的两种方式,使用wire方式必须要指定后台的apex方法声明 cacheable=true,使用命令方式则不需要有这个限制。当然,如果我们使用了cacheable声明以后,当我们觉得数据可能不是最新或者是有问题的数据情况下,我们可以调用refreshApex()去获取最新的数据。

如果我们apex中涉及到和外部系统的长时间的交互,我们可以对方法声明 continuation=true,如果同时声明了 cacheable以及continuation,则中间使用空格分隔。如下所示:
@AuraEnabled(continuation=true cacheable=true)

后台方法的要求已经说完了,下面介绍两种方式的调用。

1)wire方式调用:

@wire(apexMethod, { apexMethodParams })
propertyOrFunction;

apexMethod: 和上面import的apexMethodName相同;
apexMethodParams:apexMethodName传递的参数。后台的方法可以无参数和有参数,如果无参数将apexMethodParams设置为null,如果有参数则传递此参数。这里需要注意的是,如果apexMethodParams设置为null可以正常调用,意思是无参方法,如果此参数为undefined,则wire不会调用后台的此方法。
propertyOrFunction:wire装载给变量或者方法。如果是变量,后台方法如果没有错误情况下,返回的是正常的返回内容。否则返回的是error变量。
如果是方法,则方法对应的是一个object,object中包含了data变量或者error变量。说起来比较绕,通过一个例子更好的了解。

下面的例子为wire装载给方法。wiredContacts返回变量中有两个参数,error,data。我们通常判断如果data不为空,则正常返回。如果error不为空,则代表当前的数据获取存在异常。demo中没有形参,如果想要有形参,在getContactList方法后面使用逗号分隔以后添加形参

 1 @wire(getContactList)
 2 wiredContacts({ error, data }) {
 3     if (data) {
 4         this.contacts = data;
 5         this.error = undefined;
 6     } else if (error) {
 7         this.error = error;
 8         this.contacts = undefined;
 9     }
10 }

下面的例子为wire装载给变量。findContacts有一个参数searchKey。我们使用$符号代表当前的变量是动态的reactive的,返回值给contacts。如果正常返回,contacts里面是后台的apex 返回的数据列表。如果存在error,contacts里面是error变量。

1 @wire(findContacts, { searchKey: '$searchKey' })
2 contacts;

因为后台声明了cacheable以后,只有刷新以后才能重新装载最新版本的数据。LWC针对wire声明的变量提供了refreshApex方法。使用两步走。

1. import { refreshApex } from '@salesforce/apex';

2. refreshApex(wiredProperty)

其中wiredProperty为我们使用wire标签声明的变量。这里需要注意的一点是,针对wire声明的方法无法使用此方法进行刷新缓存操作。
如果声明了方法我们想清空缓存,需要先声明变量。然后方法中对此变量赋值,然后再refreshApex中传递声明的变量。

下面以一个例子更好的了解wire方式调用apex代码以及refreshApex的使用。

ContactController中声明了一个方法findContacts,形参是一个string用来传递想要搜索的contact的name。此方法使用AuraEnabled并且指定了cacheable=true,则LWC针对前台处理可以使用wire方式,也可以使用命令方式。

1 public with sharing class ContactController {
2     @AuraEnabled(cacheable=true)
3     public static List<Contact> findContacts(String searchKey) {
4         String key = '%' + searchKey + '%';
5         return [SELECT Id, Name, Title, Phone, Email FROM Contact WHERE Name LIKE :key LIMIT 10];
6     }
7 }

 contactListSearchWithWireService.html:展示搜索出来的contact的信息,上面有一个输入框以及两个按钮,点击search进行搜索,点击refresh清空缓存重新获取。

 1 <template>
 2     <lightning-card icon-name="custom:custom63">
 3         <div class="slds-m-around_medium">
 4             <lightning-input type="search" class="slds-m-bottom_small" label="Search" value={searchKey}></lightning-input>
 5             <lightning-button-group>
 6                 <lightning-button variant="brand" label="search" onclick={handleSearch}></lightning-button>
 7                 <lightning-button variant="brand" label="Refresh" onclick={handleRefresh}></lightning-button>
 8             </lightning-button-group>
 9             <template if:true={contacts}>
10                 <template for:each={contacts} for:item="contact">
11                     <p key={contact.Id}>{contact.Name}</p>
12                 </template>
13             </template>
14             <template if:true={error}>
15                 <!-- TODO -->
16             </template>
17         </div>
18     </lightning-card>
19 </template>

contactListSearchWithWireService.js:这里需要注意的一点是我们使用wire装载的一个方法名字是wiredContacts,为了后期可以针对方法使用refreshApex,我们设置了storedContacts变量,并且在wire方法中设置了值,针对refreshApex方法我们更新此变量。因为我们在searchKey使用了$符号,标识它是reactive的,变化以后会重新执行方法,所以我们点击search时只需要赋值searchKey变量便可以达到调用wire方法重新读取数据的作用了。

 1 import { LightningElement, track,wire } from 'lwc';
 2 import findContacts from '@salesforce/apex/ContactController.findContacts';
 3 import {refreshApex} from '@salesforce/apex'
 4 export default class ContactListSearchWithWireService extends LightningElement {
 5     @track searchKey;
 6     @track contacts;
 7     @track errors;
 8     storedContacts;
 9
10     @wire(findContacts, { searchKey: '$searchKey' })
11     wiredContacts(storedContacts) {
12         this.storedContacts = storedContacts;
13         const {data,error} = storedContacts;
14         if(data) {
15             this.contacts = data;
16             this.errors = undefined;
17         } else if(error) {
18             this.errors = error;
19             this.contacts = undefined;
20         }
21     }
22
23     handleSearch(event) {
24         this.searchKey = this.template.querySelector('lightning-input').value;
25     }
26
27     handleRefresh(event) {
28         refreshApex(this.storedContacts);
29     }
30 }

 结果展示:

1) 当我们输入sa yang点击search 以后会搜索出来sa yang数据,即使后期sa yang这条数据有改掉不符合要求,点击search还会搜索出来,因为有缓存。

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

2) 当我们点击refresh以后,更改过的数据将不再展示在结果区域。

Salesforce LWC学习(五)  LDS &amp; Wire Service 实现和后台数据交互 &amp; meta xml配置-LMLPHP

 上面的demo我们使用wire装载函数以及针对函数情况下使用apexRefresh的方式。下面的demo为使用wire装载变量并且使用apexRefresh刷新变量缓存的demo。

 contactListSearchWithWireProperty.html:因为我们后台返回的是变量,所以我们针对for:each使用需要property.data方式。

 1 <template>
 2     <lightning-card icon-name="custom:custom63">
 3         <div class="slds-m-around_medium">
 4             <lightning-input type="search" class="slds-m-bottom_small" label="Search" value={searchKey}></lightning-input>
 5             <lightning-button-group>
 6                 <lightning-button variant="brand" label="search" onclick={handleSearch}></lightning-button>
 7                 <lightning-button variant="brand" label="Refresh" onclick={handleRefresh}></lightning-button>
 8             </lightning-button-group>
 9             <template if:true={contacts.data}>
10                 <template for:each={contacts.data} for:item="contact">
11                     <p key={contact.Id}>{contact.Name}</p>
12                 </template>
13             </template>
14             <template if:true={conacts.error}>
15                 <!-- TODO -->
16             </template>
17         </div>
18     </lightning-card>
19 </template>
ContactListSearchWithWireProperty.js:如果wire装载的是变量,我们直接在refreshApex方法传递此变量即可。
 1 import { LightningElement, track,wire } from 'lwc';
 2 import findContacts from '@salesforce/apex/ContactController.findContacts';
 3 import {refreshApex} from '@salesforce/apex'
 4 export default class ContactListSearchWithWireProperty extends LightningElement {
 5     @track searchKey;
 6
 7     @wire(findContacts, { searchKey: '$searchKey' })
 8     contacts;
 9
10     handleSearch(event) {
11         this.searchKey = this.template.querySelector('lightning-input').value;
12     }
13
14     handleRefresh(event) {
15         refreshApex(this.contacts);
16     }
17 }

两个demo的显示效果相同,这里不多做展示。

2) 使用命令方式调用后台方法。

我们使用wire方式操作后台的apex通过上面的两个例子可以很好的理解了,但是使用wire方式有一个大的前置条件,需要后台的方法声明cacheable=true。

我们针对数据获取的方法使用wire方式很好,但是针对DML操作的方法不能使用cacheable=true就只能使用我们这种命令方式的访问后台的方式。

此种方式的固定写法为:

1 methodName({ param : parameterObject })
2 .then(result => {
3 this.message = result;
4 this.error = undefined;
5 })
6 .catch(error => {
7 this.message = undefined;
8 this.error = error;
9 });

后台的要求区别已经说完了,再说一下前端的区别。使用wire方式返回的是一个stream data,并且参数是reactive的,只要参数改变,就会自动触发wire。使用上述方式返回的是promise,此种方式只能当次调用有效,如果后期有变化,则需要重新调用。
另外一点为refreshApex只能用在wire装载的方法和变量,使用此种方式不支持此方法。

参数部分为可选项,如果不传递参数则直接methodName()。如果传递参数使用{}方式传递即可。

我们还是使用上面的例子,只是把JS端改成以命令方式编写,html端重复内容不再粘贴(需要去掉refresh按钮),如下:

 1 import { LightningElement,track } from 'lwc';
 2 import findContacts from '@salesforce/apex/ContactController.findContacts';
 3 export default class ContactListSearchWithImperative extends LightningElement {
 4     @track searchKey;
 5     @track contacts;
 6     @track errors;
 7
 8     handleSearch() {
 9         this.searchKey = this.template.querySelector('lightning-input').value;
10         findContacts({searchKey:this.searchKey})
11             .then(result => {
12                 this.contacts = result;
13                 this.errors = undefined;
14             })
15             .catch(error =>{
16                 this.errors = error;
17                 this.contacts = undefined;
18             });
19     }
20 }

效果展示同上面相同。

三. Configuration File Tag

我们在创建一个LWC component时,会默认生成一个html / js /meta xml文件,其中meta xml 会指定LWC component很多配置特性,比如当前的LWC component可以引用在哪种类型的lightning page中,api version等配置信息。主要配置项有以下几点:

apiVersion: 指定LWC component的api version;

description:当前LWC component的描述信息,当显示在lightning app builder或者community builder的列表中我们鼠标移动到上面会展示此描述信息,此配置项是一个可选项,不必填;

isExposed:用来指定当前的component是否可以暴露给lightning app builder或者community builder使用;此标签很像aura中针对aura:component的implements的用法;

masterLabel: 用来指定当前的component在lightning app builder或者community builder显示的名字。默认名字显示的是定义的component的API name,如果我们想在列表初显示需要显示的名字,我们可以设置此字段。和description一样,这个是可选项,非必填;

 targets:用来指定当前的component在哪里可以添加。当我们指定isExposed为true时,则必须要有targets信息。targets下面有taget子标签,用来标识当前的component可以加在什么类型的lightning page中。target的可选值如下:

  • lightning__AppPage:允许当前的component在lightning app builder使用在app page中;
  • lightning__HomePage:允许当前的component在lightning app builder使用在home page中;
  • lightning__RecordPage:允许当前的component在lightning app builder使用在record page中;
  • lightning__Inbox:允许当前的component在lightning app builder中使用,用于为outlook/gmail集成添加email 应用窗;
  • lightningCommunity__Page:允许当前的component在community builder中使用在lightning community page;
  • lightningCommunity__Default:和lightningCommunity__Page共同使用。添加此项可以包括可配置的变量当这个component引用的时候;
  • lightningSnapin__ChatMessage:允许在Embedded Service Chat Setup中选择此component。

我们在项目中常用的配置就是lightning__AppPage / lightning__HomePage / lightning__RecordPage了。

 targetConfigs:用来配置不同类型的lightning page和不同的初始化的component 变量。一个lwc component可能有很多的变量声明,我们针对不同类型的lightning page中需要初始化不同的变量,便可以使用此标签去实现。和上面的targets一样,他也是一个父标签,内容在子标签targetConfig 声明。targetConfig有一个属性叫做targets,我们可以使用此属性去声明当前的配置项针对哪个类型的lightning page,针对多个类型的lightning page,我们可以使用逗号','分隔。例如:

<targetConfig targets="lightning__RecordPage,lightning__AppPage">

 targetConfig下可以配置三个子标签,分别是property / objects / supportedFormFactors,用来给变量进行初始化操作。下面对这三个子标签分别描述。

 1. Property: 我们在LWC js中会使用@api标签声明public变量,使用Property在引用在lightning app builder或者community builder的时候我们可以设置一些初始值以及初始化配置。Property有以下的属性:

  • type:用来声明变量的类型,比如 Integer/ String / Boolean .
  • required:用来标识当前的变量是否必须设置。默认值为false;
  • placeholder: 仅用于type为String的情况,用于当输入框为空的时候在输入框中展示的提示信息;
  • name:我们在js中声明的变量名称,两者必须完全匹配。
  • min: 当type为Integer的时候,设置我们想要设置变量的最小值;
  • max: 当type为Integer的时候,设置我们想要设置变量的最大值;
  • label:在工具中展示attribute的显示的label 名称;
  • description:在工具中展示attribute的描述信息;
  • default:当前attribute的默认值;
  • datasource:当type为string情况下需要渲染变量为picklist,则使用此属性,不同之间的字符串使用逗号','分割。

上述的属性中,只有name以及type是必填项,其他都是可选项。

2. objects:当我们在target中声明当前的LWC component在targetConfig中配置了可以引用在lightning record page时,我们可以指定当前的component可以用于哪些objects使用。
同targetConfigs一样,他也有一个子标签叫做object用来声明可以用在哪个object中使用。object标签不能使用*来声明可以引用所有objects

 3. supportedFormFactors:我们访问salesforce可能使用手机或者电脑,我们针对不同的媒介访问不同的页面(home page/ record page等)可能需要展示不同的大小,这时我们就需要设置supportedFormFactors了。针对此配置项的配置信息,详情可以查看https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.use_config_form_factors

总结:篇中主要介绍的是LDS在LWC中的使用方式以及在LDS功能无法满足情况下,如何使用wire service以及访问后台方法进行增强。篇中有引入salesforce/ lightning/ui*Api甚至PageReference等信息下篇LWC内容会详细阐述。篇中有错误的地方欢迎指出,有不懂的欢迎留言。

09-01 14:35