本周实验课完成《电影周周看》的第二个版本,主要包括三方面:
在前面讲到的 about.wxml 和 weekly.wxml 页,所有的数据都是直接硬编码的。
这种方式对于about页是没有问题的,因为里面关于小程序的名称、属性等数据,是不会变化的。在小程序编写的时候,就已经确定了,这种我们叫做静态数据,这些进行硬编码是没有问题。
但是我们看一下 weekly 页面,如下图所示:
本周推荐的是《教父》这部电影,这里使用硬编码是不可取的。因为每周推荐的电影,肯定是要变化的。如果我们使用硬编码的方式,这就意味着,每周我都要修改 wxml 代码,然后对小程序进行重新打包,并进行上传发布,这样是非常麻烦的。
这里我们期待的是,每周推荐电影的详细数据,在小程序运行过程中,可以动态的从服务器端获取,然后再渲染出到这个视图中进行显示。
DOM API 在具体实现的时候,一般会在页面加载的过程中,通过一个ajax调用来请求server返回本周推荐电影的详细信息。假设我们收到的是这样一个javascript对象:
var thisWeekMovie = {
name:"教父",
comment: "最精彩的剧本,最真实的黑帮电影",
imagePath: "/images/jf.jpg"
}
这样在页面中,会进行这样一个操作:
document.getElementbyId("t001").innerHMTL = thisWeekMovie.name
这个是基于 DOM API的一种方式。接下来思考,这种方式有什么缺点?
接下来思考,理想的方式应该是什么样?
理想的方式应该是有一种机制,让视力中每一个部分与对应的数据做一个映射。当建立这个映射以后,开发者就只需要关注如何去获取这个数据。
接下来我们看小程序中如何实现数据绑定,在小程序框架中,每个页面所需要的各种数据,都是集中在这个页面所注册的页面对象中定义的。即在页面对应的 js 文件中 Page 对象中的 data 属性来定义。
这里我们在 weekly.js 里面添加数据变量:
Page({
data: {
thisWeekMoview: [
{
name: "教父",
comment: "最精彩的剧本,最真实的黑帮电影。",
imagePath: "/images/jf.jpg"
}
],
count: 123
}
})
其中, count 表示目前累计有多少人查看这个页面。接下来我们看如何绑定这个数据在 视图中进行显示。首先来显示 count 的值,要把值渲染输出到视图,我们是通过一个双大括号进行数据绑定的。添加下面代码:
<text>{{count}}text>
这样,123就显示在视图中了。这就是最简单的一种数据绑定。还有一些复杂的数据绑定,即通过一定的运算来进行显示。数据绑定部分比较简单,这里不再多说。
在小程序调试窗口里面的 AppData,可以对页面里的数据进行调试。我们可以看到,小程序给页面 weekly 添加了一个变量 webviewId 等于10。这个是做什么用的呢?
这就需要了解小程序的运行环境和运行架构。
运行环境: 每个小程序都是运行在它所在的微信客户端上的,通过微信客户端给它提供运行环境,小程序可以直接获取微信客户端的原生体验和原生能力。
视力层和逻辑层: 小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。
所以,刚才我们在 AppData 中可以看到,about页和weekly页都内置了一个 webviewId 的内部状态变量,记录它们各自是在几号 webview 进程中进行渲染的。
条件渲染指的是:条件成立时才渲染生成。
拿上面的例子来看,我们给推荐页面的电影添加一个这段,表明这个电影是不是强烈推荐的。
isHighlyRecommended: true
我们现在要做的一个改动就是,希望在视图中,对于真正想强烈推荐的电影,我们要显示一个强烈推荐的红色标记。
首先我们把强烈推荐的元素定义出来,添加到 weekly.wxml 中:
<text style="font-size:16px; color:red;"> 强烈推荐 text>
这时可以看到,在视图中,出现了红色的 “强烈推荐” 的字样。
但是这个是肯定会生成的,但我们需要的效果是,如果电影是要推荐的,就生成;不是推荐的,就不生成。
所以,我们就可能 通过 wx:if 做条件判断来实现。代码可以修改为:
<text wx:if="{{thisWeekMovie.isHighlyRecommended}}" style="font-size:16px; color:red;">强烈推荐text>
这个意思是,强烈推荐这个 text 元素,渲梁生成的条件是:绑定到了 thisWeekMoview 的 isHighlyRecommended 属性上。当这个属性是 true 的情况下,这个 text 元素会被生成,如果这个属性是 false 的情况下,这个 text 元素就不会生成。
同时,我们也可以使用 hidden 属性来实现这个效果。 代码如下:
<text hidden="{{!thisWeekMovie.isHighlyRecommended}}" style="font-size:16px; color:red;">强烈推荐text>
这里注意是求反的操作。当用户没有推荐的时候,hidden是true;用户强烈推荐的时候,hidden是false,所以是求反的操作。
此时,我们在 AppData 里面进行查看,可以看到,这个 text 元素无论 hidden 是取 true 还是 false,都始终是生成的。因此,我们可以知道,使用 hidden 属性时,这个元素总是要被渲染生成的,hidden 只是控制了其可见性而已。
那么,在实际应用中,我们该如何选择呢?
从上面的例子我们可以看到,当wx:if的条件值切换时,框架有一个局部渲染的过程,他会确保条件在切换是销毁或者重新渲染。同时wx:if也是有惰性的,如果初始渲染条件为false,框架什么也不会做,只有在条件第一次变为真的时候才会开始渲染。因此,对于可见性需要频繁切换的时候,不建议使用条件渲染,因为它的开销会比较大,这个时候就使用 hidden
对于我们的这个例子,我们要如何选择呢? 当电影列表从服务器获得以后,用户就不会对是否推荐的信息进行修改。意思就是,强烈推荐这个元素的渲染条件,用户是不会去发生切换的。所以这个时候,我们应该使用 条件渲染来做 这件事情。这个时候如果使用 hidden,他的初始化开销会增大。博主没有去推荐这个电影,它也会在初始生成的时候,去生成这么一个元素。
列表渲染:重复的生成组件,类似编程里的 循环
这里需要使用swiper组件 。
weekly.wxml 里面的代码为:
<view class=''>
<swiper class='movie-swiper' indicator-dots='{{true}}'
previous-margin="50rpx" next-margin="50rpx"
>
<swiper-item class='movie' wx:for="{{weeklyMovieList}}">
<view class='container movie-card'>
<image class='movie-image' src='{{item.imagePath}}'>image>
<text>第{{index+1}}周:{{item.name}}text>
<text>点评:{{item.comment}}
text>
<text wx:if="{{item.isHighlyRecommended}}" style="font-size:16px; color:red;">强烈推荐text>
view>
swiper-item>
swiper>
view>
weekly.js 代码为:
Page({
data: {
weeklyMovieList: [
{
name: "泰坦尼克号",
comment: "失去的才是永恒的",
imagePath: "/images/titanic.jpg",
isHighlyRecommended: false,
},
{
name: "这个杀手不太冷",
comment: "小萝莉与怪蜀黍纯真灿烂的爱情故事",
imagePath: "/images/leon.jpg",
isHighlyRecommended: false,
},
{
name: "教父",
comment: "最精彩的剧本,最真实的黑帮电影。",
imagePath: "/images/jf.jpg",
isHighlyRecommended: true,
}
],
count: 123,
score: 61
}
})
weekly.wxss 样式文件如下:
.movie {
display: flex;
}
.movie-details {
display: flex;
flex-direction: column;
width: 550rpx;
}
.movie-image {
width: 200rpx;
height: 200rpx;
}
.movie-swiper {
height: 90vh;
}
.movie-card {
height: 100%;
width: 100%;
background: #eee;
margin: 0 20rpx;
}
这一节主要有四个任务:
对于 swiper ,要设置默认显示的那页幻灯片,可以添加一个 current 属性,这个属性默认值是0。也就是说,首张幻灯片页是默认显示的。现在要做的是,因为用户每次最想看到的是,本周推荐的页。所以,要把 swiper 默认设置成最后一页幻灯片。
最后一页的序号是 数组长度减1。解决方案是,把 current 属性这样绑定:
current = '{{weeklyMovieList.length-1}}'
同时,还给每页幻灯片上添加了一个按钮 “返回本周”,可以快速返回当前周的推荐电影。具体代码可以参考我提供代码。
在这里,展示了一种数据更新的方式,就是直接像下面函数的形式来进行更新:
f0: function(event){
this.data.count = this.data.count + 1
}
反复点击按钮,会发现页面显示并没有更新。但是进入 AppData 查看,可以发现 count 的值已经更新到了 9。
所以,这种通过对变量直接赋值写入的方式,不能让框架自动更新,而且,非常容易引起数据不一致问题。所以,对小程序中的变量进行更新,不能采取直接赋值的方式,而必须采用小程序提供的 this.setData 方法。
大家观察我提供的代码,可以发现实际的写法为:
f0: function (event) {
this.setData({
currentIndex: this.data.weeklyMovieList.length - 1
})
}
前面的应用里,我们已经使用了事件函数。这一节我们要讲解如下事项:
对于点击的操作有两种处理方式:
<button catchtap='f0'> +1 button>
<button bindtap='f0'> +1 button>
这两种有什么区别呢?首先我们要了解冒泡事件和非冒泡事件。小程序中事件分为冒泡事件和非冒泡事件:
对于 bindtap ,如果事件触发了,就会向上传递给父元素。也就是说,button 如果点击了,就会传递给外边的 view 元素,如果 view 元素里面自己也有一个 bindtap 函数,它也会调用这个函数进行处理。
相反,如果这里使用 catchtap ,那么点击以后,就会阻止元素向上传递。也就是说,按钮点击以后,只会处理自己的函数,但外层的 view 元素,就不会进行相应处理。
接着,我们看看事件对象包含哪些信息。我们把f0函数修改如下:
f0: function(event){
console.log(event)
}
点击以后,可以在 log 里看到如下事件触发后,方方面面的信息,具体如下:
比如说事件的类型,是一个 tap 事件,还有一个是 currentTarget ,指向的是当前事件处理的元素。可以观察到,currentTarget 还有一个 dataset 属性,这个还会在后边讲到。
本周实验课的内容就如上所示,希望大家能够有所收获。有任何问题,可以在页面底下评论,或者直接在微信,QQ里面联系我。