我们使用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的基础知识,深入学习还请自己查看文档。篇中有错误地方欢迎指出,有不懂的欢迎留言。