第3章 小程序框架结构介绍 3.1MINA框架 微信团队为小程序提供的框架命名为MINA应用框架(在微信官方文档中现已取消该名称),它的核心是一个响应的数据绑定系统。 如图3.1所示,MINA框架分为两大部分,分别是页面视图层与AppService应用逻辑层。在页面视图层,开发者使用WXML文件搭建页面的基本视图结构,使用WXSS文件控制页面的展现样式。小程序自己开发了一套WXML标签语言和WXSS样式语言,并非直接使用标准的HTML5+CSS3。 图3.1微信小程序框架结构图 AppService应用逻辑层是MINA的服务中心,由微信客户端启用异步线程单独加载运行。页面渲染所需的数据、页面交互处理逻辑都在AppService中实现。AppService使用JavaScript编写交互逻辑、网络请求、数据处理,小程序中的各个页面可以通过AppService实现数据管理、网络通信、应用生命周期管理和页面路由。 MINA框架提供视图层与逻辑层之间的数据绑定和事件绑定系统,该系统实现了视图层和逻辑层的基于数据绑定和事件机制的数据交互: 在逻辑层传递数据到视图层的过程中,它让数据与视图非常简单地保持同步; 当作数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。 文件结构 微信小程序开发快速入门微课视频版 第 3 章小程序框架结构介绍 MINA框架程序包含一个描述整体程序的App和多个描述各个页面的pages,一个MINA程序的主体部分由3个文件组成,这3个文件必须放在项目的根目录下,如图3.2所示。 图3.2MINA程序结构 pages用于创建页面,图3.2中的index页面和logs页面,用于展示欢迎页和小程序启动日志页面。小程序中的每个页面的“路径+页面名”都要写在app.json的pages中,且pages中的第一个页面是小程序的首页即index页面。表3.1和表3.2分别给出了程序主体部分的3个文件以及构成一个页面的4个文件。 表3.1组成MINA程序主体的3个文件 文 件 名 称 是 否 必 需 作用 app.js 是 小程序逻辑 app.json 是 小程序公共设置 app.wxss 否 小程序公共样式表 app.js是小程序的脚本代码,用于监听并处理小程序的生命周期函数、声明全局变量等。app.json用于配置小程序由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,以及配置默认标题; 需要注意的是该文件不可添加任何注释,示例代码为。 { "pages": [ "pages/index/index", "pages/logs/logs" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "sitemapLocation": "sitemap.json" } 小程序中每新增或减少页面时,都需要对app.json文件中的pages数组进行修改。 app.wxss是整个小程序的公共样式表,可以在页面组件的class属性上直接使用app.wxss中定义的样式规则,示例代码为: /**app.wxss**/ .container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; padding: 200rpx 0; box-sizing: border-box; } 表3.2给出了微信规定的小程序的界面组织模式中的4个文件构成,其中,.wxml文件(页面结构文件)类似HTML,负责页面结构,可绑定数据; .wxss文件(样式表文件)类似CSS; .json文件(配置文件)用于配置样式,如选项卡、窗口样式等; .js文件(脚本文件)用于运行页面逻辑,使用JS语言。 表3.2构成一个MINA页面的4个文件 文 件 类 型 是 否 必 需 作用 wxml 是 页面结构 wxss 否 页面样式表 json 否 页面配置 js 是 页面逻辑 3.2WXML标签语言 3.2.1WXML简介 WXML用于指定界面的框架结构,而WXSS则用于指定界面的框架及元素的显示样式。 WXML是微信小程序团队设计的一套标签语言,可以构建出页面的结构,开发者通过借助WXML提供的各种组件,可以很方便地实现文字的嵌入、图片的嵌入、视频的嵌入等。 我们知道,在开发基于Web的网络系统时,网页编程通常采用HTML+CSS+JS组合,HTML用来描述当前这个页面的结构,CSS用来描述页面的显示样式,JS用来处理这个页面和用户的交互。 在小程序中,WXML充当的就是类似HTML的角色。打开pages/index/index.wxml,其中代码如下。 <!--index.wxml--> <view class="container"> <view class="userinfo"> <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo">获取头像昵称</button> <block wx:else> <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> </view> <view class="usermotto"> <text class="user-motto">{{motto}}</text> </view> </view> 从视图层的角度来说,WXML由标签、属性等构成,但也存在如下与HTML的不同之处。 1. 开发工具不同 常见的HTML5的开发工具有Adobe Dreamweaver CS6、Adobe Edge、DevExtreme、JetBrains WebStorm等; 小程序开发有自己的开发工具,名为“微信开发者工具”,可以实现同步本地文件+开发调试+编译+预览+上传+发布等一整套流程。 2. 开发语言不同 小程序自己开发了一套WXML标签语言和WXSS样式语言,并非直接使用标准的HTML5+CSS3。 常见的HTML有,定义声音内容的<audio>标签,定义粗体字的<b>标签,定义表格标题的<caption>标签,定义文档中的节的<div>标签等; HTML5中又新增了一些标签,如装载非正文内容附属信息的<aside>标签,对标题元素(h1~h6)进行组合的<hgroup>标签,用于描述独立的流内容(图像、图表、照片、代码等)的<figure>(<figcaption>)标签等。 小程序的WXML用的标签在语法上更接近XML,遵循SGML规范,区别于HTML随意的标签闭合方式,WXML必须包括开始标签和结束标签,如<view></view>、<text></text>、<navigator url="#" redirect></navigator>等,这些标签都是小程序给开发者包装好的基本能力,还提供了地图、视频、音频等组件能力。 3. 组件封装不同 小程序独立出来了很多原生App的组件,在HTML5中需要模拟才能实现的功能,在小程序中可以用直接调用组件的形式来实现。 4. 多了一些wx: if这样的属性以及{{}}这样的表达式 在一般的网页开发流程中,通常会通过JS操作DOM,以引起界面的一些变化来响应用户的行为。例如,用户单击某个按钮的时候,JS会记录一些状态到JS变量里,同时通过DOM API操控DOM的属性或者行为,从而引起视图层的一些变化。当项目越来越大的时候,代码中将会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式,因此就有了MVVM的开发模式(例如React、Vue),提倡把渲染和逻辑分离。简单来说,就是不要再让JS直接操控DOM,JS只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可。 小程序的框架也是用到了这个思路,如果需要把一个Hello World的字符串显示在界面视图上,WXML的写法是: <text>{{msg}}</text> JS只需要管理状态即可: this.setData({ msg: "Hello World" }) 通过{{}}的语法把一个变量绑定到界面视图上,称为数据绑定。仅通过数据绑定还不能够完整地描述状态和界面的关系,还需要if/else、for等控制能力,在小程序里,这些控制能力都用以“wx:”开头的属性来表达,例如: <view wx:if="{{boolean==true}}"> <view class="bg_black"> </view> </view> <view wx:elif="{{boolean==false}}"> <view class="bg_red"> </view> </view> 3.2.2基础知识 1. 语法规则 (1) 所有的元素都需要闭合标签,例如: <text>Hello World</text>; (2) 所有的元素都必须正确嵌套,例如: <view> <text>Hello World</text> </view>; (3) 属性值必须用引号包围,例如: <text id = "myText">myText</text>; (4) 标签必须用小写。 (5) WXML中连续多个空格会合并成一个空格。 2. WXML的共同属性 由于某些属性被几乎所有的组件使用,这些属性被抽离出来,形成WXML组件的共同属性,如表3.3所示。 表3.3WXML组件的共同属性 属性名 描述 注解 id 组件唯一标识 页面内唯一 class 组件样式类 在对应的WXSS内定义 style 组件的内联样式 常用语动态样式 hidden 组件是否显示 默认显示 data* 自定义数据 触发时会进行上报 hide/catch 组件事件 3. 基本组件 组件是视图层的基本组成单元,自带一些功能与微信风格类似的样式。一个组件通常包括开始标签和结束标签,属性用来修饰这个组件,内容在两个标签之内,形如: <tagname property="value"> Contents here … </tagname> 需要注意的是,所有组件与属性都是小写,以连字符连接。 界面结构WXML主要由七大类基础组件构成,分别如表3.4~表3.10所示。 表3.4视图容器(View Container)类组件 序号 组件名 说明 1 movableview 可移动的视图容器,在页面中可以拖曳滑动 2 coverimage 覆盖在原生组件之上的图片视图 3 coverview 覆盖在原生组件之上的文本视图 4 movablearea movableview的可移动区域 5 scrollview 可滚动视图区域 6 swiper 滑块视图容器,其中只可放置swiperitem组件 7 swiperitem 仅可放置在swiper组件中,宽高自动设置为100% 8 view 视图容器 表3.5基础内容(Basic Content)类组件 序号 组件名 说明 1 icon 图标,长度单位默认为px 2 text 文本 3 progress 进度条 4 richtext 富文本 表3.6表单(Form)类组件 序号 组件名 说明 1 button 按钮 2 form 表单 3 input 输入框 4 checkbox 多选项目 5 checkboxgroup 多项选择器,内部由多个checkbox组成 续表 序号 组件名 说明 6 editor 富文本编辑器,可以对图片、文字进行编辑 7 radio 单选项目 8 radiogroup 单项选择器,内部由多个radio组成 9 picker 从底部弹起的滚动选择器 10 pickerview 嵌入页面的滚动选择器 11 pickerviewcolumn 滚动选择器子项 12 slider 滑动选择器 13 switch 开关选择器 14 label 标签,用来改进表单组件的可用性 15 textarea 多行输入框 表3.7导航(Navigator)类组件 序号 组件名 说明 1 functionalpagenavigator 仅在插件中有效,用于跳转到插件功能页 2 navigator 页面链接 表3.8媒体(Media)类组件 序号 组件名 说明 1 audio 音频 2 camera 系统相机 3 image 图片 4 liveplayer 实时音视频播放 5 livepusher 实时音视频录制 6 video 视频 表3.9地图(Map)类组件 序号 组件名 说明 1 map 地图 表3.10画布(Canvas)类组件 序号 组件名 说明 1 canvas 画布 其他更多组件及其使用方法将在第4章中予以介绍。 3.2.3WXML主要功能 1. 实现数据绑定 WXML中的动态数据均来自于对应Page的data,数据绑定使用{{}}将变量包含起来,可以作用于以下几方面。 1) 内容 如在某一个页面的JS文件中定义一个字符串型变量message,给其赋值为“Hello World”,代码为: Page({ data: { message: 'Hello World' } }) 然后在视图层的<view>组件中显示该message变量,代码为: <view> {{ message }} </view> 2) 组件属性 如视图层中某一个<view>组件的id属性命名为: <view id="item-{{id}}"> </view> 其中的id值在Page中定义: Page({ data: { id: 0 } }) 3) 控制属性 如在Page页面中定义一个boolean型变量condition,初始化为true: Page({ data: { condition:true }, }) 然后再在WXML文件中添加如下代码: <view wx:if="{{condition}}">"Hello World"</view> 编译后会在模拟器中显示Hello World字样; 如将condition改为false,则运行后不会显示。 4) 运算 可以在{{}}内进行简单的运算,如条件表达式中的三元运算: <view hidden="{{flag ? true : false}}"> Hidden </view> 以及算术运算: <view> {{a + b}} + {{c}} + d </view> Page({ data: { a: 2, b: 3, c: 4 } }) view中的显示内容为: 5+4+d。 再比如字符串运算: <view>{{"hello" + name}}</view> Page({ data:{ name: 'World' } }) 等。 2. 条件渲染 在WXML框架中,使用wx:if=""来判断是否需要渲染该代码块,如前面代码中的 <view wx:if="{{condition}}">"Hello World"</view> 也可以用wx:elif和wx:else来添加一个else块,如: <view wx:if="{{length > 5}}"> 1 </view> <view wx:elif="{{length > 2}}"> 2 </view> <view wx:else> 3 </view> 3. 列表渲染 在WXML文件中输入以下代码: <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i"> <view style='display:inline-block;width:35px' wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j"> <view wx:if="{{j <= i}}"> {{i}}*{{j}}={{i*j}} </view> </view> </view> 此处通过wx:for两重嵌套循环,实现一个九九乘法表。<view>组件的wx:for控制属性用于绑定一个数组,可利用数组中各项数据重复渲染该组件,wx:foritem用于指定数组当前元素的变量名。 以上代码编译运行后,在模拟器中的显示效果如图3.3所示。 图3.3九九乘法表的实现效果(一) 之所以会出现显示字符的重叠,是因为样式设置有问题,需要在相应页面的WXSS文件中补充相应代码,如下。 .multiply{ font-size:8px } 然后在WXML文件中添加一个视图组件<view>,将其class属性设置为: <view class="multiply"> 再将上述代码置于该<view>组件内,则最终的实现效果如图3.4所示。 图3.4九九乘法表的实现效果(二) 3.3WXSS WXML理解起来较为容易,但如果只是通过WXML搭建好了小程序页面的框架,并不能达到开发者想要的界面效果,正如同图3.3中的显示字符重叠一样,此时就需要用到WXSS。WXSS(WeiXin Style Sheets)是一套样式语言,用于描述WXML的组件样式,用来决定WXML的组件该如何显示。微信提供的WXSS具有CSS大部分的特性,并做了一些扩充和修改。 1. 新增了尺寸单位 在用CSS布局样式时,开发者需要考虑到手机终端的屏幕显示会有不同的宽度和设备像素比,从而采用一些技巧来换算一些像素单位。WXSS在底层支持新的尺寸单位rpx,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用浮点数运算,所以运算结果会和预期结果有一点点儿偏差。 rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx=375px=750物理像素,1rpx=0.5px=1物理像素。微信官方文档建议在开发小程序时以iPhone6作为视觉设计标准。 rpx与px单位换算如表3.11所示。 表3.11不同手机终端上的尺寸单位换算 序号 设备名 rpx换算px(屏幕宽度/750) px换算rpx(750/屏幕宽度) 1 iPhone5(320×568) 1rpx=0.42px 1px=2.34rpx 2 iPhone6(375×667) 1rpx=0.5px 1px=2rpx 3 iPhone6 Plus(414×736) 1rpx=0.552px 1px=1.81rpx 2. 提供了全局的样式和局部样式 和前面app.json、page.json的作用类似,可以写一个app.wxss作为全局样式,它将会作用于当前小程序的所有页面,而局部页面样式page.wxss仅对当前页面生效。 3. 样式导入 可以使用@import语句来导入外联样式表,其后面跟需要导入外联样式表的相对路径,并以分号结束。 例如,定义other页面的WXSS文件,在其中建立一个样式appText,然后在app.wxss中直接导入该样式,代码如下。 /** other.wxss **/ .appText{ margin:10px; } /** app.wxss **/ @import "other.wxss"; .content_text:{ margin:15px; } 如前所述,此处的app.wxss是全局样式,它将会作用于每一个页面; 某一个page下的WXSS文件只作用于当前页面,并会覆盖全局样式中的相同属性。 微信小程序WXSS样式的使用大部分都和CSS样式一致,如表3.12~表3.15所示。 表3.12WXSS文本(text)的主要属性 序号 属性 说明 语法(属性值) 1 color 设置文本颜色 2 direction 设置文本方向 ltr: 从左到右。rtl: 从右到左 3 letterspacing 设置字符间距 4 lineheight 设置行高 5 textalign 对齐元素中的文本 对齐方式有left、right、center、justify等 6 textindent 缩进元素中文本的首行 7 textshadow 设置文本阴影 hshadow、vshadow、blur、color 8 verticalalign 设置元素的垂直对齐 9 wordspacing 设置字间距 表3.13WXSS字体(font)的主要属性 序号 属性 说明 语法(属性值) 1 fontstyle 指定文本的字体样式 normal、italic、oblique等 2 fontweight 指定字体的粗细 normal、bold、bolder、lighter等 3 fontsize 设置字体大小 smaller、larger、length(一个固定的值)等 表3.14WXSS背景(background)的主要属性 序号 属性 说明 语法(属性值) 1 backgroundcolor 指定要使用的背景颜色 2 backgroundposition 指定背景图像的位置 center等 3 backgroundsize 指定背景图片的大小 如backgroundsize:80px 60px; 4 backgroundimage 指定要使用的一个或多个背景图像 url('URL')、none等 表3.15WXSS显示(display)的主要属性 序号 属性 说明 1 flex 多栏多列布局 2 flexdirection 主轴方向(及项目的排列方向),包含水平方向row、垂直方向column等 3 inlineblock 行内块元素 4 inlinetable 作为内联表格来显示(类似<table>),表格前后没有换行符 5 listitem 此元素会作为列表显示 6 table 会作为块级表格来显示(类似<table>),表格前后带有换行符 7 tablecaption 作为一个表格标题显示(类似<caption>) 8 tablecell 作为一个表格单元格显示(类似<td>和<th>) 9 tablecolumn 作为一个单元格列显示(类似<col>) 10 tablerow 作为一个表格行显示(类似<tr>) 11 padding 内边距 12 margin 外边距 3.4视图层和逻辑层的信息传递交互实现 一个小程序服务只有WXML+WXSS界面展示是不够的,还需要和用户交互,来响应用户的单击、获取用户的位置等。这里,通过编写JS脚本文件来举例说明视图层和逻辑层的信息传递交互逻辑的实现,如图3.5所示。 图3.5新建demo1页面 右击pages,新建一个目录,命名为“demo1”,如图3.6所示。 依次新建3个文件,分别命名为demo1.wxml、demo1.wxss和demo1.js,如图3.7所示。 图3.6新建demo1页面的相关文件 图3.7新建成功的demo1相关文件 双击demo1.wxml文件,输入以下代码。 <view>{{msg}}</view> <button bindtap="clickMe">单击我</button> 现在要实现的交互逻辑是,当用户单击“单击我”按钮的时候,将界面上msg变量的信息显示成“Hello World”。首先需要在button上声明一个属性bindtap,在JS文件里声明clickMe方法来响应这次单击操作。 双击demo1.js文件,在其中输入以下代码。 Page({ data: { msg:"WeChat" }, clickMe: function () { this.setData({msg: "Hello World" }) } }) 再打开app.json文件,在pages中增加: "pages/demo1/demo1" 然后,自定义编译条件如图3.8所示。 让小程序首先从demo1启动,编译运行,界面如图3.9所示。 单击“单击我”按钮后,“WeChat”文字就变成了“Hello World”,如图3.10所示。 在这个例子中,WXML文件用于定义要显示的视图内容,变量msg在JS文件中定义并初始化为“WeChat”,通过定义button的单击事件函数clickMe,将msg的内容变更为“Hello World”,并在WXML文件中显示。 图3.8自定义编译条件 图3.9初始运行界面 图3.10按钮单击后的界面 除了自定义触发函数外,还可以在JS中调用小程序提供的丰富的API,利用这些API可以很方便地调用微信提供的能力,例如,获取用户信息、本地存储、微信支付等。第2章的例子程序图2.10中,在pages/index/index.js中就调用了wx.getUserInfo获取微信用户的头像和昵称,最后通过setData把获取到的信息显示到界面上,代码为: wx.getUserInfo({ success: res => { app.globalData.userInfo = res.userInfo this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) 3.5配置文件解析 当小程序开发工具运行后,出现如图3.11所示界面。 其中,pages规定了路由页面指定的页面模块,每个pages模块下面都包含JS、WXML、WXSS、 图3.11配置文件 JSON文件。图3.11中有两个页面,分别为index和logs,就对应两组配置文件。utils规定公用的方法,其中,小程序加载的时候必须以app.js作为入口开始运行,全局方法和变量可以放在里面。 3.5.1app.json app.json文件必须要有,如果没有这个文件,IDE会报错,因为微信框架把这个文件作为配置文件入口。当IDE报错的时候,只需新建这个文件,里面写个大括号,暂时不添加其他任何代码即可通过编译。在后续开发中,再根据项目需要继续完善该文件的配置。 如图3.11所示,app.json为当前小程序的全局配置,包括小程序的所有页面路径、界面表现、网络超时时间、底部选项卡等。 打开app.json文件,其中代码如下。 { "pages": [ "pages/index/index", "pages/logs/logs" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "sitemapLocation": "sitemap.json" } 这里的pages字段,用于描述当前小程序所有页面路径,用于让微信客户端知道当前小程序页面定 图3.12导航栏背景色 义在哪个目录。window字段,定义小程序所有页面的顶部背景颜色、文字颜色等。navigationBarBackgroundColor用于指定导航栏背景色,如改为“#0f0”,则效果如图3.12所示。 3.5.2project.config.json project.config.json文件为每个开发者在本地生成的一些配置。 3.5.3app.wxss app.wxss文件用于配置一些公用的样式设置,例如: .container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; padding: 200rpx 0; box-sizing: border-box; } 3.5.4app.js app.js文件也必须要有,没有也会报错。但是这个文件创建一下就行,什么都不需要写,以后可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。 3.5.5app.wxml app.wxml文件不是必须要有,它只是规定了全局页面视图文件。 3.6小程序的启动 微信客户端在打开小程序之前,将其代码包下载到本地手机终端,然后通过app.json的pages字段获取当前小程序的所有页面路径,例如: { "pages":[ "pages/index/index", "pages/logs/logs" ] } 上述代码配置说明在当前小程序项目中定义了两个页面,分别位于pages/index/index和pages/logs/logs。其中,pages字段的第一个页面就是这个小程序的首页,即打开小程序看到的第一个页面。根据该页面设置信息,微信客户端将首页代码装载进来,进而渲染出这个首页。 小程序启动之后,在app.js中定义的App实例的onLaunch回调会被执行: App({ onLaunch: function () { … } }) 整个小程序只有一个App实例,是全部页面共享的。 另外一个页面是pages/logs/logs,它包含4个文件,如图3.13所示, 分别为logs.js、logs.json、logs.wxml、logs.wxss。 图3.13logs页面包含的4个文件 微信客户端会根据logs.json配置生成一个界面,顶部的颜色和文字都可以在这个JSON文件中定义好,其代码为: { "navigationBarTitleText": "查看启动日志", "usingComponents": {} } 紧接着,微信客户端就会装载这个页面的WXML结构和WXSS样式,其中,logs.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> logs.wxss的代码为: .log-list { display: flex; flex-direction: column; padding: 40rpx; } .log-item { margin: 10rpx; } 最后客户端会装载logs.js,可以看到logs.js的大体内容如下。 //logs.js const util = require('../../utils/util.js') Page({ data: { logs: [] }, onLoad: function () { this.setData({ logs: (wx.getStorageSync('logs') || []).map(log => { return util.formatTime(new Date(log)) }) }) } }) Page是一个页面构造器,这个构造器就生成了一个页面。在生成页面的时候,小程序框架会把data数据和index.wxml一起渲染出最终的结构。在渲染完界面之后,页面实例就会收到一个onLoad的回调,可以在这个回调中处理程序逻辑。 3.7事 件 绑 定 事件是一种用户的行为,如长按某一种图片(识别二维码,或进行编辑处理等操作)是一个事件,单击某个按钮是另外一个事件,还有其他很多不同的事件。事件也是一种视图层到逻辑层的通信方式,如当用户单击按钮的时候,UI层会把一些信息发送给程序中的逻辑代码; 事件可以绑定在组件上,当触发事件发生时,就会执行逻辑层中对应的事件处理函数; 事件对象可以携带额外信息,如id、dataset、touches。 3.7.1事件的类别 每个控件都有自己的事件,事件分为四种类型,如表3.16所示。 表3.16事件类型 序号 事 件 类 型 详 细 信 息 1 单击事件tap 2 长按事件longtab 3 触摸事件touch touchstart开始触摸 touchend结束触摸 touchmove移动触摸 touchcancel取消触摸 4 其他的触摸事件submit 3.7.2事件的使用方式 首先需在组件中绑定一个事件处理函数,如以下代码: <view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view> 该代码定义该view组件的id为“tapTest”,自定义属性为datahi,绑定一个bindtap事件“tapName”,当用户单击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。 接下来,就需要在相应的Page定义中添加相应的事件处理函数,参数为event。 tapName:function(event){ console.log(event) } 当单击“Click me!”时,在控制台显示出来的信息大致为: { "type":"tap", "timeStamp":4117, "target": { "dataset": { "hi":"WeChat" } "id": "tapTest", }, "currentTarget": { "dataset": { "hi":"WeChat" } "id": "tapTest", }, "detail": { "x":48.79999542236328, "y":656.6000366210938 }, "touches":[{ clientX: 49.60000228881836 clientY: 491.20001220703125 force: 1 identifier: 0 pageX: 49.60000228881836 pageY: 657.6000366210938 }], "changedTouches":[{ clientX: 49.60000228881836 clientY: 491.20001220703125 force: 1 identifier: 0 pageX: 49.60000228881836 pageY: 657.6000366210938 }] } 3.7.3冒泡事件与非冒泡事件 事件分为冒泡事件和非冒泡事件。冒泡事件是指,当一个组件上的事件被触发后,该事件会向父节点传递; 非冒泡事件是指,当一个组件上的事件被触发后,该事件不会向父节点传递。 这里举例说明什么是冒泡事件。 首先打开index.wxss文件,定义3个view组件的样式,在该WXSS文件中增加以下代码。 .view1{ height:500rpx; width:100%; background-color:cyan } .view2{ height:300rpx; width:80%; background-color:greenyellow } .view3{ height:100rpx; width:60%; background-color:red } 然后在启动界面index.wxml中,创建3个逐层嵌套的view组件,代码如下。 <view class="view1" bindtap="clickView1"> HelloWorld1 <view class="view2" bindtap="clickView2"> HelloWorld2 <view class="view3" bindtap="clickView3"> HelloWorld3 </view> </view> </view> 再在index.js文件中定义单击事件处理函数,增加以下代码,在控制台输出对应的log信息。 clickView1:function(){ console.log("您单击了view1") }, clickView2: function () { console.log("您单击了view2") }, clickView3: function () { console.log("您单击了view3") }, 以上代码完成以后,进行“编译”,观看运行效果。 可以发现,当单击HelloWorld1的时候,在控制台显示如图3.14所示信息。 图3.14单击view1时console输出的信息 当单击HelloWorld2的时候,在控制台显示如图3.15所示信息。 当单击HelloWorld3的时候,在控制台显示如图3.16所示信息。 图3.15单击view2时console 输出的信息 图3.16单击view3时console 输出的信息 为什么会出现这种情况?首先来分析一下index.wxml文件中3个view组件的嵌套层次关系,如图3.17所示。第1个view组件(view1)是第2个view组件(view2)的父节点,第2个view组件(view2)又是第3个view组件(view3)的父节点。 图3.17view组件的 层次关系 也就是说,当单击子view组件的时候,同时也触发了所有父view节点的事件,这种触发事件就称为冒泡事件; 如果不触发父节点的事件,则即为非冒泡事件。 WXML的冒泡事件主要如表3.17所示。 表3.17冒泡事件主要列表 序号 事 件 类 型 触 发 条 件 1 touchstart 手指触摸动作开始 2 touchmove 手指触摸后移动 3 touchcancel 手指触摸动作被打断,如来电提醒、弹窗等 4 touchend 手指触摸动作结束 5 tap 手指触摸后马上离开 6 longpress 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 7 longtap 手指触摸后,超过350ms再离开 除表3.17中所列事件类型之外的其他组件自定义事件都是非冒泡事件,如<form>的submit事件,<input>的input事件,<scrollview>的scroll等。 3.7.4事件绑定和冒泡 事件绑定的写法同组件的属性,为key和value的形式。key以bind或catch开头,然后跟上事件的类型,如bindtap、catchtouchstart。value是一个字符串,需要在对应的Page中定义同名的函数。 需要注意的一点是: bind事件绑定不会阻止冒泡事件向上冒泡,但catch事件绑定可以阻止冒泡事件向上冒泡。 如将上例中index.wxml中组件的事件绑定由bind改为catch,具体如下。 <view class="view1" catchtap="clickView1"> HelloWorld1 <view class="view2" catchtap="clickView2"> HelloWorld2 <view class="view3" catchtap="clickView3"> HelloWorld3 </view> </view> </view> 则在运行界面中,依次单击3个view组件,尽管tap类型组件为冒泡组件,均不会触发父节点的事件,显示结果如图3.18所示。 图3.18catch事件绑定的显示效果 下面在这个例子的基础上增加一点儿程序设计的逻辑,即如何让文字的背景及颜色随着单击事件进行变化。 首先,在index.wxss中添加两个样式,一个用于显示购物车信息的view组件,另一个用于text组件。 .cart{ float: center; text-align: center; padding: 5rpx 20rpx; } .cart-text{ font-style: normal; font-weight: bold; font-size: 30rpx; line-height: 50rpx; } 然后打开index.wxml文件,添加如下代码。 <view class="cart" style="background-color:{{BgColor}}" bindtap='add2Cart'> <text class="cart-text" style="color:{{TextColor}}">{{add2Cart}}</text> </view> 其中,view组件中的style="backgroundcolor:{{BgColor}}"用于定义背景颜色,其中的变量BgColor在index.js中的data里面定义,'add2Cart'为该view组件的事件触发函数; text组件中的style="color:{{TextColor}}"用于定义该组件显示的文字颜色,add2Cart为index.js文件中data里面定义的变量。 以上几个data数据域中定义的变量的相应代码为: //index.js Page({ data: { … BgColor: '#5cb85c', TextColor:"red", add2Cart: "添加至购物车", }, … }) 接下来定义view组件单击触发的事件函数代码,如下。 add2Cart: function () { var bgColor;//定义局部变量,用于存放view组件的背景色 var textColor; //定义局部变量,用于存放text组件的文本颜色 var add2cart; //定义局部变量,用于改变text组件的文本信息 if(flag){ //此处的flag变量是一个boolean变量,用于改变显示信息 bgColor = "blue"; textColor = "gray"; add2cart = "是否添加至购物车?"; flag=false; } else{ bgColor = "gray"; textColor = "blue"; add2cart = "已添加至购物车!"; flag=true; } this.setData({ //将逻辑层的数据变化信息传送到视图层进行渲染显示 BgColor: bgColor, TextColor: textColor, add2Cart: add2cart }); }, 关于上述代码中的flag变量的定义,如果放在Page中的data域来定义,编译就会报错,此时的一个处理方法,是将其放在Page的外面作为全局变量来定义,例如: //index.js //获取应用实例 const app = getApp() var flag = true Page({ data: { … 则会通过系统编译,初始运行界面如图3.19(a)所示,初始背景色BgColor为#5cb85c,TextColor颜色为red,显示文本变量add2Cart初始化为“添加至购物车”; 当第一次单击后,由于flag初始化为true,因此执行ifelse中的if分支,通过代码: bgColor = "blue"; textColor = "gray"; add2cart = "是否添加至购物车?"; flag=false; 分别改变背景色、文本色、显示文本,以及boolean变量flag的信息,从true变为false,这样当再一次单击的时候,就会执行到ifelse中的else分支,通过代码: bgColor = "gray"; textColor = "blue"; add2cart = "已添加至购物车!"; flag=true; 再次改变显示信息以及flag变量的值,这样当下一次单击的时候,又会交替执行ifelse中的if分支,如此不断变化下去。 最后部分的代码非常关键,它完成逻辑层与渲染层的交互,代码如下: this.setData({ BgColor: bgColor, TextColor: textColor, add2Cart: add2cart }); 通过更新data中的变量,将逻辑层的数据变化信息传送到视图层进行渲染显示,最终才会得到图3.19的显示效果。 图3.19不同执行阶段触发渲染的不同界面 思考题 1. WXML与WXSS各自在前端页面开发中分别起什么作用?二者缺其一会怎么样? 2. 微信小程序主要目录和文件的作用分别是什么? 3. 请谈谈WXML与标准的HTML的异同。