微信小程序开发教程(八)视图层——.wxml详解
框架的视图层由WXMKL(WeiXin Markup language)与WXSS(WeiXin Style Sheet)编写,由组件进行展示。
对于微信小程序而言,视图层就是所有.wxml文件与.wxss文件的集合。
微信小程序在逻辑层将数据进行处理后发送给视图层展现出来,同时接受视图层的事件反馈。
♦ .wxml文件用于描述页面的结构。
♦ .wxss文件用于描述页面的样式。
视图层以给定的样式展现数据并将时间反馈给逻辑层,而数据展现是以组件来进行的。组件(Component)是视图的基本单元,是构建.wxml文件必不可少的。
对于小程序的WXML编码开发,我们基本上可以认为就是使用组件、结合时间系统,构建页面结构的过程。.wxml文件中所绑定的数据,均来自于对应页的.js文件中Page方法的data对象。
WXML
WXML是框架设计的一套类似HTML的标签语言,结合基础组件、事件系统,可以构建出页面的结构,即.wxml文件。
.wxml文件第一行建议写<!--页面名.wxml-->。如:
<!--logs.wxml--> <view class="container log-list"> <block wx:for="{{logs}}" wx:for-item="log"> <text class="log-item">{{index+1}}.{{log}}</text> </block> </view>
通过<view>组件控制页面内容展现,通过<block>组件与<text>组件实现页面数据绑定。
WXML具有数据绑定、列表渲染、条件渲染、模板及事件绑定的能力。
数据绑定
.wxml文件中的动态数据均来自对应页面的.js文件中Page的data对象。
(1)简单绑定
数据绑定使用Mustache语法(即“双大括号”语法)将变量包起来。
a.作用于内容
<!--wxml--> <view>{{message}}</view>
//page.js Page({ date:{ message:'Hello MINA!' } })
b.作用于组件属性
<!--wxml--> <view id="item-{{id}}"></view>
//page.js Page({ data:{ id:0 } })
c.控制属性
<!--wxml--> <view wx:if="{{condition}}"></view>
//page.js Page({ data:{ condition:true } })
(2)运算
可以在{{}}内进行简单的运算,支持以下几种方式:①三元运算,②算数运算,③逻辑判断,④字符串运算,⑤数据路径运算
<!--wxml--> <!--①--> <view hidden="{{flag ? true : false}}">Hidden</view> <!--②--> <view>{{a + b}} + {{c}} + d </view> //page.js Page({ data:{ a:1, b:2, c:3 } }) <!--③--> <view wx:if="{{length > 5}}"></view> <!--④--> <view>{{"hello" + name}}</view> //page.js Page({ data:{ name:'MINA' } }) <!--⑤--> <view>{{object.key}}{{array[0]}}</view> //page.js Page({ data:{ object:{ key:'Hello' }, array:['MINA'] } })
(3)组合
可以再Mustache内直接进行组合,构成新的对象或者数组。
数组:
<!--wxml--> <view wx:for="{{zero,1,2,3,4}}">{{item}}</view>
//page.js Page({ data:{ zero:0 } })
最终组合成数组[0,1,2,3,4]。
对象:
<!--wxml--> <template is="objectCombine" data="{{for:a,bar:b}}"></template>
//page.js Page({ data:{ a:1, b:2 } })
最终组合成的对象是{{for:1,bar:2}}。
列表渲染
列表语句可用于.wxml中进行列表渲染,将列表中的各项数据进行重复渲染。
(1)wx:for
在组件上使用wx:for控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组当前项的下标变量名默认为index,数组当前项的变量名默认为item。
<!--wxml--> <view wx:for="{{items}}">
{{index}}:{{item.message}}
</view>
//page.js Page({ data:{ items:[{
message:'foo'
},{
message:'bar'
}] } })
♦ 使用wx:for-item可以指定数组当前元素的变量名。
♦ 使用wx:for-index可以指定数组当前下标的变量名。
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName"> {{idx}}:{{itemName.message}} </view>
wx:for也可以嵌套,例如九九乘法表:
<view wx:for="{{[1,2,3,4,5,6,7,8,9]}}" wx:for-item="i"> <view wx:for="{{[1,2,3,4,5,6,7,8,9]}}" wx:for-item="j"> <view wx:if="{{i<=j}}"> {{i}} * {{j}} = {{i*j}} </view> </view> </view>
条件渲染
条件语句可用于.wxml中进行条件渲染,不同的条件进行不同的渲染。
我们用wx:if=“{{condition}}”来判断是否需要渲染该代码块。也可以用wx:elif和wx:else来添加一个else块。
<!--wxml--> <view wx:if="{{view == 'WEBVIEW'}}">WEBVIEW</view> <view wx:elif="{{view == 'APP'}}">APP</view> <view wx:else="{{view == 'MINA'}}">MINA</view>
//page.js Page({ data:{ view:'MINA' } })
因为wx:if是一个控制属性,需要将它添加到一个组件标签上。如果想一次性判断多个组件标签,其实可以使用一个<block/>标签将多个组件包装起来,并在其上使用wx:if控制属性。
<block wx:if="{{true}}"> <view> view1 </view> <view> view2 </view> </block>
注:<block/>并不是一个组件,他仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
wx:if 和 hidden的区别:
因为wx:if之中的模块也可能包含数据绑定,所以当wx:if的条件值切换时,框架由一个局部渲染的过程,从而确保条件块在切换时销毁或重新渲染。
同时wx:if也是惰性的,如果在初始渲染条件为false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。相比之下,hidden就简单的多,组件始终会被渲染,只需简单地控制显示与隐藏。
一般来说,wx:if有更高的切换消耗,而hidden有更高的初始渲染消耗。因此,如果需要频繁切换的情况下,用hidden更好;如果运行时条件不大可能改变,则wx:if较好。
模板
WXML支持模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
使用name属性,作为模板的名字。使用is属性,声明需要使用的模板,然后将模板所需要的data传入。
<!--wxml--> <template name="staffName"> <view> FirstName:{{firstName}},LastName:{{lastName}} </view> </template> <template is="staffName" data="{{...staffA}}"></template> <template is="staffName" data="{{...staffB}}"></template> <template is="staffName" data="{{...staffC}}"></template>
//page.js Page({ date:{ staffA:{firstName:'Hulk',lastName:'Hu'}, staffB:{firstName:'Shang',lastName:'You'}, staffC:{firstName:'Gideon',lastName:'Lin'}, } })
字例代码中,“...”为扩展运算符,用来展开一个对象,如staffA对象。
is属性可以使用Mustache,来动态决定具体需要渲染哪个模板:
<template name="odd"> <view> odd </view> </template> <template name="even"> <view> even </view> </template> <block wx:for="{{[1,2,3,4,5]}}"> <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"> </block>
模板拥有自己的作用域,只能使用data传入的数据。
事件绑定
事件的定义如下:
♦ 事件是视图层到逻辑层的通信方式。
♦ 事件可以将用户的行为反馈到逻辑层进行处理。
♦ 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
♦ 事件对象可以携带额外信息,如id、dataset、touches。
我们小程序与用户交互,多数情况下是通过事件来进行的。首先,在组件中绑定一个事件处理函数。在下面代码中,我们使用bindtap,当用户点击该组件view时会在该页面对应Page中找到相应的事件处理函数add。
<!--wxml--> <!--指定view组件的唯一标识tapTest;自定义属性hi,其值为MINA;绑定事件add--> <view id="tapTest" data-hi="MINA" bindtap="add">{{count}}</view>
注:应将bindtap理解为:bind+tap,即绑定冒泡事件tap(手指触摸后离开)。
其次,在相应的Page定义中写上相应的事件处理函数。
//page.js Page({ data:{ count:1 }, add:function(e){ this.setData({ count:this.data.count + 1 }) } })
事件详解:
微信小程序里的事件分为冒泡事件和非冒泡事件:
♦ 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
♦ 非冒泡事件:当一个组件上的事件被触发后,改时间不会向父节点传递。
WXML中的冒泡事件仅有6个
除上表之外的其他组件自定义时间都是非冒泡事件。
事件绑定的写法同组件属性,以key,value的形式。
♦ key以bind或touch开头,然后跟上事件的类型,如bindtap、catchtouchstart。
♦ value是一个字符串,需要在对应的Page中定义同名的函数。不然当触发事件的时候会报错。
注:bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
看一个例子:
<view id="outter" bindtap="handleTap1"> outer view <view id="middle" catchtap="handleTap2"> middle view <view id="inner" catchtap="handleTap3"> inner view </view> </view> </view>
点击id为inner的组件会先后触发handleTap3和handleTap2,不会触发handleTap1。
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象,事件对象具有属性如下:
BaseEvent 基础事件对象属性列表:
CustomEvent 自定义事件对象属性列表(继承 BaseEvent):
TouchEvent 触摸事件对象属性列表(继承 BaseEvent):
特殊事件: <canvas/>
中的触摸事件不可冒泡,所以没有 currentTarget。
♦ type:通用事件类型。
♦ timeStamp:页面打开到触发事件所经过的毫秒数
♦ target:触发事件的源组件。是一个对象,具有以下三个属性:
♦ currentTarget:事件绑定的当前组件。与target类似,是一个对象,同样具有上表三个属性。
♦ touches:触摸点数组,每个触摸点包含以下属性。
♦ changedTouches:数据格式同touches。表示有变化的触摸点。
♦ detail:指特殊事件所携带的数据。如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息。
引用
WXML提供两种文件引入方式:import和include。
(1)import
import可以在该文件中使用目标文件定义的template,例如:在item.wxml中定义了一个叫item的template:
<!--item.wxml--> <template name="item"> <text>{{text}}</text> </template>
在index.wxml中引用了item.wxml,就可以使用item模板:
<import src="item.wxml"> <template is="item" data="{{text:'forbar'}}"/>
import有作用域,只会引用目标文件中定义的template,而不会引用目标文件嵌套import的template。
(2)include
include可将目标文件除模板代码(<template/>)块的所有代码引入,相当于拷贝到include位置,如:
<!--index.wxml--> <include src="header.wxml"/> <view> body</view> <include src="footer.wxml"/> <!--header.wxml--> <view> header </view> <!--footer.wxml--> <view> footer </view>