Salesforce LWC学习(四) 父子component交互 / component声明周期管理 / 事件处理

咱们在上篇介绍了 @track / @api的区别。在父子 component中,针对api类型的变量,若是声明之后就只容许在parent修改,son component修改便会致使报错。javascript

sonItem.htmlcss

1 <template>
2     <lightning-input value={itemName} onchange={changeItemName} label="item name"></lightning-input>
3 </template>

sonItem.jshtml

1 import { LightningElement, api } from 'lwc';
2 
3 export default class SonItem extends LightningElement {
4     @api itemName = 'test';
5 
6     changeItemName() {
7         this.itemName = 'change item name';
8     }
9 }

parentForSonItem.htmljava

1 <template>
2     <c-son-item item-name="test parent for son item"></c-son-item>
3 </template>

运行结果:默认显示一个输入框,当修改了里面内容,打开console之后会显示报错,缘由为sonItem这个 component嵌在了parentForSonItem这个父component中声明了api注解,因此针对itemName只能容许parentForSonItem去更新,而后传递到子component。若是咱们想子修改后影响api声明的public变量,就只能经过事件处理方式传播给父component,让父去作修改的事情。这个后续会有相似的demo。git

 若是咱们单独把sonItem放在布局中,改变item name即可以正常的触发事件而且没有报错信息。github

一. 父子component交互 web

在项目中咱们针对一个大的component/app设计时,可能有多个component组合在一块儿,好比咱们在salesforce lightning零基础学习(十一) Aura框架下APP构造实现 这篇中,针对一个最终的功能页面可能有N个component进行组合从而实现,这种设计的好处是不少component都是可重用的。针对LWC中针对这种component组合有几个概念。下面是例举的一个官方的demo。根据层次结构,在LWC中有几个概念:api

Owner:Owner表明当前Own这个template的component,咱们能够理解成当前的最高级别的component。当前的component中,todoItem嵌入在了todoWrapper中,todoWrapper嵌在了todoApp中,因此针对当前的component,todoApp是这几个component的owner。针对owner的 component有如下的功能:数组

  • 针对他包含的component能够设置public变量,这里咱们能够设置todoItem的item-name这个public变量(itemName在todoItem中声明为api类型);
  • 能够调用包含的component中的方法;
  • 当包含的component设置了事件状况下,owner的component能够监听到。

Container:Container表明当前这个component包含了其余的component,可是当前的component还在其余的component中。下面的demo中咱们能够看到todoWrapper包含了todoItem,可是todoWrapper还被todoApp包含着,因此针对这个demo中,todoWrapper是container。针对container的component有如下的功能:浏览器

  • 能够读到包含的component中的public的变量,可是是只读的,无法编辑;
  • 能够调用包含的component中的方法;
  • 能够监听到bubble类型的对应的所包含的component事件。(事件能够分为bubble/capture)
1 <!-- todoApp.html -->
2 <template>
3     <c-todo-wrapper>
4         <c-todo-item item-name="Milk"></c-todo-item>
5         <c-todo-item item-name="Bread"></c-todo-item>
6     </c-todowrapper>
7 <template>

Parent and Child:当一个component只包含一个子component时,造成了父子模型,todoApp为父,todoItem为子。按照上面的模型,todoApp为owner,具备owner的功能。

1 <!-- todoApp.html -->
2 <template>
3     <c-todo-item item-name="Milk"></c-todo-item>
4     <c-todo-item item-name="Bread"></c-todo-item>
5 </template>

 咱们在上一篇和这一篇demo中已经介绍过如何去针对Owner设置child component的public变量,此篇中讲parent/owner component如何调用child component的方法。这里先介绍一下html中的selector的概念。

Selector:下面的这张截图你们很常见也很好懂,咱们在声明一个css时常常会写成相似这种,左侧表明一组选择器,右侧表明声明的css规则块。css selector能够理解成CSS rule中左侧的Group of selectors.

Selector能够分红不一样的类型:

  • Simple selectors: 基于element type来匹配一个或者多个元素,好比使用class或者id;
  • Attribute selectors: 基于attribute或者attribute value来匹配一个或者多个元素;
  • Pseudo-classes:匹配一个或者多个处于特定状态的元素,例如鼠标指针悬停在其上的元素、当前被禁用或选中的复选框或是DOM树中其父级的第一个子级元素等等;
  • Pseudo-elements: 匹配一个元素的某个位置的一个或者多个内容。好比每一个段落的第一个字等。

Simple selectors咱们在项目中常常用到的就是标签选择器,class选择器,id选择器。

 1 <style type="text/css">
 2 p {
 3     background:green;
 4 }
 5 
 6 .spanClass {
 7     background:red;
 8 }
 9 
10 #spanId {
11     background:yellow;
12 }
13 </style>
14 
15 <p>标签选择器</p>
16 <span class="spanClass">class选择器</span>
17 <span id="spanId">id选择器</span>

 Attribute Selector咱们在项目中经常使用的就是基于属性精确或者模糊设置CSS样式。

 1 <style type="text/css">
 2 [data-vegetable] {
 3     color: green;
 4   }
 5 
 6   [data-vegetable="liquid"] {
 7     background-color: goldenrod;
 8   }
 9   
10   [data-vegetable~="spicy"] {
11     color: red;
12   }
13 </style>
14 
15 <ul>
16     <li data-quantity="700g" data-vegetable="not spicy like chili">Red pepper</li>
17     <li data-quantity="2kg" data-meat>Chicken</li>
18     <li data-quantity="optional 10ml" data-vegetable="liquid">Olive oil</li>
19 </ul>

 Pseudo-classes咱们常常会在项目中处理一些伪类的处理,好比针对超连接的悬停,active等的处理。针对此种类型,咱们一般在一个selector后面使用' : '关键字。

 1 <style type="text/css">
 2    a {
 3     color: blue;
 4     font-weight: bold;
 5   }
 6 
 7   a:visited {
 8     color: blue;
 9   }
10 </style>
11 
12 <a href="https://trailhead.salesforce.com" target="_blank">trailhead</a>

 Pseudo-elements和Pseudo-classes用法很像,区别是关键字是' :: '.用来获取一个元素的一部份内容。demo中展现的是若是href后面中以https起始,则添加⤴。

 1 <style type="text/css">
 2   [href^=https]::after {
 3     content: '⤴';
 4   }
 5 </style>
 6 
 7 <ul>
 8   <li><a href="https://test.com">HTTPS</a> demo will show ⤴</li>
 9   <li><a href="http://test.com">HTTP</a> demo will not show ⤴</li>
10 </ul>

四种经常使用的css selector介绍完了,下面就引出querySelector以及querySelectorAll的概念。

 querySelector方法是一个标准的DOM API,做用为针对匹配的selector返回第一个元素。salesforce建议咱们尽可能不要使用ID做为selector,由于当template中使用ID的时候,浏览器渲染之后ID将会变成一个global 的惟一的key。若是咱们使用ID做为selector,将可能没法正常匹配上。

querySelector详情能够查看:https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector

咱们针对一个数组的迭代,好比咱们针对复选框使用class做为selector可能须要返回多个元素,这个时候咱们就要使用querySelectorAll。此方法用来返回的是全部的匹配的selector的元素。querySelector咱们能够查看:https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll

有人会提出疑问,绕了这么半天,我可使用document或者window的global方法啊,好比document.getElementById或者document.querySelector?缘由为由于locker缘由,LWC不容许使用window或者document这个global 变量,因此替代方案为使用this.template.querySelector()替代document.querySelector()。

使用querySelector/querySelectorAll有几点注意事项:

  • 针对返回的多个数据,元素的顺序没法保证;
  • 使用querySelector时,若是元素没有在DOM中渲染的没法搜索出来,咱们在后面会有component生命周期管理的内容,当子component没有渲染加载或者当前在构造函数没有渲染出来的时候,使用querySelector是没法查询出来的;
  • querySelector不要使用ID做为selector。

下面就经过一个例子来了解针对parent/owner如何去访问son component的方法。

 currentTime.html: 用来将传进来Date变量format成指定格式的日期,默认获取的是当前的时间;

 1 <template>
 2     当前时间:
 3     <lightning-formatted-date-time
 4         value={currentTime}
 5         year="numeric"
 6         month="numeric"
 7         day="numeric"
 8         hour="2-digit"
 9         minute="2-digit"
10         second="2-digit"
11         time-zone-name="short"
12     >
13     </lightning-formatted-date-time>
14 </template>

currentTime.js:声明一个变量用于前台展现,声明的方法必需要使用@api标签才能够供owner/parent component进行方法调用。

1 import { LightningElement,track,api } from 'lwc';
2 
3 export default class CurrentTime extends LightningElement {
4     @track currentTime = new Date();
5 
6     @api refreshTime() {
7         this.currentTime = new Date();
8     }
9 }

showCurrentTime.html:引入current-time,而且放一个按钮

1 <template>
2     <c-current-time></c-current-time>
3     <lightning-button
4         label="Refresh Time"
5         onclick={handleRefresh}
6     ></lightning-button>
7 </template>

showCurrentTime.js:使用querySelector获取到current-time这个元素,而后调用其方法。这里的template变量用于在javascript中访问组件中渲染的元素。

1 import { LightningElement } from 'lwc';
2 
3 export default class ShowCurrentTime extends LightningElement {
4     handleRefresh() {
5         this.template.querySelector('c-current-time').refreshTime();
6     }
7 }

上述的demo中实现的就是最基本的使用querySelector实现获取子component而且调用子component方法的例子。其余更多细节欢迎自行查看文档。

二. LWC针对component的生命周期管理

LWC针对component加载以及移除有一套生命周期管理机制,针对不一样生命周期的节点咱们能够作不一样的事情,也有不一样的限制。

针对component加载渲染的生命周期管理图以下所示:

1. 针对有父子关系嵌套的component,先执行parent component的constructor()方法,针对constructor方法,有几点须要注意:

  • 第一个语句必须是super()而且不带参数,声明之后即可以使用了this关键字;
  • 在constructor方法里面不要使用return语句去return什么返回值,除非是针对某些逻辑下直接返回不执行下面可使用return 或者return this,其余不容许;
  • 和上面的querySelector相同,不容许使用document以及window;
  • 不要检查元素的attribute以及他们的子元素,由于这个阶段他们还不存在;
  • 不要检查元素中的使用@api声明的public 变量,由于他们在component建立之后才能够引用;

2. 查看public变量是否有等待被更新的,若是有,更新public 变量的值;

3. Parent Component插入进DOM中,当插入完会触发parent component的connectedCallback()方法,这个时候由于parent component已经插入完毕,因此此方法中能够调用parent component中对应的element等信息,咱们可使用this.template去访问相关的element。经过方法描述能够看出来,此方法可能不止调用一次,当DOM中有新插入的component便会触发此方法。好比咱们动态搜索数据,list数据可能会变化或者reorder,会调用此方法屡次;

4. 当connectedCallbacl()方法执行完之后,parent component渲染完成;

5. 此时子component会自动触发构造函数constructor()方法;

6.查看子component中的变量是否有被等待更新的,若是有,更新public 变量的值;

7. 子component插入进DOM中,插入完成后会调用connectedCallback()方法;

8.子component渲染完成;

9. 当父子component都渲染完成之后,父component调用renderedCallback()方法。

 针对component移除的生命周期管理图以下所示:

当parent component从DOM移除时,会触发parent component的disconnectedCallback方法;

当son component从DOM移除时,会触发son component的disconnectedCallback方法。

 当咱们了解了LWC针对component的生命周期管理,咱们即可以更好的针对每一个作不一样的处理,固然,咱们不少时候会将生命周期管理和事件管理一块儿使用。接下来的内容为LWC的事件管理。

三. LWC 事件管理

对Aura事件不了解或者对web标准的事件管理不了解的能够先看一下salesforce lightning零基础学习(五) 事件阶段(component events phase),LWC和他们有不少类似之处。最开始的demo中咱们演示了针对@api的public变量,子component不能修改其变量值,若是子真的有必要修改如何作呢?那就建立一个事件而且去通知其父组件。父组件对这个事件进行监听,而后父组件去更改这个值而且从新渲染会子组件从而实现了子组件修改变量值的诉求。

在LWC中,Event基于DOM Event,感兴趣的小伙伴能够读一下https://dom.spec.whatwg.org/#events,里面包括了不少的object以及相对应的API方法。当建立Event的时候,官方推荐使用customEvent,由于其拥有更好的兼容性以及更多的功能,同时他封装了detail变量,咱们在事件处理中可使用此变量去传递任意类型的数据。Event事件管理能够进行如下的几步走。

1. 建立事件

 咱们使用CustomEvent()去新建一个自定义的事件,此构造函数由两个参数,第一个参数传递的是事件名称,第二个参数是CustomEventInit,是一个可选的设置项,此参数能够设置好几个字段。好比detail字段用来在事件中传递处理中能够做为参数做为传递,bubbles来决定当前的事件处理是bubble仍是capture,cancelable来决定当前事件触发之后是否能够取消,composed来肯定是否会触发shadow DOM 根节点之外的事件监听器。固然咱们在使用中可能经常使用的就是设置detail用来传递参数以及bubble来设置传播方式。

2. 调度事件

当咱们自定义完事件之后,咱们须要调度此事件才能够正常的进行事件监听。使用this.dispatchEvent(eventName)便可调度,调度之后,会根据custom event设置的传播方式对父子component进行调度。调度顺序不懂的能够查看上面的事件阶段的博客。

3. 事件监听处理

当事件建立而且在子component调度完成后,父component便须要进行事件监听处理。LWC提供了两种方式进行事件监听。一种是在父component引入子component时直接在其template上添加监听器的标签,另一种是经过js方式设置监听器,很像咱们的浏览器标准事件监听处理方式。

component标签方式:好比咱们建立了一个自定义的事件名称为notification在child的component,咱们在parent component引入而且想要设置此事件的监听处理方法为handleNotification方法,咱们只须要使用on + 自定义事件名称便可实现事件监听处理,这也就是上一篇中介绍为何不能以on做为变量开头的缘由。

1 <template>
2     <c-child onnotification={handleNotification}></c-child>
3 </template>

js方式:咱们在父componet的初始化的方法中,使用addEventListener方法去实现事件监听,第一个参数是自定义的事件名称,第二个是要事件处理的方法。

1 import { LightningElement } from 'lwc';
2 export default class Parent extends LightningElement {
3   constructor() {
4     super();
5     this.template.addEventListener('notification', this.handleNotification.bind(this));
6   }
7 }

经过上面的三步走,咱们便完成了针对事件处理的基本了解。可是咱们疑问仍是特别多,好比针对事件处理的方法,我能作什么?针对Event是否有什么封装好的方法可让我更好的去运用? 你们在aura学习事件处理的时候应该颇有了解,salesforce lightning零基础学习(九) Aura Js 浅谈二: Event篇 aura提供了咱们针对事件处理的一系列的方法。LWC的custom event大部分使用的是DOM原生的,因此DOM 原生Event也封装好了不少的变量以及方法,想要详细了解的小伙伴能够查看:https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent,下面只例举部分经常使用变量。

detail:detail变量能够获取到事件声明的时候传递的参数信息,传递的参数类型能够为任何的类型。下面的demo中传递了一个简单的object包含了isSelected,在事件处理中咱们即可以使用event.detail获取到当前传递的参数。

1 const selectEvent = new CustomEvent('select', {
2     detail: {isSelected:true}3 });
4 this.dispatchEvent(selectEvent);
5 
6 const isSelected = selectEvent.detail.isSelected;

bubbles:返回的是一个布尔类型的变量,判断当前的事件声明的是bubble仍是capture。若是是bubble则返回true,capture则返回false。

currentTarget:咱们事件调度之后,会根据bubble/capture顺序去执行相关的handler,currentTarget指定了当前正在处理该事件的元素。

target:获取咱们当前正在执行的事件最开始调度的元素。他和currentTarget是有区别的,currentTarget永远指定的是当前正在处理该事件的元素。target指定的是最开始调度的元素。

纸上学来终觉浅,绝知此事要躬行。下面以一个官方提供的简单的demo去更好的了解事件处理。

ContactController.cls:此方法封装了一个简单的查询语句而后返回数据列表

1 public with sharing class ContactController {
2 
3     @AuraEnabled(cacheable=true)
4     public static List<Contact> getContactList() {
5         return [SELECT Id, Name, Title, Phone, Email FROM Contact LIMIT 10];
6     }
7 }

contactListItem.html:做为item,显示contact name,点击之后调用handleClick方法。

1 <template>
2     <a href="#" onclick={handleClick}>
3         {contact.Name}
4     </a>
5 </template>

contactListItem.js:在handleClick方法中声明了自定义事件而且对事件进行了调度。

 1 import { LightningElement, api } from 'lwc';
 2 
 3 export default class ContactListItem extends LightningElement {
 4     @api contact;
 5 
 6     handleClick(event) {
 7         // 1. Prevent default behavior of anchor tag click which is to navigate to the href url
 8         event.preventDefault();
 9         // 2. Read about event best practices at http://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.events_best_practices
10         const selectEvent = new CustomEvent('select', {
11             detail: this.contact.Id
12         });
13         // 3. Fire the custom event
14         this.dispatchEvent(selectEvent);
15     }
16 }

eventWithData.html:迭代显示数据,而且针对引入的子component设置了监听处理的方法。当子component点击触发事件,执行handleSelect方法获取选中的contact而后渲染出来隐藏的详情区域。

 1 <template>
 2     <lightning-card title="EventWithData" icon-name="standard:logging">
 3         <template if:true={contacts.data}>
 4             <lightning-layout class="slds-m-around_medium">
 5                 <lightning-layout-item>
 6                     <template for:each={contacts.data} for:item="contact">
 7                         <c-contact-list-item
 8                             key={contact.Id}
 9                             contact={contact}
10                             onselect={handleSelect}
11                         ></c-contact-list-item>
12                     </template>
13                 </lightning-layout-item>
14                 <lightning-layout-item class="slds-m-left_medium">
15                     <template if:true={selectedContact}>
16                         
17                         <p>{selectedContact.Name}</p>
18                         <p>{selectedContact.Title}</p>
19                         <p>
20                             <lightning-formatted-phone
21                                 value={selectedContact.Phone}
22                             ></lightning-formatted-phone>
23                         </p>
24                         <p>
25                             <lightning-formatted-email
26                                 value={selectedContact.Email}
27                             ></lightning-formatted-email>
28                         </p>
29                     </template>
30                 </lightning-layout-item>
31             </lightning-layout>
32         </template>
33 
34     </lightning-card>
35 </template>

eventWithData.js:此方法封装了一个变量,使用wire Service调用后台的controller去获取数据展现,当选中子之后调用handleSelect方法执行事件监听处理。wire service后期会有单独篇去讲。

 1 import { LightningElement, wire, track } from 'lwc';
 2 import getContactList from '@salesforce/apex/ContactController.getContactList';
 3 
 4 export default class EventWithData extends LightningElement {
 5     @track selectedContact;
 6 
 7     @wire(getContactList) contacts;
 8 
 9     handleSelect(event) {
10         const contactId = event.detail;
11         this.selectedContact = this.contacts.data.find(
12             contact => contact.Id === contactId
13         );
14     }
15 }

显示效果:

默认显示列表数据,当点击一个contact name之后,会建立而且调度事件,eventWithData监听到事件之后,执行监听处理方法,设置selectedContact。此变量使用track标签声明,因此改变之后会从新渲染eventWithData component实现细节的展现。

 官方在 github.com/trailheadapps/lwc-recipes提供了不少的学习的demo,感兴趣的能够查看。更多生命周期的demo能够查看git demo中的pubsubContactList。

总结:篇中只是介绍了父子经过querySelector获取相关的element实现交互以及component生命周期管理和事件的简单处理。篇中有错误的地方欢迎指出,有问题欢迎留言。更多学习内容还要自行参看官方文档。

相关文章
相关标签/搜索