3.2 视图层
视图层主要负责视图的显示,WXML页面、WXSS样式表和组件都是视图层的内容。
3.2.1 WXML
WXML的全称是WeiXin Markup Language(微信标记语言),类似于HTML,也是一种使用<标签>和</标签>构建页面结构的语言。
WXML具有数据绑定、列表渲染、条件渲染、模板、事件和引用的功能。
1 数据绑定
1)简单绑定
在WXML页面中可以使用{{变量名}}的形式表示动态数据。例如:
<view>{{msg}}<view>
此时WXML页面上不会显示msg这个词,而是会显示这个变量对应的值。动态数据的值都来自JS文件的data属性中的同名变量。例如:
上述代码会把“你好!”渲染到WXML页面上{{msg}}出现的地方。
2)组件属性绑定
组件的属性也可以使用动态数据,例如组件的id、class等属性值。
WXML页面的相关代码如下:
<view id='{{id}}'>测试</view>
JS文件的相关代码如下:
3)控制属性绑定
控制属性也可以使用动态数据,但必须在引号内。
WXML页面的相关代码如下:
<view wx:if='{{condition}}'>测试</view>
JS文件的相关代码如下:
上述代码表示测试组件不被显示出来。
4)关键字绑定
如果直接在引号内写布尔值也必须用双花括号括起来,例如:
1. <view wx:if='{{false}}'>测试1</view> 2. <view wx:if='{{true}}'>测试2</view>
注意:不可以去掉双花括号直接写成wx:if='false',此时false会被认为是一个字符串,转换为布尔值后表示true。
5)运算绑定
在双花括号内部还可以进行简单的运算,其支持的运算有三元运算、算术运算、逻辑判断、字符串运算和数据路径运算。
三元运算的示例代码如下:
算术运算的示例代码如下:
双花括号内的a+b会进行算术运算,但是注意括号外面的+会原封不动地显示出来,不起任何算术运算作用。
逻辑判断的示例代码如下:
此时,判断x>5返回true,因此wx:if条件成立。
字符串运算的示例代码如下:
此时,双花括号中的“+”号起到了连接前后字符串的作用。
数据路径运算的示例代码如下:
6)组合绑定
用户还可以在双花括号内直接进行变量和值的组合,构成新的对象或者数组。
数组组合的示例代码如下:
上述代码中的x会被替换成数字3,从而形成数组[1,2,3,4]。
对象组合的示例代码如下:
上述代码最终会组合出对象{username: 'admin', password: '123456'}。其中,WXML代码部分使用了<template>标签,该标签可以用于定义模板。
用户也可以使用“…”符号将对象内容展开显示,示例代码如下:
上述代码最终会组合出对象{ stuID:'123', stuName:'张三', gender: 2}。
如果对象中元素的key和value名称相同,可以省略表达。示例代码如下:
上述代码最终会组合出对象{x: '123', y: '456'}。以上几种方式可以随意组合。
如果存在相同的key名称,后者会覆盖前者的内容。示例代码如下:
上述代码最终会组合出对象{x:1, y:6, z:3}。
2 列表渲染
1)简单列表
小程序在组件上使用wx:for属性实现列表渲染,即同一个组件批量出现多次,内容可以不同。其原理是使用wx:for绑定一个数组,然后就可以自动用数据中的每个元素依次渲染该组件,形成批量效果。
例如:
运行结果等同于:
1. <view>学生0:张三</view> 2. <view>学生1:李四</view> 3. <view>学生2:王五</view>
在以上代码中,index是数组当前项下标默认的变量名,item是数组当前项默认的变量名。
用户也可以使用wx:for-item和wx:for-index自定义当前元素和下标的变量名。将上面示例代码的WXML部分修改如下:
能得到完全一样的运行结果。
需要注意的是,wx:for引号中数组的双花括号不可以没有,如果直接填写数组变量名会被拆解为字符的形式。例如:
<view wx:for='array'>学生{{index}}:{{item}}</view> <!--array会被拆解为'a','r', 'r', 'a', 'y'-->
运行结果等同于:
1. <view>学生0:a</view> 2. <view>学生1:r</view> 3. <view>学生2:r</view> 4. <view>学生3:a</view> 5. <view>学生4:y</view>
显然这不是预期效果。
2)嵌套列表
wx:for还可以嵌套出现,例如九九乘法口诀表的代码如下:
3)多节点列表
用户可以将wx:for用在<block>标签上,以渲染一个包含多节点的结构块。例如:
运行结果等同于:
注意:<block>并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染。
4)wx:key属性
前面使用wx:for的示例均会在Console控制台得到一个warning提示,如图3-5所示。
图3-5 Console控制台的warning提示
上述提示的大致内容是建议用户使用wx:key属性提高wx:for的性能表现。
这是由于如果列表中的项目位置会动态改变或者有新的项目添加到列表中,可能导致列表乱序。如果用户明确地知道该列表是静态的或者顺序不重要,可以选择忽略该提示。
若要避免乱序的情况或不想看到该提示,可以使用wx:key属性指定列表中的项目唯一标识符。wx:key的值以下面两种形式提供。
• 字符串:代表在wx:for循环数组中的一个项目属性,该属性值需要是列表中唯一的字符串或数字,且不能动态改变。
• 保留关键字*this:代表在wx:for循环中的项目本身,这种表示需要项目本身是唯一的字符串或者数字。
这里以wx:key属性为自定义字符串为例,代码如下:
1. <view wx:for="{{[ '张三', '李四', '王五']}}" wx:key='stu{{index}}'> 2. <view>学生{{index}}:{{item}} </view> 3. </view>
运行结果等同于:
1. <view>学生0:张三</view> <!--wx:key='stu0'--> 2. <view>学生1:李四</view> <!--wx:key='stu1'--> 3. <view>学生2:王五</view> <!--wx:key='stu2'-->
当数据改变导致页面被重新渲染时会自动校正带有key的组件,以确保项目被正确排序并且提高列表渲染时的效率。
3 条件渲染
1)简单条件
在小程序框架中使用wx:if="{{condition}}"判断是否需要渲染该代码块。例如:
上述代码表示测试组件可以被显示出来,如果condition的值为false,则该组件无法显示。
用户也可以组合使用0~N个wx:elif加上一个wx:else添加其他条件,代码如下:
由于x>5不成立,A组件不被显示;x>2成立,B组件被显示并且直接忽略C组件。
2)多节点条件
如果要一次性判断多个组件标签,可以使用<block>标签将多个组件包装起来,并在<block>上使用wx:if控制属性。例如:
1. <block wx:if="{{true}}"> 2. <view> view1 </view> 3. <view> view2 </view> 4. </block>
此时可以同时控制view1和view2的显示与隐藏。
3)wx:if与hidden
读者通过学习可以发现,wx:if和hidden属性都可以规定组件的显示和隐藏效果。这两种属性的对比如表3-13所示。
表3-13 wx:if与hidden属性的对比
综上所述,如果需要频繁地切换组件,用hidden更好;如果在运行时条件很少发生改变,则wx:if更好。开发者可以根据实际需要选择其中一种属性或两种组合使用。
4 模板
小程序框架允许在WXML文件中提供模板(template),模板可以用于定义代码片段,然后在不同的页面被重复调用。
1)定义模板
小程序使用<template>在WXML文件中定义代码片段作为模板使用,每个<template>都使用name属性自定义模板名称。例如:
上述代码表示制作了一个名称为myTemp的模板,该模板包含了一个<view>组件,其内部带有两个<text>组件,分别用于表示姓名和年龄,其中name和age可以动态变化。
2)使用模板
在新的WXML页面继续使用<template>标签就可以引用模板内容了,引用的<template>标签必须带有is属性,该属性值用于指定正确的模板名称才能成功引用,然后使用data属性将模板所需要的数据值传入。例如:
模板拥有自己的作用域,只能使用data传入的数据。上述代码表示引用name为myTemp的模板,并且姓名和年龄分别更新为张三和20。
5 事件
事件是视图层到逻辑层的通信方式,有以下特点:
• 可以将用户的行为反馈到逻辑层进行处理;
• 可以绑定在组件上,当触发事件时就会执行逻辑层中对应的事件处理函数;
• 对象可以携带额外信息,例如id、dataset、touches。
1)事件的使用方式
首先需要在WXML页面为组件绑定一个事件处理函数,例如:
<button id="myBtn" bindtap="myTap" data-my="hello">按钮组件</button>
上述代码表示为按钮绑定了一个触摸单击事件,用手指触摸后将执行自定义函数myTap()。其中data-*为事件附加属性,可以由用户自定义或省略不写。
然后必须在对应的JS文件中添加同名函数,若函数不存在,则会在触发时报错。例如:
运行代码,然后触摸单击该按钮,Console控制台输出的内容如图3-6所示。
图3-6 触发tap事件时Console控制台输出的内容
展开输出内容前面的箭头可以查看详情,整理后节选信息如下:
由此可见输出的事件对象包含了按钮的id名称、附属的data-my属性值、坐标位置、事件类型等信息。例如想获得data-my中的数据值,可以描述为e.currentTarget.dataset.my,开发者之后可以利用这些信息进行后续的代码编写。
2)事件的分类
事件分为冒泡事件和非冒泡事件,说明如下。
• 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
• 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML中支持的冒泡事件如表3-14所示。
表3-14 WXML中支持的冒泡事件
注意:除了表3-14之外的其他组件自定义事件,如无特殊声明,都是非冒泡事件,例如表单<form>的提交事件、输入框<input>的输入事件等,详见第4章“小程序组件”。
3)事件绑定和冒泡
事件绑定的写法与组件的属性描述相同,均是以key=value的形式,说明如下。
• key:以bind或catch开头,然后跟事件的类型,例如bindtap、catchtouchstart。自基础库版本1.5.0起,bind和catch后可以紧跟一个冒号,其含义不变,例如bind:tap、catch:touchstart。
• value:一个字符串,需要在对应的Page中定义同名的函数。
bind事件和catch事件的区别是,bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
例如有3个<view>组件A、B、C,其中A包含B、B包含C,代码如下:
此时如果单击C组件会触发tap3,然后tap事件会向上冒泡至父节点View B导致触发tap2,但由于B组件使用的是catchtap,所以阻止了tap事件继续冒泡;如果单击了B组件会触发tap2;单击A组件会触发tap1。
4)事件的捕获阶段
自基础库版本1.5.0起,触摸类事件支持捕获阶段,可以在组件的冒泡事件被触发之前进行事件的捕获,使其无法冒泡。捕获阶段事件的顺序与冒泡阶段完全相反,是由外向内进行捕获。
事件捕获的写法是capture-bind(或capture-catch):key=value的形式,说明如下。
• capture-bind:在冒泡阶段之前捕获事件。
• capture-catch:在冒泡阶段之前捕获事件,并且取消冒泡阶段和中断捕获。
• key:事件的类型,例如tap、touchstart等,但只能是触摸类事件。
• value:一个字符串,需要在对应的Page中定义同名的函数。
例如有两个<view>组件A和B,其中A包含B,代码如下:
如果单击组件B会先后调用tap2、tap4、tap3和tap1。这是由于捕获优先级大于冒泡,且由外向内,所以会首先触发tap2然后是tap4。捕获阶段结束后由内向外触发冒泡事件,因此接着触发的是tap3最后是tap1。
注意:若把上述代码中第1行的capture-bind替换为capture-catch,则只会触发tap2,然后捕获被中断,冒泡阶段也被取消。
5)事件对象详解
事件对象可以分为基础事件(BaseEvent)、自定义事件(CustomEvent)和触摸事件(TouchEvent)。事件对象所包含的具体属性如表3-15所示。
表3-15 事件参数
注意:<canvas>组件中的触摸事件不可以冒泡,所以没有currentTarget属性。
基础事件对象中的target和currentTarget属性包含的参数相同,如表3-16所示。
表3-16 target和currentTarget参数
其中dataset只能接受data-*的传递形式,例如:
<button bindtap="myTap" data-test="hello">按钮组件</button>
触发事件后dataset所获得的集合就是{test: "hello"}。
如果描述多个词用连字符(-)连接,会被强制转换为驼峰标记法(又称为Camel标记法,特点是第一个单词全部小写,后面每个单词只有首字母大写),例如:
<button bindtap="myTap" data-my-test="hello">按钮组件</button>
触发事件后dataset所获得的集合就是{myTest: "hello"}。
如果同一个词里面有大写字母,会被强制转换为小写字母,例如:
<button bindtap="myTap" data-myTest="hello">按钮组件</button>
触发事件后dataset所获得的集合就是{mytest: "hello"}。
自定义事件对象中的detail属性用于携带数据,不同的组件所携带的detail有所差异。例如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息等,具体用法详见第4章。
触摸事件对象的touches是一个数组,里面每一个元素都是一个单独的touch对象,表示当前停留在屏幕上的触摸点,属性如表3-17所示。
表3-17 touch对象参数
canvas触摸事件中携带的touches是CanvasTouch对象形成的数组,属性如表3-18所示。
表3-18 CanvasTouch对象参数
changeTouches属性与touches完全相同,表示有变化的触摸点,例如从无变有(touchstart)、位置变化(touchmove)、从有变无(touchend、touchcancel)。
6 引用
WXML提供了import和include两种文件引用方式。
1)import
小程序可以使用<template>标签在目标文件中事先定义好模板,然后在当前页面使用<import>标签引用<template>中的内容。
例如,在tmpl.wxml文件中使用<template>定义一个名称为tmpl01的模板:
1. <template name="tmpl01"> 2. <text>{{text}}</text> 3. </template>
然后在首页index.wxml中使用<import>引用tmpl.wxml,就可以使用tmpl01模板:
1. <import src="tmpl.wxml"/> 2. <template is="tmpl01" data="{{text: 'hello'}}"/>
此时等同于在index.wxml中显示了:
<text>hello</text>
需要注意的是,<import>有作用域的概念,即只会引用目标文件自己定义的template,而不会引用目标文件里面用<import>引用的第三方模板。
假设有A、B、C 3个页面,其中B import A,且C import B,那么B页面可以使用A页面定义的<template>模板,C页面可以使用B页面定义的<template>模板,但是C页面不可以使用A页面定义的<template>模板,即使该模板已经被B页面引用。
A页面a.wxml的代码如下:
1. <template name="A"> 2. <text> A页面模板</text> 3. </template>
B页面b.wxml的代码如下:
1. <import src="a.wxml"/> 2. <template name="B"> 3. <text> B页面模板</text> 4. </template>
C页面c.wxml的代码如下:
1. <import src="b.wxml"/> 2. <template is="A"/> <!-- 引用模板失败!C必须自己import A才可以 --> 3. <template is="B"/> <!-- 引用模板成功!C页面有import B -->
这是为了避免多个页面彼此互相连接引用陷入逻辑错误。
2)include
小程序使用<include>将目标文件除了<template>以外的整个代码引入,相当于把目标文件的代码直接复制到了<include>标签的位置。
例如为页面制作统一的页眉、页脚,示例如下。
页眉header.wxml的代码:
<view>这是小程序的页眉</view>
页脚footer.wxml的代码:
<view>这是小程序的页脚</view>
首页index.wxml的代码:
1. <include src="header.wxml"/> 2. <view>正文部分</view> 3. <include src="footer.wxml"/>
<import>标签更适合于统一样式但内容需要动态变化的情况;<include>标签更适合于无须改动目标文件的情况。
3.2.2 WXSS
WXSS文件的全称是WeiXin Style Sheets(微信样式表),这是一种样式语言,用于描述WXML的组件样式(例如尺寸、颜色、边框效果等)。
为了适应广大的前端开发者,WXSS具有CSS的大部分特性,同时为了更适合开发微信小程序,WXSS对CSS进行了扩充以及修改。与CSS相比,WXSS独有的特性是尺寸单位和样式导入。
1 尺寸单位
小程序规定了全新的尺寸单位rpx(responsive pixel),可以根据屏幕宽度进行自适应。其原理是无视设备原先的尺寸,统一规定屏幕宽度为750rpx。
rpx不是固定值,屏幕越大,1rpx对应的像素就越大。例如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
常见机型的尺寸单位对比如表3-19所示。
表3-19 手机设备尺寸单位对比表
提示:由于iPhone6换算较为方便,建议开发者用该设备作为视觉设计稿的标准。
2 样式导入
小程序在WXSS样式表中使用@import语句导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。
例如有公共样式表common.wxss,代码如下:
1. .red{ 2. color:red; 3. }
然后可以在其他任意样式表中使用@import语句对其进行引用。例如a. wxss的代码如下:
1. @import "common.wxss"; 2. .blue { 3. color:blue; 4. }
此时,.red和.blue样式均能被页面a.wxml使用。
3 常用属性
WXSS所支持的样式属性与CSS属性类似,为方便读者理解本节的示例代码,表3-20列出了部分常用样式属性和参考值。
表3-20 常用样式属性和参考值
颜色可以用以下几种方式表示。
• RBG颜色:用RGB红绿蓝三通道色彩表示法,例如rgb(255,0,0)表示红色。
• RGBA颜色:在RGB的基础上加上颜色透明度,例如rgba(255,0,0,0.5)表示半透明红色。
• 十六进制颜色:又称为HexColor,用#加上6位数字表示,例如#ff0000表示红色。
• 预定义颜色:使用颜色英文单词的形式表示,例如red表示红色。小程序目前共预设了148种颜色名称,见附录。
4 内联样式
小程序允许使用style和class属性控制组件的样式。
1)style
style属性又称为行内样式,可直接将样式代码写到组件的首标签中。例如:
<view style="color:red;background-color:yellow">测试</view>
上述代码表示当前这个<view>组件中的文本将变为红色、背景将变为黄色。
style也支持动态样式效果,例如:
<view style="color:{{color}} ">测试</view>
上述代码表示组件中的文本颜色将由页面JS文件的data.color属性规定。
官方建议开发者尽量不要将静态的样式写进style中,以免影响渲染速度。如果是静态的样式,可以统一写到class中。
2)class
小程序使用class属性指定样式规则,其属性值由一个或多个自定义样式类名组成,多个样式类名之间用空格分隔。
例如,在test.wxss中规定了两个样式:
在test.wxml中代码如下:
<view class="style01 style02">测试</view>
上述代码表示组件同时接受.style01和.style02的样式规则。注意,在class属性值的引号内部不需要加上类名前面的点。
5 选择器
小程序目前在WXSS样式表中支持的选择器如表3-21所示。
表3-21 选择器
例如,在WXSS样式表中规定:
上述代码表示将当前页面中所有view组件的宽度都更新为100rpx。
6 全局样式与局部样式
对于小程序WXSS样式表中规定的样式,根据其作用范围分为两类:在app.wxss中的样式为全局样式,作用于每一个页面;在页面WXSS文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖app.wxss中相同的选择器。
3.2.3 组件
组件是WXML页面上的基本单位,例如小程序页面上的按钮、图片、文本等都是用组件渲染出来的,详细介绍见第4章“小程序组件”。