1. 程式人生 > >Salesforce LWC學習(三) import & export / api & track

Salesforce LWC學習(三) import & export / api & track

 我們使用vs code建立lwc 時,檔案會預設生成包含 template作為頭的html檔案,包含了 import LightningElement的 js檔案以及對應的.js-meta.xml檔案。前一個LWC學習的文章中已經說過當瀏覽器渲染的時候,遇見<template>會將其渲染成<namespace-component-name>,比如 helloWorld.html引入在頁面中瀏覽器進行渲染時,此檔案的template會被渲染成<c-hello-world>。這裡不知道大家是否有過疑問, import / export 是幹什麼用的,針對變數宣告和html以及js之間的資料繫結如何實現的,這些都將會在下面有簡單的描述。

 一. 變數宣告, api以及track

我們想要實現一個簡單的功能,預設顯示 'hello World!',下面存在一個輸入框,當輸入內容以後,'World'便會變成其他的值並且以大寫顯示,同時輸入框中不允許輸入xx,我們在下方有一個輸入歷史記錄區域會記錄當前輸入的內容。在Aura框架下大家都知道,只需要設定一個attribute,事件觸發以後component.set()便可以實現實時修改了。在lwc中,html使用{}將屬性包圍起來,{property}後臺宣告property,想要計算這個property的值,我們只需要呼叫 get property即可獲取到property的值。需要注意的是當一個component重新渲染的時候,裡面所有的表示式都會重新渲染。OK下面我們進行實現。

testVariable.html

 1 <template>
 2     Hello,{wiredGreeting}!
 3     <br/>
 4     <lightning-input label="Name" value={greeting} onchange={handleChange} onblur={logHistory}></lightning-input>
 5     <template if:true={showDetail}>
 6         <div class="slds-m-vertical_medium">
 7             不允許輸入xx
 8         </div>
 9     </template>
10 
11     <template if:true={showHistory}>
12         <lightning-card title="input history list">
13             <ul class="slds-m-around_medium">
14                 <template for:each={historyList} for:item="h">
15                     <li key={h.Id}>
16                         {h.Name}
17                     </li>
18                 </template>
19             </ul>
20         </lightning-card>
21     </template>
22 </template>

testVariable.js

 1 import { LightningElement,track } from 'lwc';
 2 
 3 export default class MyTestVariable extends LightningElement {
 4     @track greeting = 'World';
 5     @track showDetail = false;
 6     @track historyList = new Array();
 7 
 8     handleChange(event) {
 9         this.greeting = event.target.value;
10         if(this.greeting.toLocaleLowerCase() === 'xx') {
11             this.showDetail = true;
12         } else {
13             this.showDetail = false;
14         }
15     }
16 
17     logHistory(event) {
18         const previousHistoryValue = this.historyList.length > 0 ? this.historyList[this.historyList.length - 1].Name : '';
19         const previousHistoryId = this.historyList.length > 0 ? this.historyList[this.historyList.length - 1].Id : 0;
20         if((previousHistoryValue !== '' && event.target.value !== '' && previousHistoryValue !== event.target.value) || (previousHistoryValue === '' && event.target.value !== '')) {
21             const tmpId = previousHistoryId + 1;
22             const tmpName = event.target.value;
23             const history = {Id:tmpId,Name:tmpName};
24             //this.historyList = [...this.historyList,history];
25             this.historyList.push(history);
26         }
27     }
28 
29     get wiredGreeting() {
30         return this.greeting.toUpperCase();
31     }
32 
33     get showHistory() {
34         return this.historyList.length > 0 ? true : false;
35     }
36 }

我們將metadata檔案設定為可以放在lightning_app/ lightning_home以及lightning_record以後,部署以後便可以放在home的page layout中展示了。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="myTestVariable">
 3     <apiVersion>46.0</apiVersion>
 4     <isExposed>true</isExposed>
 5     <targets>
 6         <target>lightning__AppPage</target>
 7         <target>lightning__HomePage</target>
 8         <target>lightning__RecordPage</target>
 9     </targets>
10 </LightningComponentBundle>

結果如下圖所示:

這個demo中涉及到了幾個關鍵的學習的點:

 if:true: 做過aura的小夥伴都知道aura:if標籤,if:true和aura:if的功能類似,或者小夥伴做過angular也會知道ng-if,功能為true的情況下展示包含的內容,false的情況下則不展示;

for:each:在LWC中,針對list的遍歷有兩種迴圈方式,一種是使用for:each方式,一種是使用iterator方式。demo中使用的是for:each接收list,for:item作為他的每個迴圈中的item變數用來訪問當前的item,如果我們想訪問當前的index,我們可以用for:index來接收當前的index,這裡需要注意一點的是,針對每一個item,必須要有key這個屬性宣告,當list的值變了以後,系統渲染list的時候看當前的key對應的資料是否有變化來進行渲染,沒有變化就不會重新渲染。key必須是唯一的,而且不能使用index的值作為key,並且針對key的型別只能是String或者Number;另外一種iterator:iteratorName={array}方式來宣告list,這種宣告的方式為它封裝了first,last兩個屬性,可以判斷出來當前的item是否為這個列表的first和last。詳情用法參看:https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.create_lists

上面的js程式碼中大家可以看到我們import了track,並且針對前端的展示的變數我們都使用了@track這個註解進行了宣告。如果沒有宣告會怎樣呢?實際上沒有了track的宣告以後,當我們在前臺改變了World的值以後,上方的區域不會實時的變化成輸入的值,下面的input History List也不會在失去焦點的情況下增加了item。可以說因為有了track的存在,使這些變數的值改變以後,前臺的component進行了reRender(重新渲染)操作。針對LWC的變數改變是否會重新渲染前臺component,我們可以將變數分成兩種型別:Private 以及 Reactive。

 不管是Private 還是 Reactive型別,lwc針對變數宣告都有相同的規範。即:宣告變數必須遵從駝峰標準,使用駝峰標準好處是如果有父子引用等,可以將js中的變數按照指定規則識別成html中的attribute,比如變數名稱為itemName會在html中識別成item-name,當然這個在後面的demo中會用到。除了這個宣告的規範以外,LWC還有一些變數宣告的要求,比如變數宣告中不能以 on / aria / data作為起始字元;變數名不能為slot / part / is 等限制。感興趣的可以自行檢視文件。

接下來回到 Private 以及 Reactive,針對 Private,舉一個不太恰當的比喻,在apex中可以宣告變數為 private / public / global 型別,其中 private只能當前這個類當中引用,並且 apex page中無法引用,這裡的 Private也有這層意思,區別是 Private型別變數可以在component中使用,但是他的後期的任何變化不會對component進行重新渲染,而且父頁面也無法通過注入方式修改此型別變數;我們更多的要介紹的是 Reactive型別變數,此種變數的特點為當此變數改變以後,component便會重新渲染,在這個component中的所有的表示式都會重新計算。此種變數有兩個型別: public / tracked(private). public 使用@api註解進行修飾。tracked使用@track註解進行修飾。這裡需要注意的是,如果使用了@track 或者@api,必須要import track或者 import api在頭部才能使用。

 Tracked: Tracked型別也可以稱為Private型別的Reactive,此種宣告型別可以實現當變數改變以後,component進行reRender操作,此種類型的變數當其他的component引用以後,不可以通過attribute方式進行注入值,;此種類型頭部宣告需要引用:

import { LightningElement, track} from 'lwc';

Public:Public型別和Track型別區別為當其他的component進行引用時,可以通過attribute進行注入值。此種類型宣告時頭部需要引用:

import { LightningElement, api} from 'lwc';

看到上面這幾種型別的介紹,我們可能會有一個疑問,宣告成reactive是不是比private更好更方便?其實在變數宣告時我們一定要千萬的注意考慮好變數的作用域,變數型別。reactive型別當改變以後整個component都會reRender,所有template中包含的表示式都會被重新計算,使用不當可能會造成不準確或者不必要的效能影響,所以宣告以前要考慮清楚變數用途。下面的Demo用來深化一下Tracked以及Public的用法。我們想要知道LWC封裝了哪些component,我們可以訪問:https://developer.salesforce.com/docs/component-library/overview/components進行檢視。

 chartBar.html: 展示了一個區域用來顯示percentage,旁邊使用lightning-progress-bar展示目前percentage對應的比例。

 1 <template>
 2     <div class="container">
 3         <lightning-layout vertical-align="center">
 4             <lightning-layout-item>
 5                 {percentage}%
 6             </lightning-layout-item>
 7             <lightning-layout-item flexibility="grow">
 8                 <lightning-progress-bar value={percentage} size="large"></lightning-progress-bar>
 9             </lightning-layout-item>
10         </lightning-layout>
11     </div>
12 </template>

chartBar.js: 聲明瞭兩個變數。一個是track註解的private型別的reactive變數,一個是api註解的public型別的reactive變數。因為process-bar 最小值為0,最大值為100, 所以我們針對這個percentage進行限制,大於等於100設定為100, 小於等於0或者不是數字情況下設定為0,其他情況下正常顯示。我們對percentage進行track,同時將formattedPercentage暴露給其他的APP用來可以賦值注入,針對變數,如果有get則必須要有set,兩個儘量做到一起出現,而且官方建議在get方法去宣告@api註解。set方法去做資料的初始化處理

 1 import { LightningElement, api, track } from 'lwc';
 2 
 3 export default class ChartBar extends LightningElement {
 4 
 5     @track percentage;
 6 
 7     @api get formatedPercentage() {
 8         return this.percentage;
 9     }
10 
11     set formatedPercentage(value) {
12         if(isNaN(value) || value === '') {
13             this.percentage = 0;
14         } else {
15             const integerValue = parseInt(value);
16             if(integerValue < 0) {
17                 this.percentage = 0;
18             } else if(integerValue > 100) {
19                 this.percentage = 100;
20             } else {
21                 this.percentage = integerValue;
22             }
23         }
24     }
25 }

apiProperty.html: 我們聲明瞭一個輸入框,當然這裡面也可以不用指定上限下限以及型別,因為我們在chartBar中已經在set方法中進行了邏輯處理

1 <template>
2     <lightning-input label="Percentage" type="number" min="0" max="100"
3         value={percentage} onchange={handlePercentageChange}></lightning-input>
4     <c-chart-bar formated-percentage={percentage}></c-chart-bar>
5 </template>

apiProperty.js: 我們對變數進行了track標籤處理以及新增方法去實時獲取輸入的值給子component.

1 import { LightningElement, track } from 'lwc';
2 
3 export default class ApiProperty extends LightningElement {
4     @track percentage = 50;
5 
6     handlePercentageChange(event) {
7         this.percentage = event.target.value;
8     }
9 }

 顯示效果如下:我們輸入值以後,因為apiProperty中percentage是track的,所以改變以後會reRender 這個component中所有的表示式,c-chart-bar的formated-percentage便會走set方法從而chartBar component的percentage變化。chartBar的percentage變化便會重新渲染chartBar的所有的表示式,所以下方的進度條也會跟著變化。

我們常用的註解除了@track 以及 @api以外,還會經常使用@wire,區別為前兩個是針對前臺的,後面的這個是wire service,可以用來獲取資料,建立資料,呼叫apex等等,這個以後會有講解。

既然reActive型別變數這麼神奇,是否有什麼相關的limitation呢?答案是有的。針對reActive型別變數只支援三種類型:

  • Primitive values
  • Plain objects created with {…}
  • Arrays created with []

只有這三種類型允許reActive,其他的型別即使聲明瞭,也沒法做到改變以後重新渲染component,並且會在console log裡面提示你當前型別不支援track。

二. Import / Export

上面的例子中,我們預設需要import LightningElement,我們自動生成的類會有export變數,那import / export有什麼用呢?

 export 詳細介紹:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

 import 詳細介紹:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

讀完上面的兩個宣告的用法以後,我們可以簡單的理解為如果我們有方法或者模組可以作為公用的方法去進行javascript share,我們便可以使用export標籤去宣告,宣告以後其他的component js或者其他需要引入的便可以 import並且直接使用其封裝好的功能。import中會用到的更多,比如我們程式碼中用到了track / api / wire,我們便需要對他們進行import,我們引用了第三方的庫存在了static resource中,我們也要進行import,我們使用了其他的公用的js,我們同樣需要將其import進來。下面進行一個簡單的例子去理解一下import/ export

 commonUtils.js:用來宣告一個公用方法,傳入date,返回當前date的最大的一天

 1 const getMaxDayForThisMonth = (currentDate) => {
 2     if(currentDate.getMonth() === 12) {
 3         return 31;
 4     } else {
 5         const tmpDate = new Date(currentDate.getFullYear(),currentDate.getMonth() + 1,1);
 6         return (new Date(tmpDate - 24*60*60*1000)).getDate();
 7     }
 8 };
 9 
10 export {getMaxDayForThisMonth};

useCommonUtils.html:顯示最大天數

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

useCommonUtils.js: 引入commonUtils暴露的方法,並且引用此方法。

1 import { LightningElement } from 'lwc';
2 import {getMaxDayForThisMonth} from 'c/commonUtils';
3 
4 export default class UseCommonUtils extends LightningElement {
5     maxDate = getMaxDayForThisMonth(new Date()); 
6 }

結果展示:當前的日期是8月,最大的一天是31,所以結果輸出為31.

 

總結:篇中只簡單的介紹了關於track / api 以及 import / export的基礎知識,深入學習還請自己檢視文件。篇中有錯誤地方歡迎指出,有不懂的歡迎留