本次的系列博文的知识点讲解和代码,主要是来自于 七月老师 的书籍《微信小程序开发:入门与实践》,由个人总结并编写,关于更多微信小程序开发中的各项技能,以及常见问题的解决方案,还请大家购买书籍进行学习实践,该系列博文的发布已得到七月老师的授权许可
我们在 WeChat 文章列表页面(一) 中,已经完成了文章列表页面了,效果图如下所示
Page 页面的生命周期
post.js 文件默认包含的代码如下所示
代码语言:javascript复制Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})
可以看到,整个页面执行了一个 Page({...}) 方法,参数是一个 Object 对象,用来指定页面的初始数据 (data)、生命周期函数 (on 开头的函数)、事件处理函数等
一个页面从创建到卸载,会经历以下 5 个周期:加载、显示、渲染、隐藏、卸载,MINA 框架分别提供了 5 个生命周期函数来监听这 5 个特定的生命周期,以方便开发者可以在这些特定的时刻执行一些自己的代码逻辑,它们分别是:
- onLoad 监听页面加载,一个页面只会调用一次
- onShow 监听页面显示,每次打开页面都会调用
- onReady 监听页面初次渲染完成,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
- onHide 监听页面隐藏
- onUnload 监听页面卸载
除了以上 5 个生命周期函数之外,还包括以下 3 个小程序特定事件的处理函数:
- onPullDownRefresh 监听用户下拉动作的事件处理函数
- onReachBottom 页面上拉触底事件的处理函数
- onShareAppMessage 用户点击右上角分享
我们接下来通过控制台打印的方式,来看下生命周期函数及事件处理函数的触发时机,也可以通过打断点的方式,进行调试,具体代码如下所示:
代码语言:javascript复制Page({
data: {
},
onLoad: function (options) {
console.log("onLoad:页面被加载");
},
onReady: function () {
console.log("onReady:页面被渲染");
},
onShow: function () {
console.log("onShow:页面被显示");
},
onHide: function () {
console.log("onHide:页面被隐藏");
},
onUnload: function () {
console.log("onUnload:页面被卸载");
},
onPullDownRefresh: function () {
console.log("onPullDownRefres:监听用户下拉动作");
},
onReachBottom: function () {
console.log("onReachBottom:页面上拉触底事件");
},
onShareAppMessage: function () {
console.log("onShareAppMessage:用户点击右上角分享");
}
})
可以看到,一个页面要正常显示,必须经过以上 3 个生命周期:加载、显示、渲染,至于 onHide 和 onUnload 函数,以及 3 个特定事件的处理函数,它们的触发都需要执行一些 API 操作,这些我们到之后的部分再做介绍
官方文档中,也是给出 Page 实例生命周期的图解,同时也告诉我们,以下内容你不需要立马完全弄明白,不过以后它会有帮助,所以在这里建议大家的是,在遇到问题或者业务需要时,再回过头来研究这张完整的生命周期图更有意义
数据绑定
在真实项目中,业务数据通常都放置在自己的服务器中,然后通过 HTTP 请求来访问服务器提供的 RESTFUI API,从而实现数据的获取
接下来,我们尝试将编码在 post.wxml 文件里的数据移植到 post.js 中,在 post.js 中加入一个临时变量 postData 来模拟文章数据,并将上一小节中测试生命周期的代码移除,编写完成后的代码如下:
代码语言:javascript复制Page({
data: {
date: "Jan 28 2017",
title: "小时候的冰棍儿与雪糕",
postImg: "/images/post/post-4.jpg",
avatar: "/images/avatar/avatar-5.png",
content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
readingNum: 92,
collectionNum: 108,
commentNum: 7
},
})
如果是传统的网页开发,我们会想到,先获取 HTML 文档的 DOM,然后对 DOM 标签进行复制,从而实现数据的显示,但在小程序中,是没有 DOM 结构的,无法通过这样的方式,将数据“填充”到页面当中
在现在流行的 MVC 或者 MVVM 框架中,如 AngularJS、Vue.js 中,都有数据绑定的概念,小程序也是借鉴了这些流行框架的思想,采用数据绑定的机制来做数据的初始化和更新
不同于 AngularJS 的双向数据绑定,小程序仅实现了单向数据绑定,即只支持从逻辑层传递到渲染层的数据绑定,反之则不可以
小程序使用 Page 方法参数里的 data 变量作为数据绑定的桥梁,data 里已经被我们放置了一些数据,这些直接写在 data 里的数据,被称为数据绑定的初始化数据
需要注意的是,数据绑定有以下两种:
- 一种是初始化数据的数据绑定,通常将这些数据直接写在 Page 方法参数的 data 对象下面
- 另外一种是使用 setData 方法来做数据绑定,这种方式也可以理解为数据更新,这样的数据更新将引起页面的 Rerender(重新渲染),参考上一小节的页面生命周期图
接下来,我们对 post.wxml 文件做一些改动,使用 Mustache 语法的双大括号 {{}} 在 wxml 组件里进行数据的绑定,凡是对标签属性做绑定的,一定要记得加上双引号,代码如下:
代码语言:javascript复制<view class="post-container">
<view class="post-author-date">
<image src="{{avatar}}" />
<text>{{date}}</text>
</view>
<text class="post-title">{{title}}</text>
<image class="post-image" src="{{postImg}}" mode="aspectFill" />
<text class="post-content">{{content}}</text>
<view class="post-like">
<image src="/images/icon/wx_app_collect.png" />
<text>{{collectionNum}}</text>
<image src="/images/icon/wx_app_view.png" />
<text>{{readingNum}}</text>
<image src="/images/icon/wx_app_message.png" />
<text>{{commentNum}}</text>
</view>
</view>
我们通过页面生命周期图解,来解释一下初始化数据绑定的过程,当页面执行了 onShow 函数后,逻辑层会收到一个通知 (Notify);随后逻辑层会将 data 对象以 json 的形式发送到 View 视图层 (Send Initial Data),视图层接受初始化数据后,开始渲染并显示初始化数据 (First Render),最终将数据呈现在开发者的面前
我们打开“编辑”选项卡,点击 AppData 就能够看到数据绑定变量,如下图所示
点击 Tree 选项,切换成 Code,数据将以 json 的形式呈现,如下图所示
如果 data 对象的属性较为复杂,包括对象和数组,那需要相应的调整 wxml 文件,可以看下面两张图进行理解
数据绑定更新
通过 setData 函数来进行数据绑定,这种方式可以理解为“数据更新”,setData 方法位于 Page 对象的原型链上:Page.prototype.setData,在大多数的情况下,我们使用 this.setData 的方式来调用这个方法
setData 的参数接受一个对象,以 key 和 value 的形式将 this.data 中的 key 对应的值设置成 value,这句话需要注意两点:① setData 会改变 this.data 变量里相同 key 的值;② setData 执行后会通知逻辑层执行 Rerender,并立刻重新渲染视图
代码语言:javascript复制Page({
data: {
object: {
date: "Jan 28 2017"
},
title: "小时候的冰棍儿与雪糕",
postImg: "/images/post/post-4.jpg",
avatar: "/images/avatar/avatar-5.png",
content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
readingNum: 92,
collectionNum: {
array: [108]
},
commentNum: 7
},
onLoad:function(){
this.setData({
title: "一根雪糕的经济学原理"
})
}
})
可以看到,第一篇文章的标题由 data 里所设置的 title:"小时候的冰棍儿与雪糕",被更改成了“一根雪糕的经济学原理”,key 可以使用字符串来表示,可以看下面 3 个例子
代码语言:javascript复制onLoad:function(){
this.setData({
"title": "一根雪糕的经济学原理"
})
}
代码语言:javascript复制onLoad:function(){
this.setData({
"collectionNum.array[0]": 66
})
}
代码语言:javascript复制onLoad:function(){
this.setData({
"object.date": "Jan 28 2012"
})
}
使用 this.setData 所绑定或者更新的数据,并不要求在 this.data 中已预先定义,接下来,我们将 post.js 文件修改成使用 setData 函数进行数据绑定
代码语言:javascript复制Page({
data: {
},
onLoad:function(){
var iceCreamData = {
object: {
date: "Jan 28 2017"
},
title: "小时候的冰棍儿与雪糕",
postImg: "/images/post/post-4.jpg",
avatar: "/images/avatar/avatar-5.png",
content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
readingNum: 92,
collectionNum: {
array: [108]
},
commentNum: 7
}
this.setData({
postData: iceCreamData
})
}
})
相应的,wxml 文件修改如下:
代码语言:javascript复制<view class="post-container">
<view class="post-author-date">
<image src="{{postData.avatar}}" />
<text>{{postData.object.date}}</text>
</view>
<text class="post-title">{{postData.title}}</text>
<image class="post-image" src="{{postData.postImg}}" mode="aspectFill" />
<text class="post-content">{{postData.content}}</text>
<view class="post-like">
<image src="/images/icon/wx_app_collect.png" />
<text>{{postData.collectionNum.array[0]}}</text>
<image src="/images/icon/wx_app_view.png" />
<text>{{postData.readingNum}}</text>
<image src="/images/icon/wx_app_message.png" />
<text>{{postData.commentNum}}</text>
</view>
</view>
此时,我们来看一下 AppData 面板中的数据绑定情况
目前,关于数据绑定的错误,小程序不会给出任何的错误提示,如果你发现整个页面是空白的有没有错误消息,多半是数据绑定出了问题,而这个时候,AppData 面板就是我们最好的调试工具
最后我们再将另外两篇的文章的数据提取到 post.js 文件中,同一篇文章的数据组成一个数组
代码语言:javascript复制Page({
data: {
},
onLoad:function(){
var postList = [{
object: {
date: "Jan 28 2017"
},
title: "小时候的冰棍儿与雪糕",
postImg: "/images/post/post-4.jpg",
avatar: "/images/avatar/avatar-5.png",
content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
readingNum: 92,
collectionNum: {
array: [108]
},
commentNum: 7
},
{
object: {
date: "Jan 9 2017"
},
title: "从童年呼啸而过的火车",
postImg: "/images/post/post-5.jpg",
avatar: "/images/avatar/avatar-1.png",
content: "小时候,家的后面有一条铁路。听说从南方北上的火车都必须经过这条铁路。火车大多在晚上经过,但也不定时只有在夜深人静的时候,火车的声音才能从远方传来...",
readingNum: 92,
collectionNum: {
array: [108]
},
commentNum: 7
},
{
object: {
date: "Jan 29 2017"
},
title: "记忆里的春节",
postImg: "/images/post/post-1.jpg",
avatar: "/images/avatar/avatar-3.png",
content: "年少时,有几样东西,是春节里必不可少的:烟花、心意、凉茶、压岁钱、饺子。年分大小年,有的地方是腊月二十三过小年,有的地方是腊月二十四...",
readingNum: 92,
collectionNum: {
array: [108]
},
commentNum: 7
}
]
this.setData({
postList: postList
})
}
})
列表渲染 wx:for
上一小节,我们已经把三篇文章的数据提取到 post.js 文件中了,但是 wxml 文件我们并没有改写,我们固然可以像改写第一篇文章一样,依次修改其他两篇文章的 {{}} 绑定,但假如这里有 100 篇文章呢?
小程序提供了一个 wxml 组件的 for 循环,称为列表循环,它具体指的是,在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件,默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
代码语言:javascript复制<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
代码语言:javascript复制<block wx:for="{{postList}}" wx:for-index="idx" wx:for-item="item">
<view class="post-container">
<view class="post-author-date">
<image src="{{item.avatar}}" />
<text>{{item.object.date}}</text>
</view>
<text class="post-title">{{item.title}}</text>
<image class="post-image" src="{{item.postImg}}" mode="aspectFill" />
<text class="post-content">{{item.content}}</text>
<view class="post-like">
<image src="/images/icon/wx_app_collect.png" />
<text>{{item.collectionNum.array[0]}}</text>
<image src="/images/icon/wx_app_view.png" />
<text>{{item.readingNum}}</text>
<image src="/images/icon/wx_app_message.png" />
<text>{{item.commentNum}}</text>
</view>
</view>
</block>
在这里的 <block>
标签并没有实际意义,可以理解为常见编程语言里的括号
事件
在讲述事件之前,我们先将导航栏的颜色修改成其他颜色,只需在 app.json 中修改即可,并将 welcome 页面重新调整为启动页面
代码语言:javascript复制{
"pages": [
"pages/welcome/welcome",
"pages/post/post"
],
"window": {
"navigationBarBackgroundColor": "#4A6141"
}
}
但也别忘记,把 welcome 页面的导航栏颜色修改回来,welcome.json 文件里的代码如下:
代码语言:javascript复制{
"navigationBarBackgroundColor": "#b3d4db"
}
那么页面的 json 文件配置和 app.json 文件的配置有什么不同?
- 页面的 json 文件只能够配置和 window 相关的属性,但 app.json 除了可以配置 window 外,还可以配置 pages、tabBar 等选项
- 页面的 json 配置不需要像 app.json 那样,加上 window 这个对象,直接编写 window 下面的配置项即可
接下来,我们想要从 welcome 页面跳转到 post 页面,需要使用事件来响应点击“开启小程序之旅”这个动作,要实现这个操作,需要做两件事情:
- 在组件上注册事件,注册事件将告诉小程序,我们要监听哪个组件的什么事件,而在这里,就是监听“开启小程序之旅”这个组件的 tap 事件
- 在 js 中编写事件处理函数响应事件,也就是说,监听到事件后,需要编写自己的业务,我们在这里将调用 MINA 框架的导航 API,让 welcome 页面跳转到 post 页面
更改 welcome.wxml 页面的代码,具体代码如下所示:
代码语言:javascript复制<view class="container">
<image class="avatar" src="/images/avatar/niangao.png"></image>
<text class="motto">Hello,Nian糕</text>
<view class="journey-container" catchtap="onTapJump">
<text class="journey">开起小程序之旅</text>
</view>
</view>
这里和之前的代码并没有太大的改动,仅仅是在 class="journey-container"
的这个 view 组件上添加了一个 catchtap="onTapJump"
的事件绑定,需要注意的是,tap 是一个冒泡事件,常见的冒泡事件类型还有以下几种:
- touchstart 手指触摸动作开始
- touchmove 手指触摸后移动
- touchcancel 手指触摸动作被打断,如来电提醒、弹窗
- touchend 手指触摸动作结束
- tap 手指触摸后马上离开
- longtap 手指触摸后,超过 350ms 再离开
小程序在 wxml 组件里注册事件时,不可以直接使用 tap="function" 或 touch="function",而是需要在事件名之前添加 catch 或者 bind 前缀,catch 将组织事件继续向父节点传播,而 bind 不会阻止事件的传播
当用户点击之后,会执行一个 onTapJump 的函数,而该函数需要在页面的 js 中定义
代码语言:javascript复制Page({
onTapJump:function(event){
wx.redirectTo({
url:"../post/post",
success:function(){
console.log("jump success")
},
fail:function(){
console.log("jump failed")
},
complete:function(){
console.log("jump complete")
}
});
}
})
微信小程序一共提供了 5 个导航 API,以帮助开发者实现页面的跳转
- wx.redirectTo 关闭当前页面,跳转到应用内的某个页面
- wx.navigateTo 保留当前页面,跳转到应用内的某个页面,使用 wx.navigateBack 可以返回到原页面
- wx.switchTap 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
- wx.navigateBack 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
- wx.reLaunch 关闭所有页面,打开到应用内的某个页面
这 5 个方法都接受一个 Object 对象作为参数,除了 wx.navigateBack 之外,其余 4 个方法的 Object 参数还可以接受 3 个方法,分别是:
- success 跳转页面成功时 MINA 框架将调用此函数
- fail 跳转页面失败时 MINA 框架将调用此函数
- complete 无论成功或者失败,MINA 框架都将调用此函数
我们选取了 redirectTo 方法进行页面跳转,但是 redirectTo 方法将关闭当前页面并将页面卸载,无法从 post 页面返回到 welcome 页面当中,所以我们在这里选取 navigateTo 的方法进行页面的跳转,在 post 页面的左上角会有一个返回按钮,至于两个页面之间跳转时,两个页面是被卸载还是被隐藏,大家可自行在 welcome.js 文件和 post.js 文件当中添加下面的代码进行分析理解
代码语言:javascript复制onUnload: function (event) {
console.log("page is unload")
},
onHide: function (event) {
console.log("page is hide")
},
该章节的内容到这里就全部结束了,源码我已经发到了 GitHub WeChat_03 上了,有需要的同学可自行下载