Salesforce LWC学习(二十三) Lightning Message Service 浅谈
本篇参考:
https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_specialist
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel
讲这个以前先以一个例子作为展开。lwc的 superbadge中有一个功能为 左侧 Gallery列表中点击一个图片,在右侧 details会展示这个船的详细信息。
以往我们可能想着,简单,将这两部分组成到同一个父组件中,Gallery中的某个item点击以后,传递一个事件到父,父进行handler处理以后,将record id 传递给右侧的组件,右侧组件这个reRender一下就搞定了。
先说一下上面的分析能不能实现? 能,而且肯定能。因为事件的传播以后,父组件是肯定可以监听到,监听到处理到,变量绑定到其他的子可以实现。那这样好不好呢?
老实地说,好与不好不清楚,因为项目上更关注三点:
1. 稳定性
2. 性能
3. 可扩展性
这种方式这三点应该都没有太大的问题,问题在于当需求变动以后,父 组件里的逻辑将可能会越来越多。父组件我们的初衷可能是套个壳子,让他们有亲戚关系进行简单的信息交换,结果爸爸需求可能越来越多最后可能承受不能承受之重。
有没有其他的方式去实现即使两个组件没有关系,但是也可以做到信息之间的传递呢?今天的 Lightning Message Service便可以实现这个需求。
一. Lightning Message Service
Lightning Message Service用于在 VF Page, Aura Component, lwc之间进行跨DOM 通讯。可以在单一的 lightning page或者是多个page之间进行通讯。操作的步骤为发布订阅原则。听到发布订阅,大家可能想到 Streaming API 或者是 Platform Event, salesforce针对不同的通讯场景有多种的广播订阅模型进行选择,页面之间的跨DOM通讯使用 Lightning Message Service。值得注意的是,在 spring 20的时候这个功能还是一个 beta版本,在现在的 summer20已经是一个正式的功能,所以可以放心使用。 Lightning Message Service的特别的细节的介绍以及limitation还请参看上面的链接,接下来讲一下具体的使用步骤。
1. 创建 Message Channel
我们在vs code项目的目录中查看是否有messageChannels这个目录,如果不包含就手动创建一下。新建一个以messageChannel-meta.xml 结尾的文件即可。篇中demo创建的是 BoatMessageChannel.messageChannel-meta.xml。进行相关的信息填充以后保存到环境即可。如果曾经有创建过,需要从sandbox或者developer环境导入下来,执行 sfdx force:source:retrieve -m LightningMessageChannel即可retrieve下来。
那这个xml应该如何写呢?这个时候就需要看 salesforce的 metadata api关于 LightningMessageChannel的介绍:https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_lightningmessagechannel.htm
lightning message channel包括以下的几部分构成:
- description:lightning message channel的描述信息;
- isExposed:标签指定当前的 lightning message channel是否暴露出来,类似我们做 lwc的metadata xml的配置;
- masterLabel: lightning message channel的label名称,这个属性是一个必填字段;
- lightningMessageFields:这个是 lightning message channel的核心属性,通过这个负载字段用来声明广播订阅接收的变量信息。针对这个属性有两个子属性。description用来描述 lightningMessageField的描述信息,fieldName用来描述当前 lightningMessageField要传播的字段的api name。
下面的xml是BoatMessageChannel的全的信息,以便更好的了解 lightning message channel。sample中我们声明了一个fieldName为recordId的名称是 BoatMessageChannel的 lightning message channel信息。如果需要传递多个变量,只需要多写几个 <lightningMessageFields>即可。
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<description>This is a sample Lightning Message Channel for the Lightning Web Components Superbadge.</description>
<isExposed>true</isExposed>
<lightningMessageFields>
<description>This is the record Id that changed</description>
<fieldName>recordId</fieldName>
</lightningMessageFields>
<masterLabel>BoatMessageChannel</masterLabel>
</LightningMessageChannel>
2. 定义Lightning Message Service的作用域
广播订阅机制一个另外的重要的事情就是作用域的问题,即哪种情况订阅者可以订阅到广播源发送的消息。是整个应用级别,还是某些active区域。如果我们在lwc组件间进行广播订阅时,一定要写上@wire(MessageContext)去让scope特性可用。下图为订阅的scope的模型。salesforce默认的订阅模型的scope范围是active的,如果我们希望订阅范围扩大,需要lwc component头部引入APPLICATION_SCOPE,这个是在 ‘lightning/messageService’中。项目中没有要求指定哪种scope,如果有要求即使在Hidden的tab中也可以接收到相关的订阅消息并进行什么处理,可以设置成整个应用级别,篇中demo设置的即应用级别。
3. 广播一个message Channel
我们在广播或者订阅以前都需要先引入我们创建的 message channel,使用 @salesforce/messageChannel进行引用,如果是包里的内容,需要添加namespace信息,如果不是包里的,直接使用channelReference即可。channelReference需要以 __c结尾,这个是强制的要求。
import channelName from '@salesforce/messageChannel/namespace__channelReference';
所以我们篇中的demo中的 messageChannel名称为BoatMessageChannel,所以引入信息如下,其中 BOATMC的名字任意起,messageChannel以__c结尾。
import BOATMC from '@salesforce/messageChannel/BoatMessageChannel__c';
引入以后我们进行发布操作,lightning/messageService包含了 publish方法,我们在发布以前也需要在头部以前引入,因为lwc需要强制使用 MessageContext让scope可用,这里也一并引入 MessageContext
import { publish, MessageContext } from 'lightning/messageService';
上面的准备工作完成以后,只需要调用publish方法即可实现发布。publish有以下的几个参数需要传递。
- messageContext: messageContext,这里默认填写我们使用wire方法获取声明的变量即可。
- messageChannel:头部声明的 messageChannel的引用变量;
- message:一个序列化的JSON object信息,根据messageChannel的字段设置去填充这个字段即可。
下面有一个简单的publish代码块进行更好的了解。如果我们在 BOATMC中声明了两个变量,一个是 recordId,一个是recordData,则我们的 publish方法包含这三部分即可。
@wire(MessageContext)
messageContext; handleClick() {
const message = {
recordId: '001xx000003NGSFAA4',
recordData: {accountName: 'Burlington Textiles Corp of America'}
};
publish(this.messageContext, BOATMC, message);
}
4. 订阅 messageChannel
广播源广播出去一个以后,订阅的component如何订阅呢?如果小伙伴先仔细查看上面的连接以后发现在 lightning/messageService里面同样封装了一个用来订阅的方法:subscribe,如果想要取消订阅,只需要调用unsubscribe()即可。
同样的操作,第一个步骤,需要先引入 MessageContext,这里不做重复的描述。直接描述一下 subscribe方法,里面有4个参数。
- messageContext:描述同上;
- messageChannel:描述同上;
- listener:一个函数用来当发布以后处理message用;
- subscriberOptions:这个是一个可选操作,当我们指定从APPLICATION级别接收消息情况下,设置成{scope: APPLICATION_SCOPE},如果APPLICATION级别,头部需要从lightning/messageService引入APPLICATION_SCOPE。
APPLICATION_SCOPE级别的sample,用于当订阅以后,调用 handleMessage去处理具体订阅逻辑, message.fieldName即可取出相关的值,比如message.recordId即可以取出 发布时 recordId这个变量对应的值。
this.subscription = subscribe(
this.messageContext,
BOATMC,
(message) => {
this.handleMessage(message);
},
{ scope: APPLICATION_SCOPE }
);
active级别的sample:只需要将最后一个参数 scope信息删除即可。
this.subscription = subscribe(
this.messageContext,
BOATMC,
(message) => {
this.handleMessage(message);
}
);
unsubscription这里不在介绍,看一下上面的文档以及参数介绍大家便可以进行正常的学习。
二. 代码实现
我们在第一部分已经介绍了 Lightning Message Service的基础知识以及方法的使用,下面的内容通过demo可以更好的去学习以及理解。
1. 创建 Message Channel,这里步骤同上面,创建了一个BoatMessageChannel 的 Message Channel,设置了一个 recordId的变量
2. 广播操作
BoatDataService.cls:这里对代码进行了删减,只保留了这次demo用到的方法,通过 getBoats获取船的数据列表信息3.
public with sharing class BoatDataService { public static final String LENGTH_TYPE = 'Length';
public static final String PRICE_TYPE = 'Price';
public static final String TYPE_TYPE = 'Type';
@AuraEnabled(cacheable=true)
public static List<Boat__c> getBoats(String boatTypeId) {
// Without an explicit boatTypeId, the full list is desired
String query = 'SELECT '
+ 'Name, Description__c, Geolocation__Latitude__s, '
+ 'Geolocation__Longitude__s, Picture__c, Contact__r.Name, '
+ 'BoatType__c, BoatType__r.Name, Length__c, Price__c '
+ 'FROM Boat__c';
if (String.isNotBlank(boatTypeId)) {
query += ' WHERE BoatType__c = :boatTypeId';
}
query += ' WITH SECURITY_ENFORCED ';
return Database.query(query);
} @AuraEnabled(cacheable=true)
public static List<Boat__c> getSimilarBoats(Id boatId, String similarBy) {
List<Boat__c> similarBoats = new List<Boat__c>();
List<Boat__c> parentBoat = [SELECT Id, Length__c, Price__c, BoatType__c, BoatType__r.Name
FROM Boat__c
WHERE Id = :boatId
WITH SECURITY_ENFORCED];
if (parentBoat.isEmpty()) {
return similarBoats;
}
if (similarBy == LENGTH_TYPE) {
similarBoats = [
SELECT Id, Contact__r.Name, Name, BoatType__c, BoatType__r.Name, Length__c, Picture__c, Price__c, Year_Built__c
FROM Boat__c
WHERE Id != :parentBoat.get(0).Id
AND (Length__c >= :parentBoat.get(0).Length__c / 1.2)
AND (Length__c <= :parentBoat.get(0).Length__c * 1.2)
WITH SECURITY_ENFORCED
ORDER BY Length__c, Price__c, Year_Built__c
];
} else if (similarBy == PRICE_TYPE) {
similarBoats = [
SELECT Id, Contact__r.Name, Name, BoatType__c, BoatType__r.Name, Length__c, Picture__c, Price__c, Year_Built__c
FROM Boat__c
WHERE Id != :parentBoat.get(0).Id
AND (Price__c >= :parentBoat.get(0).Price__c / 1.2)
AND (Price__c <= :parentBoat.get(0).Price__c * 1.2)
WITH SECURITY_ENFORCED
ORDER BY Price__c, Length__c, Year_Built__c
];
} else if (similarBy == TYPE_TYPE) {
similarBoats = [
SELECT Id, Contact__r.Name, Name, BoatType__c, BoatType__r.Name, Length__c, Picture__c, Price__c, Year_Built__c
FROM Boat__c
WHERE Id != :parentBoat.get(0).Id
AND (BoatType__c = :parentBoat.get(0).BoatType__c)
WITH SECURITY_ENFORCED
ORDER BY Price__c, Length__c, Year_Built__c
];
}
return similarBoats;
} }
boatSearchResults.html:使用 layout方式展示一些数据
<template>
<lightning-tabset variant="scoped">
<lightning-tab label="Gallery">
<template if:true={boats.data}>
<div class="slds-scrollable_y">
<lightning-layout horizontal-align="center" multiple-rows>
<template for:each={boats.data} for:item="boat">
<lightning-layout-item key={boat.Id} padding="around-small" size="12" small-device-size="6"
medium-device-size="4" large-device-size="3">
<c-boat-tile boat={boat} selected-boat-id={selectedBoatId}
onboatselect={updateSelectedTile}></c-boat-tile>
</lightning-layout-item>
</template>
</lightning-layout>
</div>
</template>
</lightning-tab>
</lightning-tabset>
</template>
boatSearchResults.js:这里我们可以看到,首先头部引入了 MessageChannel 以及使用了 MessageContext以及 publish方法,下面的方法中使用了 publish方法去进行了广播操作
import { LightningElement, wire, api, track } from 'lwc';
import getBoats from '@salesforce/apex/BoatDataService.getBoats';
import { publish, MessageContext } from 'lightning/messageService';
import BoatMC from '@salesforce/messageChannel/BoatMessageChannel__c'; export default class BoatSearchResults extends LightningElement {
boatTypeId = '';
@track boats;
@track draftValues = [];
selectedBoatId = '';
isLoading = false;
error = undefined;
wiredBoatsResult; @wire(MessageContext) messageContext; columns = [
{ label: 'Name', fieldName: 'Name', type: 'text', editable: 'true' },
{ label: 'Length', fieldName: 'Length__c', type: 'number', editable: 'true' },
{ label: 'Price', fieldName: 'Price__c', type: 'currency', editable: 'true' },
{ label: 'Description', fieldName: 'Description__c', type: 'text', editable: 'true' }
]; @api
searchBoats(boatTypeId) {
this.isLoading = true;
this.notifyLoading(this.isLoading);
this.boatTypeId = boatTypeId;
} @wire(getBoats, { boatTypeId: '$boatTypeId' })
wiredBoats(result) {
this.boats = result;
if (result.error) {
this.error = result.error;
this.boats = undefined;
}
this.isLoading = false;
this.notifyLoading(this.isLoading);
} updateSelectedTile(event) {
this.selectedBoatId = event.detail.boatId;
this.sendMessageService(this.selectedBoatId);
} notifyLoading(isLoading) {
if (isLoading) {
this.dispatchEvent(new CustomEvent('loading'));
} else {
this.dispatchEvent(CustomEvent('doneloading'));
}
} sendMessageService(boatId) {
publish(this.messageContext, BoatMC, { recordId : boatId });
}
}
boatTile.html:展示gallery中的每一个item的UI
<template>
<div onclick={selectBoat} class={tileClass}>
<div style={backgroundStyle} class="tile"></div>
<div class="lower-third">
<h1 class="slds-truncate slds-text-heading_medium">{boat.Name}</h1>
<h2 class="slds-truncate slds-text-heading_small">{boat.Contact__r.Name}</h2>
<div class="slds-text-body_small">
Price: <lightning-formatted-number maximum-fraction-digits="2" format-style="currency" currency-code="USD" value={boat.Price__c}> </lightning-formatted-number>
</div>
<div class="slds-text-body_small"> Length: {boat.Length__c} </div>
<div class="slds-text-body_small"> Type: {boat.BoatType__r.Name} </div>
</div>
</div>
</template>
boatTile.js:item点击以后调度事件,boatSearchResults这个父组件 handle事件,从而实现了广播的发布
import { LightningElement, api} from "lwc";
const TILE_WRAPPER_SELECTED_CLASS = "tile-wrapper selected";
const TILE_WRAPPER_UNSELECTED_CLASS = "tile-wrapper";
export default class BoatTile extends LightningElement {
@api boat;
@api selectedBoatId;
get backgroundStyle() {
return `background-image:url(${this.boat.Picture__c})`;
}
get tileClass() {
return this.selectedBoatId == this.boat.Id ? TILE_WRAPPER_SELECTED_CLASS : TILE_WRAPPER_UNSELECTED_CLASS;
}
selectBoat() {
this.selectedBoatId = !this.selectedBoatId;
const boatselect = new CustomEvent("boatselect", {
detail: {
boatId: this.boat.Id
}
});
this.dispatchEvent(boatselect);
}
}
3. 消息订阅
boatDetailTabs.html:用来展示发布过来的指定的记录的详细信息
<template>
<template if:false={wiredRecord.data}>
<!-- lightning card for the label when wiredRecord has no data goes here -->
<lightning-card class= "slds-align_absolute-center no-boat-height">
<span>{label.labelPleaseSelectABoat}</span>
</lightning-card>
</template>
<template if:true={wiredRecord.data}>
<!-- lightning card for the content when wiredRecord has data goes here -->
<lightning-card>
<lightning-tabset variant="scoped">
<lightning-tab label={label.labelDetails}>
<lightning-card icon-name={detailsTabIconName} title={boatName}>
<lightning-button slot="actions" title={boatName} label={label.labelFullDetails} onclick={navigateToRecordViewPage}></lightning-button>
<lightning-record-view-form density="compact"
record-id={boatId}
object-api-name="Boat__c">
<lightning-output-field field-name="BoatType__c" class="slds-form-element_1-col"></lightning-output-field>
<lightning-output-field field-name="Length__c" class="slds-form-element_1-col"></lightning-output-field>
<lightning-output-field field-name="Price__c" class="slds-form-element_1-col"></lightning-output-field>
<lightning-output-field field-name="Description__c" class="slds-form-element_1-col"></lightning-output-field>
</lightning-record-view-form>
</lightning-card>
</lightning-tab> </lightning-tabset>
</lightning-card>
</template>
</template>
boatDetailsTabs.js:connectedCallback生命周期函数中进行订阅操作,订阅到boatId的值,从而 getRecord展示左侧galery选择的数据的详情信息
// Custom Labels Imports
// import labelDetails for Details
// import labelReviews for Reviews
// import labelAddReview for Add_Review
// import labelFullDetails for Full_Details
// import labelPleaseSelectABoat for Please_select_a_boat
// Boat__c Schema Imports
// import BOAT_ID_FIELD for the Boat Id
// import BOAT_NAME_FIELD for the boat Name
import { LightningElement, api,wire } from 'lwc';
import labelDetails from '@salesforce/label/c.Details';
import labelReviews from '@salesforce/label/c.Reviews';
import labelAddReview from '@salesforce/label/c.Add_Review';
import labelFullDetails from '@salesforce/label/c.Full_Details';
import labelPleaseSelectABoat from '@salesforce/label/c.Please_select_a_boat';
import BOAT_ID_FIELD from '@salesforce/schema/Boat__c.Id';
import BOAT_NAME_FIELD from '@salesforce/schema/Boat__c.Name';
import { getRecord,getFieldValue } from 'lightning/uiRecordApi';
import BOATMC from '@salesforce/messageChannel/BoatMessageChannel__c';
import { APPLICATION_SCOPE,MessageContext, subscribe } from 'lightning/messageService';
const BOAT_FIELDS = [BOAT_ID_FIELD, BOAT_NAME_FIELD];
import {NavigationMixin} from 'lightning/navigation';
export default class BoatDetailTabs extends NavigationMixin(LightningElement) {
@api boatId; label = {
labelDetails,
labelReviews,
labelAddReview,
labelFullDetails,
labelPleaseSelectABoat,
}; // Decide when to show or hide the icon
// returns 'utility:anchor' or null
get detailsTabIconName() {
return this.wiredRecord && this.wiredRecord.data ? 'utility:anchor' : null;
} // Utilize getFieldValue to extract the boat name from the record wire
@wire(getRecord,{recordId: '$boatId', fields: BOAT_FIELDS})
wiredRecord; get boatName() {
return getFieldValue(this.wiredRecord.data, BOAT_NAME_FIELD);
} // Private
subscription = null;
// Initialize messageContext for Message Service
@wire(MessageContext)
messageContext; // Subscribe to the message channel
subscribeMC() {
if(this.subscription) { return; }
// local boatId must receive the recordId from the message
this.subscription = subscribe(
this.messageContext,
BOATMC,
(message) => {
this.boatId = message.recordId;
},
{ scope: APPLICATION_SCOPE }
);
} // Calls subscribeMC()
connectedCallback() {
this.subscribeMC();
} }
效果展示:当点击左侧列表的图像,右侧会展示当条的具体信息。
总结:篇中代码看上去可能有点冗余,因为superbadege中还有其他功能,所以只是做了简单的删减,想要复现这种效果可以在lwc superbadge安装一下 unmanaged package然后代码赋值粘贴可以看到效果。篇中只是简单介绍了一下lightning message service的简单实用,limitation以及unsubscription这里不做过多的讲解,自行查看官方文档。篇中有错误地方欢迎指出,有不懂欢迎留言。
Salesforce LWC学习(二十三) Lightning Message Service 浅谈的更多相关文章
- Salesforce LWC学习(二十四) Array.sort 浅谈
本篇参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort sal ...
- Salesforce LWC学习(二十一) Error浅谈
本篇参考:https://developer.salesforce.com/docs/component-library/documentation/en/lwc/data_error https:/ ...
- Salesforce LWC学习(二十七) File Upload
本篇参考: https://developer.salesforce.com/docs/component-library/bundle/lightning-file-upload/documenta ...
- Salesforce LWC学习(二十九) getRecordNotifyChange(LDS拓展增强篇)
本篇参考: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/data_ui_api https ...
- Salesforce LWC学习(二十六) 简单知识总结篇三
首先本篇感谢长源edward老哥的大力帮助. 背景:我们在前端开发的时候,经常会用到输入框,并且对这个输入框设置 required或者其他的验证,当不满足条件时使用自定义的UI或者使用标准的 inpu ...
- Salesforce LWC学习(二) helloWorld程序在VSCode中的实现
上一篇我们简单的描述了一下Salesforce DX的配置以及CLI的简单功能使用,此篇主要简单描述一下LWC如何实现helloWorld以及LWC开发时应该注意的一些规范. 做国内项目的同学直观的感 ...
- Salesforce LWC学习(二十二) 简单知识总结篇二
本篇参看: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reactivity_fi ...
- Salesforce LWC学习(二十五) Jest Test
本篇参看: https://trailhead.salesforce.com/content/learn/modules/test-lightning-web-components https://j ...
- Salesforce LWC学习(二十八) 复制内容到系统剪贴板(clipboard)
本篇参考: https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipb ...
随机推荐
- python IF while逻辑判断语句
if判断语句 if 1==1 and 2==2: pass else: print('error') if 1==1 or 2==2: pass else: print('error') while循 ...
- Android Json转换类对象,并使用
长话短说,先上代码(今天的代码是有史以来最短的)(今天的课也是有史以来最精简...) 然后就是介绍Song是啥,上面的网站应该知道,是网易云的,不过为啥会变成这样,因为这是网易云的API网站 反正就是 ...
- javaweb 测试
题目要求: 1登录账号:要求由6到12位字母.数字.下划线组成,只有字母可以开头:(1分) 2登录密码:要求显示“• ”或“*”表示输入位数,密码要求八位以上字母.数字组成.(1分) 3性别:要求用单 ...
- CSS表单与数据表(上)
表单在现代Web应用中占据着重要地位. 表单可以很简单,也可以非常复杂,要横跨几个页面. 除了从用户哪里获得数据,Web应用还需要以容易看懂的方式展示数据.表格是展示复杂数据的最佳方式. 1.设计数据 ...
- JMeter软件测试工具介绍及基本安装教程
一.工具介绍 (一)简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域. 它可以用于测试 ...
- FCOS: Fully Convolutional One-Stage Object Detection
论文:FCOS: Fully Convolutional One-Stage Object Detection 目录 0.简介 1.网络结构 2.框回归--直接.自由 3.Center-ness ...
- troubleshoot之:分析OutOfMemoryError异常
目录 简介 OutOfMemoryError java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: GC Ov ...
- 使用动态链接为什么还需要静态库lib文件
在Windows上使用动态链接时,不光需要头文件 .dll文件 还需要一个.lib 文件. 不是动态链接吗?为什么还需要静态库.lib文件? 实际上,这个.lib文件并不是静态库,而是 导入库 文件, ...
- 一句话木马变形(截止2020年8月16日通杀D盾、安全狗,微步,webshellKiller)
首先一句话木马: <?php assert($_POST['a']); ?> D盾扫描,5级 分开写: <?php $a = "assert"; $b = $_P ...
- 快速排序&&归并排序
快速排序 快速排序采用的是分治的策略,算法的具体实现过程是 1.确定一个数X(一般是选中间值X=q[l+r>>1]) 2.利用指针i,j,将数组中比X小的数放在一边,比X大的数放在另一边 ...