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生命週期管理和事件的簡單處理。篇中有錯誤的地方歡迎指出,有問題歡迎留言。更多學習內容還要自行參看官方文檔。

相關文章
相關標籤/搜索