第5章
小程序开发实例
5.1准 备 工 作
小程序从结构上可以分成一个App和多个Page,这个App是对小程序整体或者全局信息的一个抽象封装; 每个Page负责相应页面信息的抽象封装。其中,app又可以分割成三个文件,分别描述小程序整体上的逻辑,以及全局配置和一些全局的公共样式表,如表5.1所示。
表5.1App的3个文件
文件名
是 否 必 需
说明
app.js
是
负责小程序逻辑
app.json
是
负责小程序公共配置
app.wxss
否
负责建立小程序公共样式表
每个Page对应的页面其相应代码又可以分成四个单独的文件(我们将要建立的页面名称命名为about),如表5.2所示。
表5.2一个小程序页面的4个文件
文件名
是 否 必 需
说明
js
是
负责小程序页面逻辑
wxml
是
负责小程序页面结构
json
否
负责进行小程序页面配置
wxss
否
负责建立小程序页面样式表
这4个文件的名字都与页面的名称相同。需要注意的一点是: 小程序包含的所有页面,每一个页面均要放在单独的目录中,所有这些目录又放在一个总的父目录中进行集中管理。
其中,JSON对象格式的文本,不能是空白,至少应是一个空的JSON对象。如图5.1所示为创建一个空的JSON对象。
app.json中还至少应配置一个属性,即pages属性,在该属性中通过一个字符串数组来配置这个小程序中用户可能访问到的每一个页面的路径,在我们即将建立的这个项目中,只需要配置唯一的about页面的访问路径,这个路径是Pages目录下about目录下的about页。每当新建一个页面时,都必须在app.json文件pages中将该新增页面的路径添加进去,如图5.2所示。
图5.1创建一个空的JSON对象
图5.2app.json文件中页面路径配置信息
另外,页面的ahout.js函数不能是空白,至少需要调用page函数给about页面注册一个它自己的页
图5.3注册空页面对象
面对象,可以注册一个空的页面对象,如图5.3所示。
新建的about页面上当前没有添加任何内容,此时需要在对应的结构文件即视图文件about.wxml中,添加一些组件元素来展示出对应的内容。类似于HTML,可以使用最简单的基本组件元素,如用text元素来表示一个Hello World的文本; 还可以通过style属性直接添加一些inline样式,如图5.4所示。
微信小程序开发快速入门微课视频版
第
5
章小程序开发实例
图5.4about.wxml视图文件中直接使用text组件
也可以将样式代码从视图文件中抽离出来,放在一个单独的样式表中,即WXSS文件。定义一个
样式规则,命名为info,如图5.5所示。
如果要在视图文件WXML中应用,通过class取值来指定,如图5.6所示。
图5.5about.wxss样式文件的定义
图5.6在about.wxml文件中引用WXSS文件中定义的样式
5.2小程序生命周期
微信小程序的生命周期有两个,一个是App的生命周期,另一个是Page的生命周期。小程序的生命周期函数在app.js中调用,App(Object)函数用来注册一个小程序,接受一个Object参数,指定小程序的生命周期回调等,一般有onLaunch监听小程序初始化、onShow监听小程序显示、onHide监听小程序隐藏等生命周期回调函数,如表5.3所示。
表5.3小程序生命周期Object参数说明
属性
类型
描述
触 发 时 机
onLaunch
function
生命周期回调——监听小程序初始化
小程序初始化完成时(全局只触发一次)
onShow
function
生命周期回调——监听小程序显示
小程序启动,或从后台进入前台显示时
onHide
function
生命周期回调——监听小程序隐藏
小程序从前台进入后台时
onError
function
错误监听函数
小程序发生脚本错误,或者API调用失败时触发
onPageNotFound
function
页面不存在监听函数
小程序要打开的页面不存在时触发
其他
any
开发者可以添加任意函数或数据到Object参数中,用this访问
页面的生命周期函数是指每当进入或切换到一个新的页面时将会调用到的生命周期函数,Page(Object)函数用来注册一个页面。接受一个Object类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等,如表5.4所示。
表5.4页面生命周期Object参数说明
属性
类型
描述
data
Object
页面的初始数据
onLoad
function
生命周期回调——监听页面加载
onShow
function
生命周期回调——监听页面显示
onReady
function
生命周期回调——监听页面初次渲染完成
onHide
function
生命周期回调——监听页面隐藏
onUnload
function
生命周期回调——监听页面卸载
onPullDownRefresh
function
监听用户下拉动作
onReachBottom
function
页面上拉触底事件的处理函数
onShareAppMessage
function
用户单击右上角转发
onPageScroll
function
页面滚动触发事件的处理函数
onTabItemTap
function
当前是Tab页时,单击Tab时触发
其他
any
开发者可以添加任意函数或数据到Object参数中,用this访问
1. onLoad(Object query)
页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数。
2. onShow()
页面显示/切入前台时触发。
3. onReady()
页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
4. onHide()
页面隐藏/切入后台时触发。如navigateTo或底部Tab切换到其他页面,小程序切入后台等。
5. onUnload()
页面卸载时触发。如redirectTo或navigateBack到其他页面时。
5.3页面配置初探
任务1: 给about页添加一个标题,并配置为白底黑字的“关于”。
实现方法: 在about.json文件中添加如下代码。
{
"navigationBarTitleText":"关于",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black"
}
第一个属性设置标题为“关于”,第二个属性配置导航栏的背景色为白色,第三个属性配置标题文本的样式为黑色。导航栏的背景色可以是任何颜色值,而标题文本的颜色只有white和black两种取值。
表5.5列出了页面的JSON文件中可以配置的页面属性信息,当前页面中配置的相关属性信息会覆盖 app.json 的 window 中相同的配置项。
表5.5JSON文件中可以配置的页面属性信息
属性名
类型
默认值
说明
navigationBarBackgroundColor
HexColor
#000000
导航栏背景颜色
navigationBarTextStyle
string
false
导航栏标题颜色,仅支持black/white
navigationBarTitleText
string
导航栏标题文字内容
navigationStyle
string
default
导航栏样式
backgroundColor
HexColor
#ffffff
窗口的背景色
backgroundTextStyle
string
dark
下拉loading的样式,仅支持dark/light
backgroundColorTop
string
#ffffff
顶部窗口的背景色,仅iOS支持
backgroundColorBottom
string
#ffffff
底部窗口的背景色,仅iOS支持
enablePullDownRefresh
boolean
false
是否开启当前页面下拉刷新
onReachBottomDistance
number
50
页面上拉触底事件触发时距页面底部距离,单位为px
pageOrientation
string
portrait
屏幕旋转设置,支持auto/portrait/landscape
disableScroll
boolean
false
设置为true则页面整体不能上下滚动
disableSwipeBack
boolean
false
禁止页面右滑手势返回
usingComponents
Object
否
页面自定义组件配置
任务2: 给about页添加页面结构和内容,通过对应的标记与元素来表达图片和文本。
前面章节提到过,WXML也是通过框架提供的基础组件来表达内容元素,其中有一些属性,是任何一个WXML组件都可以设置的,比如
Hello World
中的class属性、id属性、style属性等,以及通过bindtap这样的方式来给该组件元素触发的事件绑定一个处理函数,也可以设置hidden属性来控制该元素是否隐藏,还可以通过data这样的属性来设置一些组件自定义的数据,这些自定义数据将会在事件触发的时候封装在事件对象中,传递给对应的事件处理函数进行处理。
还可以通过image组件完成图片的加载功能:
可以加载一个网页上的图片,也可以是一个本地的图片,该图片文件需要放到小程序项目的文件结构中,添加一个images目录放置一些需要使用到的图片文件,代码如:
CELAVI餐厅和空中酒吧
Marina Bay Sands
小程序开发测试
注意: image组件也可以使用相对路径,以上代码运行后的界面如图5.7所示。
图5.7图片加载效果
从图5.7显示效果来看,页面在渲染时是尽可能挤在同一行去展示,其原因在于text元素默认是inline元素,image元素默认是inlineblock元素。实际使用中,经常也需要将这样的多个元素放置在一个容器中,以便对由多个元素构成的整体做一个总的样式控制。在HTML中,是通过div来实现这样的容器元素,在WXML中则可以通过view元素来作容器元素,实现一个view元素包含一个image元素和三个text元素,同时也将图片和文本信息呈现出来,代码如下。
CELAVI餐厅和空中酒吧
Marina Bay Sands
小程序开发测试
代码运行后的效果仍然如图5.7所示,各个组件元素的显示在布局上没有改观,如何设计布局样式来使组件元素的显示尽量美观呢?
5.4快速实现基本布局——应用弹性盒子布局
先从一个简单的布局需求开始,具体要求如下。
(1) 从上往下,分行输出,而不是全部挤在一行输出,每个元素独占一行自上而下放置。
(2) 素材间隔均匀分布。
(3) 水平居中。
先给view元素添加一个临时的背景色,如前所述,需要修改样式文件about.wxss,并在WXML文件中引用这些样式。在about.wxss中新建一个样式container,代码如下。
.container{
background-color: rgb(233, 227, 227);
}
小技巧: 在WXSS样式文件中,经常需要定义各种颜色,如这里的backgroundcolor,当需要选
图5.8颜色选择器的使用
择想要的颜色时,此时可以先任意输入一组值,以#开头,如#fff,然后移动鼠标至相应位置,此时就会出现颜色选择器,如图5.8所示。
用鼠标单击调色板相应位置拾取想要定义的颜色,此时backgroundcolor的颜色值会以rgb三个分量的值呈现,如图5.9所示。
在about.wxml引入wxss中定义的container样式:
…
此时的渲染效果如图5.10所示。
图5.9颜色选择器的rgb三分量显示
图5.10引入container样式后的渲染效果
观察这里的view元素,它默认的高度取决于4个子元素的高度之和。在实际的页面效果中,如果希望view元素的高度等同于页面可见区域的高度,可以通过将高度设置为100vh来实现,vh即viewport height,100vh相当于页面视口高度的百分之百,即相当于视口的高度。
5.4.1传统布局的实现方式
1. 需要实现从上往下的布局需求
如果在WXML文件中不引入view组件,也不改变text组件的显示方式,此时的代码为:
CELAVI餐厅和空中酒吧
Marina Bay Sands
小程序开发测试
该段代码渲染出的结果将同图5.7。那么如何实现文本从上往下显示的布局?如果将每个text元素从inline元素变成快捷元素,就可以实现每个元素独占一行从上往下放置。
这里先在about.wxss中添加一个样式text,代码为:
.text{
display:block;
}
然后在about.wxml文件中将该text样式在text组件中引入,代码如下。
CELAVI餐厅和空中酒吧
此时的实现效果如图5.11所示。
那么水平居中又该如何实现呢?
此时需要在text样式中增加一个textalign属性并将其值置为center,此时的显示效果如图5.12所示。
图5.11引入text样式实现文本内容的
分行显示
图5.12文本居中显示的textalign设置
2. 素材均匀分布
一种传统的实现方式为: 先计算页面可见区域的总高度减去每个元素实际占的高度,得出在垂直方向上剩余的空间高度,将剩下的空间高度均匀地分配给每个元素的垂直外边距,通过设置每个
子元素的下外边距来实现,这里暂且假定为90rpx,
图5.13图片及文字垂直方向
均匀分布的实现效果
具体页边距的设置可以根据实际显示效果做动态调整。
about.wxss中的相应代码为:
image,text{
margin-bottom:90rpx;
}
实现效果如图5.13所示。
传统方式的特点如下。
(1) 布局目标可根据不同元素位置需要分别进行不同的属性赋值,有的属性需要赋值在一些子元素上,如image、text,有的属性需要赋值在每一个子元素上,如text等。
(2) 传统布局依赖于页面结构与实际内容大小,当页面结构发生变化时,布局相关属性取值都将重新调整,从而不能足够灵活应对页面结构的变化。如本例中,如果加入的图片高度变大,每个元素向外边距的值则需要重新计算; 如果视图中包含不止三个text元素,marginbottom也将重新计算,间距会相应减少。
5.4.2弹性盒子布局
首先将拟使用弹性盒子布局的所有元素包括image和text均嵌套在一个view组件中,将其变成一个flex container,方法是将该容器的display取值改为flex。about.wxss中的代码为:
.container{
background-color: rgb(233, 227, 227);
height:100vh;
display:flex;
}
在about.wxml中引入WXSS中定义的container样式:
CELAVI餐厅和空中酒吧
Marina Bay Sands
小程序开发测试
模拟器中渲染出的效果如图5.14所示。
如在container样式中增加一行代码:
flex-direction: column;
显示的效果同图5.14,说明flexdirection默认主轴方向为从上往下。如果将flexdirection的值置为row,则显示的效果如图5.15所示,此时的主轴方向为从左到右。
图5.14引入flex弹性盒子布局的
显示效果
图5.15flexdirection置为row时的
显示效果
如果希望在垂直方向上各个元素的前后间隔均等,并且第一个元素和最后一个元素距离这个容器的边缘也都有一个相应的间隔,可通过置属性justifycontentspacearound为spacearound来实现。属性alignitems可用来控制元素的对齐方式,当flexdirection主轴方向定义为row时,alignitems分别取值flexstart、flexend、baseline的显示效果如图5.16所示。
图5.16alignitems取不同值时的显示效果
5.4.3弹性盒子布局的优点
弹性盒子布局作为一种新的布局方式,实现的是一种整体布局,不仅直观高效,而且能够灵活地应对页面结构的变化,这种方式具有如下优点。
(1) WXSS属性赋值相对统一,可在容器元素上做统一控制,容器内的所有组件元素都将具有统一赋值的显示属性。
(2) 布局方式灵活,可以应对页面结构的一些变化。如在页面结构中新增两个text元素,依然可以和原来的元素在垂直方向上保持均匀分布,从而实现一种整体控制,并没有去具体计算每个元素之间的间隔大小。
5.5如何让元素大小适配不同宽度屏幕
本章中用到的图片在渲染时均未重置其显示尺寸,则保持为image元素默认的大小,即宽度为320px,高度为240px,如果希望宽高均为屏幕宽度的一半或其他尺寸,该如何实现呢?
当选择模拟器中的显示终端设备为iPhone 5时,其屏幕显示尺寸为320×568,因此可将显示图片的宽、高均设置为160。首先在about.wxss中新增一样式为:
.bar{
width:160px;
height: 160px;
}
在about.wxml文件中,修改image元素的class属性:
此时的显示效果如图5.17所示。
以上图片显示尺寸的设定是在选定显示设备型号的前提下,直接在WXSS文件中设置图片的宽高显示像素,如果想要实现图片的宽高始终保持为屏幕宽度的一半,这个时候显示像素这个绝对单位就不适用了,需要引入一个新的长度单位,它必须是一个相对于屏幕宽度大小的一个相对长度单位,小程序中称这个相对长度单位为rpx。无论当前是哪种设备,它都统一规定这个设备上的屏幕宽度为750rpx。如要实现图片的宽高均为屏幕宽度的一半,采用的相对单位就是375rpx,如图5.18所示。
图5.17设置显示图片的尺寸大小
图5.18指定图文显示效果的布局样式
最后做一个简单的优化,把这个图片设计为一个圆形的图片,相应的文本可以实现加粗字体和字号变大的效果。
(1) 在about.wxss中增加图片的圆形设计。
将显示图片用到的组件image将要使用的样式bar修改为:
.bar{
width:375rpx;
height: 375rpx;
border-radius: 50%
}
WXML文件中view组件将要使用到的样式container的完整代码为:
图5.19圆形图片及指定显示样式的文本效果
.container{
background-color: rgb(233, 227, 227);
height:100vh;
display:flex;
flex-direction: column;
justify-content: space-around;
align-items:center
}
(2) 在about.wxml中增加文本的控制。
CELAVI餐厅和空中酒吧
Marina Bay Sands
小程序开发测试
显示效果如图5.19所示。
5.6新增“优惠推荐”promotion页并快速调试
首先,需要添加promotion页对应的目录和文件(脚本文件JS、配置文件JSON、视图文件WXML、样式表文件WXSS),在promotion.json文件中配置一个空的JSON对象; 然后在脚本文件.js中调用Page函数,给页面注册一个空对象{}; 再给WXML添加“优惠推荐”所对应的WXML代码。
5.6.1使用navigator组件——从about页跳转到promotion页
添加导航链接最简单的方式是使用navigator组件,主要是两个属性的使用,分别是opentype属性和hoveclass属性。navigator组件的属性在表5.6中予以列出。
表5.6navigator组件的属性信息
属性名
类型
默认值
说明
target
string
self
在哪个目标上发生跳转,默认当前小程序,可选值: self/,miniProgram
url
string
当前小程序内的跳转链接
opentype
string
navigate
跳转方式
delta
number
当opentype为navigateBack时有效,表示回退的层数
appid
string
当target=“miniProgram”时有效,要打开的小程序appId
path
string
当target=“miniProgram”时有效,打开页面路径,如果为空则打开首页
extradata
object
当target=“miniProgram”时有效,需要传递给目标小程序的数据,目标小程序可在App.onLaunch(),App.onShow()中获取到这份数据
version
version
release
当target=“miniProgram”时有效,要打开的小程序版本,有效值: develop(开发版),trial(体验版),release(正式版)。仅在当前小程序为开发版或体验版时此参数有效; 如果当前小程序是正式版,则打开的小程序必定是正式版
hoverclass
string
navigatorhover
指定单击时的样式类,当hoverclass=“none”时,没有单击态效果
hoverstoppropagation
boolean
false
指定是否阻止本节点的祖先节点出现单击态
hoverstarttime
number
50
按住后多久出现单击态,单位: ms
hoverstaytime
number
600
手指松开后单击态保留时间,单位: ms
bindsuccess
string
当target=“miniProgram”时有效,跳转小程序成功
bindfail
string
当target=“miniProgram”时有效,跳转小程序失败
bindcomplete
string
当target=“miniProgram”时有效,跳转小程序完成
首先在app.json中添加promotion页面的路径“pages/promotion/promotion”。小程序的初始页面为about,在about页上加一个导航链接,让它指向promotion页。
about.wxml中的完整代码为:
CELAVI餐厅和空中酒吧
Marina Bay Sands
Contact Us:+65 65082188;商家优惠促销
如果只是对“商家优惠促销”中的“优惠促销”,此时需要将文本进行拆分,分别为“商家”与“优惠促销”。
Contact Us:+65 65082188;商家
优惠促销
可以发现,此时的显示效果却是分成两行显示,如图5.20所示。
图5.20增加导航链接后分行显示效果
出现这种结果的原因,是因为和都是并列的关系,都是作为元素的子元素,从上往下来进行放置。为了把它们仍然放在同一行,采取以下解决方案。
第1步: navigator默认是块级元素,需要将其变成inline元素,增加style='display:inline'属性值。
第2步: 需要将一个元素和一个元素封装在一个元素中。
经实测,两个步骤缺一不可,相应代码为:
Contact Us:+65 65082188;商家
优惠促销
通过元素的封装,仍然可以在同一行中显示为完整的一段话。只有对元素中的“优惠促销”进行单击,才会从当前页面跳转到目标页。跳转页的左上角有一个“返回”按钮,可以返回到about页面。如果希望实现不能返回,则需要用到navigator元素的opentype属性。
置opentype='redirect'(其默认值为navigate,即返回跳转前的页面)。
第3步: 如何给元素添加一个单击态的样式效果?
实现单击态的样式效果需要用到hoveclass属性,首先给元素的hoveclass赋值一个样式类:
hover-class='nav-hover'
然后在页面对应的样式表中去定义样式规则。如果希望导航链接被单击的时候,将它字体的颜色变为红色,在about.wxss中添加:
.nav-hover{
color: red;
}
5.6.2配置tabBar——对若干一级页面的入口链接
如果小程序是一个多Tab应用,可通过配置顶部或底部导航栏来实现,基本上任何一个完整的小程序都会存在一个导航栏,小程序上官方文档要求tabBar中的item最少为两个,最多为五个。tabBar标签栏配置可用于实现不同一级页面之间的快速任意切换,其本质实际上就是对若干一级页面的入口链接。
首先需要为每一个Tab准备两个icon图,分别对应这个Tab默认的时候使用的icon图和它被选中的时候使用的icon图(icon图标可在网站http://tool.58pic.com/tubiaobao/index.php里找到)。
在app.json文件中添加如下代码。
"tabBar": {
"list": [
{
"text": "关于",
"pagePath": "pages/about/about",
"iconPath": "images/icons/about.png",
"selectedIconPath": "images/icons/selectedAbout.png",
},
{
"text": "优惠促销",
"pagePath": "pages/promotion/promotion",
"iconPath": "images/icons/promotion.png",
"selectedIconPath": "images/icons/selectedPromotion.png"
},
{
"text": "联系我们",
"pagePath": "pages/about/about",
"iconPath": "images/icons/helpCenter.png",
"selectedIconPath": "images/icons/selectedHelpCenter.png"
}
]
}
上述代码中的“iconPath”“selectedIconPath”分别表示默认的icon图和选中之后的icon图,
图5.21tabBar的运行效果
编译后的运行效果如图5.21所示。
但是当单击“优惠促销”文本时,navigator元素跳转界面却没反应了,这是为什么呢?在app.json中,通过list的第一个对象,将about页设置成了第一个Tab所链接到的页面,在about页面中单击navigator元素,此时不仅底部标签栏也做一个切换,切换到promotion页所对应的第2个Tab,此时navigator元素中opentype的取值就不能是默认的navigate,而应该是一个特殊的取值叫switchTab。opentype的有效取值如表5.7所示。
表5.7opentype的有效取值
属性名
说明
navigate
保留当前页面,跳转到应用内的某个页面
redirect
关闭当前页面,跳转到应用内的某个页面
reLaunch
关闭所有页面,打开到应用内的某个页面
switchTab
跳转到tabBar页面,并关闭其他所有非tabBar页面
图5.22跳转页面显示效果
单击之后,实际上包含两个操作,第一个操作就是页面的跳转,第二个操作是底部tabBar的切换,此时跳转之后的显示界面如图5.22所示。跳转界面promotion的WXML与WXSS的代码分别为:
优惠推荐
Singapore Sling Bombe-Alask
及
.dessert{
width:375rpx;
height: 281rpx;
}
.pContainer{
background-color: rgb(233, 227, 227);
height:100vh;
display:flex;
flex-direction: column;
align-items:center
}
5.6.3数据绑定——从视图中抽离出数据
前面讲到的about页和promotion页,在两个页面的WXML代码中,相关的数据都是进行直接硬编码的,也就是数据直接在页面代码中进行赋值,硬编码数据适合于静态的数据。但实际应用中,更多的则是一些动态数据,如promotion页中的优惠推荐,在不同的时间节点,优惠商品是会发生变化的,如果采取硬编码方式,则需要频繁修改WXML代码,重新打包上传发布。甚至有些数据在编码的时候是无法获知的,需要在小程序运行过程中,动态地从服务器端去获取,然后再渲染输出到这个视图中进行显示。因此,应该提供一种机制能够让这个视图中的每一个部分与对应的数据做一个映射以便获取动态数据。
在小程序框架中,每个页面所需要的各种数据,都是集中在这个页面所注册的页面对象中集中定义的。每个页面都是在其对应的脚本文件中通过调用Page函数来给这个页面注册它所需要的页面对象。在页面对象中,通过data属性来定义该页面所需要的各种数据。
为了演示动态数据获取效果,先在promotion.js中添加如下代码。
Page({
data:{
promotionRecommend:{
name:"Frozen Mochi",
imagePath:"/images/Celavi-Frozen-Mochi.jpg"
},
count:123
}
})
代码中的promotionRecommend数据代表了当前页面的一个内部状态,相当于该页面的内部状态变量。也可以再增加一个内部状态变量(数据)count,记录当前该推荐商品可供售出的份数。
接下来的问题是,数据定义后,如何将其绑定输出到视图中去?要把count的值渲染输出到视图页面上显示,需要通过一个双大括号进行数据绑定。在promotion.wxml中,可以将count变量绑定在text的内容上,如此商品还剩{{count}}份可以售出,此为最简单的一种数据绑定形式。
还有一些复杂的数据绑定,可以对内部状态变量进行一些运算或者进行一些组合来输出显示。比如在promotion.js的data中再增加一个评分的内部状态数据score,然后在promotion.wxml视图文件中渲染输出。
该商品用户评价打分: {{(score>=60)?"及格":"不及格"}}
下面再来看下promotion.js中的数据promotionRecommend如何在视图文件中渲染输出,代码如下。
{{promotionRecommend.name}}
提示: 在开发者工具的调试器AppData中,可以很方便地对每个页面所包含的内部状态数据进行查看和调试,不仅可以查看这些内部状态变量,还可以修改它们,修改后在视图上就会有自动更新的效果,如图5.23所示。
图5.23在AppData中查看内部变量数据
5.6.4条件渲染
条件渲染指条件成立时组件才渲染生成,本节主要介绍以下两个方面的内容。
(1) wx:if属性的使用。
(2) 条件渲染wx:if与使用hidden属性的区别。
先在promotion.js中添加一个表示是否强烈推荐的内部状态变量isHighlyRecommended,对于那些客户真正强烈推荐的商品,显示一个强烈推荐的红色标记。
然后在promotion.wxml文件中,定义“强烈推荐”的text:
强烈推荐
现在的需求是: 根据实际返回的数据中的isHighlyRecommended是否为true来决定它是否要渲染生成。
此时可通过wx:if属性做数据绑定来实现。
强烈推荐
这段代码表示,该text元素渲染生成的条件绑定到promotionRecommend.isHighlyRecommended属性上,根据该属性的值来决定是否渲染。
那么使用条件渲染与hidden属性有何区别?如以下代码使用的是hidden属性:
强烈推荐
从语义上讲,使用hidden属性时,该元素总是要被渲染生成的(可从调试器的WXML标签观察生成的节点树),Hidden属性只是控制了其可见性而已,如图5.24所示。
图5.24WXML标签节点树
5.6.5列表渲染
假如推荐列表中有多款商品,如何实现将对象数组中的多个商品对象渲染输出到视图页面中?
首先在promotion.js中将原来的promotionRecommend单个商品修改为列表商品的数组promotionRecommendList,然后补充以下一些商品信息,代码如下。
Page({
data:{
promotionRecommendList:[
{
name:"Frozen Mochi",
price:"¥59.9元起",
imagePath:"/images/Celavi-Frozen-Mochi.jpg",
isHighlyRecommended:true
},
{
name:"Hokkaido Scallop & Oyster Ceviche",
price:"¥199.9元起",
imagePath: "/images/Celavi-Hokkaido Scallop & Oyster Ceviche.jpg",
},
{
name: "Rose&Watermelon Petit Gateau",
price: "¥169.9元起",
imagePath: "/images/celavi-Rose&Watermelon Petit Gateau.jpg",
},
]
}
})
第一种是最简单、实现起来最直接的方法,就是将promotionRecommendList商品列表中的数据一个个依次输出,promotion.wxml中的相应代码为:
{{promotionRecommendList[0].name}}
{{promotionRecommendList[0].price}}
强烈推荐
{{promotionRecommendList[1].name}}
{{promotionRecommendList[1].price}}
强烈推荐
{{promotionRecommendList[2].name}}
{{promotionRecommendList[2].price}}
强烈推荐
以上代码运行渲染出的效果如图5.25所示。
图5.25商品列表依次输出的效果
在实际开发中,这种输出方式不够灵活,因为在页面初始加载的时候,可能还不知道server端会返回多少个列表对象,WXML中不知道要重复编写多少个这样的view结构代码,或者商品列表的个数会发生动态变化,此时这种依次输出的方式就不再适用,而需要采用程序设计中的循环控制思想,在视图中添加可以循环的控制结构,让框架可以自动对组件进行重复的渲染生成。
实现方法: 将view元素的wx: for属性绑定到对应的数组上,此时view元素针对绑定数组中的每一个值都将重复地渲染生成一次。在view元素的结构代码中,通过内置的item循环控制变量来直接访问到本次所遍历到的数组中的值是哪一个值。
{{item.name}}
{{item.price}}
强烈推荐
上述代码中,将view元素的wx:for属性与对象数组promotionRecommendList进行绑定,通过item内置循环控制变量来对对象数组中的每一个元素进行遍历访问。可以发现,通过引入wx:for列表循环渲染,代码更加简洁,结构更为清晰。
图5.26改造后的列表渲染显示效果
如果要在视图中展示遍历到的促销商品的序号,则需要使用另一个循环控制变量index(该变量在promotion.js数据data中定义,并初始化为0),即当前所遍历到的item值在数组中的下标,代码如下。
第{{index+1}}款: {{item.name}}
图5.25中的图片文字排版布局有点儿乱,需要在此基础上做相应调整,使得图片文字分开居中显示,输出效果如图5.26所示。
原有promotion.wxml中的代码调整如下。
第{{index+1}}款: {{item.name}}
价格: {{item.price}}
强烈推荐
promotion.wxss中的样式调整如下。
.dessert{
display:flex;
flex-direction: row;
justify-content: center;
flex-wrap:wrap
}
.image{
width:350rpx;
height:210rpx;
}
.promotion-details{
display:flex;
justify-content: center;
align-items: center
}
其中,dessert样式用于实现对象数组中促销商品图片的居中显示; image样式用于重新定义渲染显示的图片尺寸大小; promotiondetails样式用于实现每一个商品详细文字信息的居中显示。
5.7数 据 更 新
逻辑层数据的更新操作需要在视图层能正确显示,微信小程序提供this.setData函数用于实现这一功能。为介绍该函数的用法,我们可以编写一些小的测试代码来进行讲解。
首先在编辑器目录树pages中新建一个目录,命名为helpCenter,然后依次新建helpCenter.js、helpCenter.json、helpCenter.wxml、helpCenter.wxss四个文件,并在app.json文件中的pages对象数组中添加“pages/helpCenter/helpCenter”页面信息。同时还需将app.json文件中tabBar的list中“联系我们”关联的pagePath修改为我们新增的页面,相应代码为:
{
"text": "联系我们",
"pagePath": "pages/helpCenter/helpCenter",
"iconPath": "images/icons/helpCenter.png",
"selectedIconPath": "images/icons/selectedHelpCenter.png"
}
此时,当单击“联系我们”时,除了按钮图像会发生变化外,只有一个空白页面,需要往此空白页面中添加相应内容,以此来介绍逻辑层与视图层数据同步更新如何实现。
首先在helpCenter.wxml中新增一个text元素,它的内容绑定到内部状态变量accessCount上,该变量在helpCenter.js中data域进行定义,并初始化为0,用于记录该页面被访问到的次数。
该服务中心页面被访问{{accessCount}}次
其中,button按钮用于对accessCount变量的取值进行显示输出。单击Show Access Count这个button按钮的代码在helpCenter.js中添加,如下。
f0:function(event){
console.log(this.data.accessCount)
}
此处是用于读取内部状态变量accessCount的值,运行效果如图5.27所示。
图5.27数据更新测试页面1
以上代码是读取data中的accessCount变量的值并将其结果在helpCenter.wxml页面中渲染出来,如果是写accessCount变量该如何实现呢?比如现在要将accessCount变量的值递增1,在helpCenter.js中将触发函数f0的代码修改为:
f0:function(event){
this.data. accessCount= this.data. accessCount+ 1
}
分析图5.28可以发现,在AppDaa中,accessCount的值随着单击次数的增加,该内部状态变量的值也发生了改变,但是视图层WXML界面的accessCount的值仍为初始化时定义的值0,这是怎么回事呢?视图层的数据并没有同步更新为逻辑层的内部状态变量的值。
因此,这种试图通过对内部状态变量的直接赋值来实现变量值写入的方式是不能让这个框架自动更新到对应的视图部分的。同时还存在一个问题,就是当单击Show Access Count按钮后,AppData标签并不能实时看到accessCount值的更新,此时需要单击一下其他的标签再回到AppData标签,才会看到accessCount被更新后的值。
正确的实现内部状态变量赋值并自动渲染到视图层的方式是使用小程序提供的this.setData()方法。
图5.28数据更新测试页面2
将helpCenter.js中的相应代码修改为:
f0:function(event){
this.setData({
accessCount: this.data. accessCount + 1
})
}
重新编译运行,显示结果如图5.29所示。
图5.29数据更新测试页面3
可以发现,不管是在渲染页面helpCenter.wxml中的文字部分,还是在AppData中,内部状态数据的改变均实现了同步更新。
通过this.setData()函数的调用,就是告诉框架,希望它来完成accessCount状态数据的更新,并且在更新完成之后,要让框架来通知这个视图层,对它所绑定到的这个内部状态变量的相关部分进行视图的更新。
this.setData方法不仅可以更新一个已有的内部状态变量的取值,而且可以根据需要动态地来新增一个内部状态变量,也可以实现对内部状态数据中的某一小部分数据进行局部的更新。如在helpCenter.wxml中新增一个按钮“获取电子邮件信息”,当被单击时显示联系方式中的电子邮件,在该文件中添加以下代码。
电子邮件: {{email}}
然后在helpCenter.js中添加“获取电子邮件信息”按钮的触发函数代码。
f1: function (event) {
this.setData({
email: "celavi@gmail.com",
})
}
上述代码this.setData函数中新增一个内部状态变量email,该变量未在data中进行定义,通过this.setData函数定义并赋值。增加这两段代码运行后的初始界面及结果界面分别如图5.30和图5.31所示。
图5.30数据更新测试页面4
图5.31数据更新测试页面5
5.8页面间跳转的实现机制
除了前面介绍的使用标签跳转之外,小程序还提供JS事件跳转方式。
1. wx.navigateTo(Object)
API中的导航wx.navigateTo(Object)功能,是保留当前页面,跳转到应用内的某个页面,但是不能跳到tabBar页面。使用wx.navigateBack即可以返回到原页面,小程序页面栈最多10层。其参数Object说明如表5.8所示。
表5.8wx.navigateTo(Object)参数说明
参数
类型
必填
说明
url
string
是
需要跳转的应用内非tabBar的页面路径,路径后可以带参数。参数与路径之间使用“?”分隔,参数键与参数值用“=”相连,不同参数用“&”分隔,如'path?key=value&key2=value2'
events
object
否
页面间通信接口,用于监听被打开页面发送到当前页面的数据
success
function
否
接口调用成功的回调函数
fail
function
否
接口调用失败的回调函数
complete
function
否
接口调用结束的回调函数(调用成功、失败都会执行)
2. wx.redirect(Object)
关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到tabBar页面,其参数说明如表5.9所示。
表5.9wx.redirectTo(Object)参数说明
参数
类型
必填
说明
url
string
是
需要跳转的应用内非tabBar的页面路径,路径后可以带参数。参数与路径之间使用“?”分隔,参数键与参数值用“=”相连,不同参数用“&”分隔,如'path?key=value&key2=value2'
success
function
否
接口调用成功的回调函数
fail
function
否
接口调用失败的回调函数
complete
function
否
接口调用结束的回调函数(调用成功、失败都会执行)
3. wx.switchTab(Object)
跳转到tabBar页面,并关闭其他所有非tabBar页面,其参数说明如表5.10所示。
表5.10wx.switchTab(Object)参数说明
参数
类型
必填
说明
url
string
是
需要跳转的tabBar页面的路径(需在app.json的tabBar字段定义的页面),路径后不能带参数
success
function
否
接口调用成功的回调函数
fail
function
否
接口调用失败的回调函数
complete
function
否
接口调用结束的回调函数(调用成功、失败都会执行)
我们在promotion.wxml中修改得到如下代码。
第{{index+1}}款: {{item.name}}
价格: {{item.price}}
强烈推荐
上述代码中的粗体字部分bindtap=“f1”即为添加代码,即当单击该view组件时,触发函数f1,也即相当于单击image组件中的图片时,触发f1。而函数f1在promotion.js中定义:
f1:function(event){
wx.navigateTo({
url:"/pages/details/details"
})
}
跳转目标details为新建的一个界面,其details.wxml代码为:
这是一个显示促销商品详细信息的页面
其中,text组件的触发函数f2,用于当用户单击到该文本时跳转回原来界面。f2函数在details.js中定义。
Page({
f2:function(event){
wx.navigateBack({
})
}
})
对上述代码简单分析后不难发现,不管单击对象数组中的哪一个image,跳转页面的显示结果都一样。也即当用户单击图5.32中任何一幅图片,跳转的页面都是图5.33的显示效果。
图5.32列表渲染显示结果
图5.33跳转页面显示效果
如果想要实现这种跳转效果,即在单击某个特定促销商品图片的时候,弹出与该商品相对应的详细信息,而不是同一个页面,该如何实现?
首先需要在promotion.js中对promotionRecommendList中的每一个对象新增一个id,分别对应每一个商品的唯一id标识符,例如:
promotionRecommendList:[
{
name:"Frozen Mochi",
price:"¥59.9元起",
imagePath:"/images/Celavi-Frozen-Mochi.jpg",
isHighlyRecommended:true,
id:10
},
{
name:"Hokkaido Scallop&Oyster Ceviche",
price:"¥199.9元起",
imagePath: "/images/Celavi-Hokkaido Scallop & Oyster Ceviche.jpg",
isHighlyRecommended: false,
id: 11
},
{
name: "Rose&Watermelon Petit Gateau",
price: "¥169.9元起",
imagePath: "/images/celavi-Rose&Watermelon Petit Gateau.jpg",
isHighlyRecommended: true,
id: 12
},
]
可以在视图页面中通过代码:
{{item.id}}
…
将该id展示出来。
接下来的任务是,如何在每一个view元素被单击的时候,将该商品的id值传递给对应的事件处理函数来处理?
实现方法: 在view元素上定义一个对应的自定义数据属性(data的方式)来记录该促销商品的id:
然后在promotion.js中将f1的代码修改为:
f1:function(event){
var promotionId = event.currentTarget.dataset.promotionId
wx.navigateTo({
url: '/pages/details/details?id=' + promotionId,
})
}
注意:
(1) 上述代码中的datapromotionid为微信小程序特有的自定义属性设置,其语法为以data开头。
(2) 如果写成datapromotionid=“{{item.id}}”,或为dataPROMOTIONID=“{{item.id}}”,在获取值时都为:
event.currentTarget.dataset.postid
即不管data后跟字符大小写,都将转换为小写。
(3) 如果写成datanameid这种形式,在获取值的时候会自动去掉连字符,以驼峰方式去获取,变为nameId,这就解释了上述代码中变量获取为什么是promotionId,而不是其他形式。经实测,如果将
var promotionId = event.currentTarget.dataset.promotionId
中的event.currentTarget.dataset.promotionId改为event.currentTarget.dataset.promotionid,控制台会输出undefined错误信息。写成其他形式均会报错。
(4) 上述代码中的currentTarget指当前单击的对象,dataset是指自定义属性的集合。
以上代码运行后,当用户单击不同view组件中的图片对象时,控制台会输出相应的id值,如图5.34所示。
图5.34控制台输出不同view标签的id信息
上述代码中的url: '/pages/details/details?id=' + promotionId用于获取完整的跳转URL,下面深入探讨完整URL的获取方式。
当details页被打开的时候,需要有一种机制,能知道在对应的完整的URL中被指定的id参数是多少。小程序框架在每次以这样完整URL的方式打开details页时,会首先调用details页注册的onload生命周期函数来对details页进行初始化,将“?”后面的querystring中的每个参数值进行解析,组合成一个对应的JavaScript对象,将该对象作为实参值传递给onload方法。
下面在details.js页面中添加如下代码。
Page({
onLoad:function(options){
console.log(options.id)
},
假如用户单击的是promotion.wxml中的第一个view标签,也就是id=10的促销商品,此时绑定事件触发函数f1,其完整代码为,
f1:function(event){
var promotionId = event.currentTarget.dataset.promotionId
wx.navigateTo({
url: '/pages/details/details?id=' + promotionId,
})
}
触发函数f1先通过事件对象event获取到当前元素,也就是当前图片(id=10)自定义的这个数据属性promotionId的取值(10),以附带id为10的这样一个完整的URL来打开details页。
注意:
这里梳理一下promotion页面与details页面关于promotionId变量的访问问题。promotion与details是同一个小程序项目的两个不同页面,promotion页面通过以下代码:
在view组件中绑定了一个f1函数,该f1函数在promotion.js中定义,用于相应view组件单击时触发的事件,通过以下代码:
wx.navigateTo({
url: '/pages/details/details?id=' + promotionId,
})
将两个页面关联起来,也即promotion页面通过url: '/pages/details/details?id=' + promotionId语句指定其跳转页面为details,details是promotion的跳转目标页面。
小程序框架以这样一个完整的URL来打开details页,首先会将其中包含的id=10的querystring解析成一个options参数对象,然后调用details页,在对应的Page Object中定义Onload生命周期函数,并且给这个Onload生命周期函数传入一个刚才解析出来的Options参数对象。Onload函数在执行的过程中会将options中的id参数的取值打印出来。
Page({
onLoad:function(options){
console.log(options.id)
}
})
这样一来,details页在初始化的时候就能够获得它本次被打开时被指定的promotionId的值,也就是说,通过这种机制,details页面获取了一个不在该页面中定义的外部变量的值。当然也可以在details.js文件中定义一个该文件本身的内部状态数据变量pid,用于存放promotion页面传递过来的promotionId的值。
//details.js
Page({
data:{
pid:0
},
…
})
然后在details页面的onLoad函数中,采用this.setData函数对该页面的内部变量pid进行赋值。
//details.js
onLoad:function(options){
//console.log(options.id)
this.setData({
pid:options.id
})
},
可以发现,URL中获取到的参数promotionId=11成功保存到了pid内部变量中,如图5.35所示。
图5.35跳转目标页面details获取promotion页面传递过来的变量值
该promotionId也可以在之后的details页的视图渲染中,以数据绑定的方式进行渲染输出,如图5.36所示。
图5.36details页面接收到promotion页面传递过来的变量并渲染显示
思考题
1. 小程序生命周期函数有哪些?触发的条件分别是什么?
2. 如果要实现图5.18中图片在左边显示,文字在右边显示的效果,如何定义WXSS样式文件?在WXML文件中如何布局?
3. 本章中关于页面信息的配置都是静态配置,如果页面数据需要在运行时动态地从服务器上获取,如何在小程序中动态地设置页面的标题?
4. 思考规划设计的小程序应用服务项目需要定义几个页面?页面之间如何实现跳转链接?需要用到几个tabBar?并在小程序开发工具中予以实现。