赞
踩
小程序包含一个描述整体程序的主体部分和多个小程序页面。一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
文件 | 作用 |
---|---|
app.js | 小程序的入口文件 |
app.json | 小程序公共配置 |
app.wxss | 小程序公共样式表 |
表 2-1 小程序主体文件
一个小程序页面由四个文件组成,分别是:
<文件 | 作用> |
---|---|
js | 页面逻辑文件 |
wxml | 页面描述文件,用来设计页面布局,进行数据绑定等。 |
json | 页面配置文件 |
wxss | 页面样式表文件 |
表 2-2 小程序页面文件
图2-1是一个简单的小程序目录结构的示例:
图 2-1 小程序项目结构
小程序根目录下的 app.json 文件用来对微信小程序进行全局配置。包括:配置页面文件的路径、窗口表现、设置网络超时时间、设置多tab等。小程序使用json数据格式进行全局配置以及页面配置,下面的代码是一个小程序进行全局配置的例子,在这个例子中设置了两个页面:
{
"pages":[
"pages/index/index",
"pages/user/info"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
一些常用的配置项:
1)entryPagePath
指定小程序的默认启动路径(初始页面),如果不填则会把 pages 列表的第一项作为默认启动路径。启动路径不支持带路径参数。
{
"entryPagePath": "pages/index/index"
}
2)pages
用于指定小程序由哪些页面组成,每一项都对应一个页面的路径(含文件名)信息。文件名不需要写文件后缀,框架会自动去寻找对应位置的 .json, .js, .wxml, .wxss 四个文件进行处理。未指定 entryPagePath 时,数组的第一项代表小程序的初始页面。小程序中新增/减少页面,都需要对pages数组进行修改。例如对于图2-1中的目录结构,在app.json的相应定义如下:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
]
}
3)tabBar
如果小程序是一个多tab应用(客户端窗口的底部或顶部有tab栏可以切换页面),可以通过tabBar配置项指定tab栏的表现,以及tab切换时显示的对应页面。在配置tabBar数组时,只能配置最少2个、最多5个tab,tab按数组的顺序排序。例如:
{
"tabBar": {
"list": [{
"pagePath": "pages/home/home",
"text": "首页"
},
{
"pagePath": "pages/self/self",
"text": "个人"
}]
}
}
微信客户端打开小程序之前,会把整个小程序的代码包下载到本地。小程序启动之后,会创建一个 App( 在app.js中 定义) 实例,并且会执行App的 onLaunch方法。整个小程序只有一个App实例,是全部页面共享的。进入小程序之后,用户可以点击右上角的关闭,或者按手机设备的Home键离开小程序,此时小程序并没有被直接销毁,而是进入了小程序的后台状态。小程序进入后台状态时App中定义的onHide方法会被调用。当再次回到微信或者再次打开小程序时,微信客户端会把“后台”的小程序唤醒,此时小程序进入前台状态,App中定义的onShow方法会被调用。小程序的生命周期图的示意图如下所示:
图 2-2 小程序的生命周期
小程序的注册以及生命周期监听函数在app.js中定义。小程序必须在 app.js 中注册App对象,且不能注册多个。下面的代码是App对象的一个简单的实现:
App({ onLaunch: function () { wx.login({ success: res => { //发送 res.code 到后台换取 openId, sessionKey } }) }, onShow: function () { }, onHide: function () { }, globalData: { userInfo: null, } })
onLaunch 小程序初始化
当小程序初始化完成时,会触发onlaunch(全局只触发一次),onlaunch在手机后台到前台切换是不会被执行的。如果要再次执行,除非在手机中关闭(关闭不是切换到后台,而是直接删除小程序的后台运行)小程序,然后重新打开,才能执行。
onShow 监听小程序显示
当小程序启动,或者是从后台进入到前台的时候,会执行onshow。
onHide 监听小程序隐藏
小程序从前台进入到后台的时候,会执行onHide。
globalData 全局数据
App中的数据和函数都是全局的,我们可以在App中设置一个全局的对象,让所有的页面都可以使用。调用方式为:在Page中通过getApp()方法得到App对象,然后通过App对象来访问全局数据和调用全局函数。
一个小程序的页面由三部分组成:界面、配置和逻辑。界面由WXML文件和WXSS文件来负责描述,配置由JSON文件进行描述,页面逻辑则是由JS文件负责。一个页面的文件需要放置在同一个目录下,其中WXML文件和JS文件是必须存在的,JSON和WXSS文件是可选的。
页面路径需要在app.json中的pages字段中声明,否则这个页面不会被注册到宿主环境中。例如有两个页面:pages/index和 pages/logs,则在app.json的定义如下:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
]
}
每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置。例如:
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "功能演示",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
小程序页面中的配置项会覆盖 app.json 文件中相同名称的配置项。
WXML全称是WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件、事件系统,可以构建出程序的页面。WXML 与网页编程中的 HTML类似。例如:
<!--user_info.wxml-->
<view>
<view class="userinfo">
<block wx:if="{{!hasUserInfo}}">
<button bindtap="getUserProfile">获取头像昵称</button>
</block>
<block wx:else>
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text>{{userInfo.nickName}}</text>
</block>
</view>
</view>
与HTML类似,WXML 由标签、属性等元素构成。但是WXML的标签与H5所使用的标签有一定的区别。例如:H5主要的容器标签是div,但是在WXML文件中没有div标签,其主要容器标签为view。除了view, button, text 等标签外,小程序还提供了地图、视频、音频等标签。在小程序里边,你只需要在 WXML 写上对应的标签为小程序提供地图、视频、音频等组件功能。例如,你需要在界面上显示地图,你只需要这样写即可:
使用标签的时候,还可以通过属性传递值给组件,让组件可以以不同的状态去展现,例如,通过src将图片的地址传递给image组件,通过mode将图片的显示方式、图片裁剪和缩放的模式传递给image组件:
另外你也可以通过 style 或者 class 来控制组件的显示样式,以便适应你的界面宽度高度等等。
小程序的WXML功能非常强大,支持通过数据绑定方式来更新页面内容。另外还支持条件渲染,以适应不同条件显示不同的界面内容。支持使用wx:for控制属性绑定一个数组,使用数组中的数据重复渲染组件。WXML的详细介绍请参见后续章节。
WXSS(WeiXin Style Sheets)是一套用于小程序的样式语言,用于描述WXML的组件样式,也就是视觉上的效果。WXSS与网页开发中的CSS类似。为了更适合小程序开发,WXSS对CSS做了一些补充以及修改。另外 WXSS 仅支持部分 CSS 选择器,具体内容如下所示:
<选择器 | 样例 | 样例描述> |
---|---|---|
.class | .intro | 选择所有拥有 class=“intro” 的组件 |
#id | #firstname | 选择拥有 id=“firstname” 的组件 |
element | view | 选择所有 view 组件 |
element, element | view, checkbox | 选择所有文档的 view 组件和所有的 checkbox 组件 |
::after | view::after | 在 view 组件后边插入内容 |
::before | view::before | 在 view 组件前边插入内容 |
表 2-3 WXSS 支持的 CSS 选择器
小程序支持全局样式与局部样式,定义在 app.wxss 中的样式为全局样式,作用于每一个页面。在page的wxss文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器。例如user_info页面对应的WXSS文件的内容为:
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
color: #aaa;
padding: 200rpx 0rpx;
}
.userinfo-avatar {
overflow: hidden;
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
与网页开发相同的是小程序也支持内联样式:
<!--内联样式-->
<view style="color: red; font-size: 48rpx"></view>
页面逻辑由JS脚本文件负责,对于小程序中的每个页面,都需要在页面对应的 js 文件中进行定义,指定页面的初始数据、生命周期的回调函数、事件处理函数等。例如user_info页面的js文件的内容如下:
//pages/user_info.js Page({ //页面的初始数据 data: { userInfo: {}, hasUserInfo: false, }, //获取头像昵称 getUserProfile(e) { wx.getUserProfile({ desc: '获取用户信息', success: (res) => { console.log(res) this.setData({ userInfo: res.userInfo, hasUserInfo: true }); } }) } })
宿主环境使用Page() 构造器用来注册一个小程序页面。Page构造器接受一个Object参数,其中的data属性是页面的初始数据。页面的WXML模板使用data属性进行数据绑定。上面代码中的getUserProfile是一个事件处理函数,用户点击【获取头像昵称】按钮会激发对getUserProfile函数的调用。
在网页的一般开发流程中,我们通常会通过 JS 操作 DOM (对应 HTML 的描述产生的树),用来对页面进行更新。例如,用户点击某个按钮的时候,JS 会记录一些状态到 JS 变量里边,同时通过 DOM API 操控 DOM 的属性或者行为,进而引起界面一些变化。当项目越来越大的时候,你的代码会充斥着非常多的界面交互逻辑和程序的各种状态变量。显然这不是一个很好的开发模式,因此就有了MVVM 的开发模式(例如React, Vue),提倡把渲染和逻辑分离。简单来说就是不要再让 JS 直接操控 DOM,JS 只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系。小程序的框架也是用到了这个思路,如果你需要把一个 Hello World 的字符串显示在界面上。在WXML中是这么写的:
{{ message }}
这个message数据来自于页面Page构造器的data字段,data参数是页面第一次渲染时从逻辑层传递到渲染层的数据。以下是JS文件的定义:
Page({
data: {
message: 'Hello World'
}
})
若要动态修改界面的显示,只需要调用Page实例的setData函数即可,例如:
WXML文件示例
<view bindtap="onSetData">{{message}}</view>
JS 文件示例
Page({
data: {
message: 'Hello World'
},
onChangeData:function(){
this.setData({message:"hello weixin"});
}
})
通过 {{ }} 的语法把一个变量绑定到界面上,我们称为数据绑定。WXML通过 {{变量名}} 来绑定WXML文件和对应的JavaScript 文件中的data对象属性。通过 {{变量名}} 语法可以使得WXML拥有动态渲染的能力。
属性值也可以动态的去改变,有所不同的是,属性值必须被包裹在双引号中,如下:
<text data-test="{{test}}"> hello world</text>
UI界面的程序需要和用户互动,例如用户可能会点击你界面上某个按钮,又或者长按某个区域,这类反馈应该通知给开发者的逻辑层。在小程序里边,我们把这种“用户在渲染层的行为反馈”以及“组件的部分状态反馈”抽象为渲染层传递给逻辑层的“事件”。
要使用事件首先需要在组件中绑定一个事件处理函数。事件绑定的写法和组件属性一致,以key="value"的形式,其中:key以bind或者catch开头,然后跟上事件的类型,如bindtap、atchtouchstart。例如:
<!-- page.wxml -->
<view data-hi="demo" bindtap="onClick">Click me!</view>
然后在相应的Page定义中写上相应的事件处理函数,参数是event:
//page.js
Page({
onClick: function(event) {
console.log(event)
}
})
事件分为冒泡事件和非冒泡事件:
冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递,例如tap事件便是一个冒泡事件。
非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递,例如input组件的input事件便是一个非冒泡事件。
除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。例如在下边这个例子中,点击 inner view 会先后调用handleTap3和handleTap2(因为tap事件会冒泡到middle view,而middle view 阻止了tap事件冒泡,不再向父节点传递),点击middle view会触发handleTap2,点击 outer view 会触发handleTap1。
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
自基础库版本1.5.0起,bind和catch后可以紧跟一个冒号,其含义不变,如bind:tap、catch:touchstart。
除 bind 和 catch 外,还可以使用 mut-bind 来绑定事件。一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。例如在下边这个例子中,点击inner view会先后调用 handleTap3 和 handleTap2 ,点击 middle view会调用 handleTap2 和 handleTap1 。
<view id="outer" mut-bind:tap="handleTap1">
outer view
<view id="middle" bindtap="handleTap2">
middle view
<view id="inner" mut-bind:tap="handleTap3">
inner view
</view>
</view>
</view>
触摸类事件支持捕获阶段。捕获阶段位于冒泡阶段之前,且在捕获阶段,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。在下面的代码中,点击inner view 会先后调用handleTap2、handleTap4、handleTap3、handleTap1。
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2。
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
我们已经了解了WXML的基本用法,并且能够通过通过 {{变量名}} 的语法把一个变量绑定到界面上,也知道如何在组件绑定事件处理函数。接下来看看WXML模板的一些高级特性的使用方法。
WXML中,使用wx:if="{{condition}}" 来判断是否需要渲染该代码块:
<view wx:if="{{condition}}"> True </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>
因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。数组的当前元素的下标变量名默认为index,数组当前元素的变量名默认为item。示例:
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
JS 文件中的脚本:
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
WXML使用 wx:for-item 可指定数组当前元素的变量名,使用 wx:for-index 指定数组当前元素的下标变量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
类似wx:if ,也可以将 wx:for 用在 标签上,用来渲染一个包含多节点的结构块。例如:
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
WXML提供了模板(template)组件,可以在模板中定义代码片段,然后在不同的地方调用。
1)定义模板
使用name属性,作为模板的名字。然后在template标签内定义代码片段,如:
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
2)使用模板
使用is属性,声明需要的使用的模板,然后将模板所需要的 data 传入,如:
<template is="msgItem" data="{{...item}}"/>
JS 文件中的脚本:
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
WXML 提供两种文件引用方式:import和include。
1)import
import可以在该文件中使用目标文件定义的template,如:
在 item.wxml 中定义了一个叫item的template:
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
在 index.wxml 中引用了 item.wxml,就可以使用item模板:
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
2)include
include 可以将目标文件除了template标签wxse标签外的整个代码引入,相当于是拷贝到 include 位置,如:
代码清单:index.wxml
<!-- header.wxml -->
<view> header </view>
代码清单:footer.wxml
<!-- footer.wxml -->
<view> footer </view>
代码清单:index.wxml
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
微信客户端在生成小程序页面时会先根据json 文件配置生成一个界面。紧接着客户端就会装载这个页面的 WXML 结构和 WXSS 样式。最后客户端会装载js文件,并调用页面构造器(Page)生成页面。在生成页面的时候,小程序框架会把 data 数据和WXML文件一起渲染生成最终的页面内容。
页面初次加载的时候,微信客户端就会给Page实例派发onLoad事件,Page构造器所定义的onLoad方法会被调用。onLoad在页面没被销毁之前只会触发1次。
页面显示之后,Page构造器所定义的onShow方法会被调用,一般从别的页面返回到当前页面时,当前页的onShow方法都会被调用。
在页面初次渲染完成时,Page构造器所定义的onReady方法会被调用,onReady在页面没被销毁前只会触发1次,onReady触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。
以上三个事件触发的时机是onLoad早于 onShow,onShow早于onReady。
页面不可见时,Page构造器所定义的onHide方法会被调用。这种情况通常发生在使用wx.navigateTo切换到其他页面时、或者是切换底部tab时。
当前页面使用wx.redirectTo或wx.navigateBack切换到其他页面时,当前页面会被微信客户端销毁回收,此时Page构造器所定义的onUnload方法会被调用。
页面页面的生命周期的示意图见图2-3所示:
图 2-3 小程序页面的生命周期
小程序页面的onLoad / onReady / onShow / onHide /onUnload是Page实例的生命周期函数。下面的代码是增加了生命周期函数的处理逻辑后的user_info.js:
//pages/user_info.js Page({ //页面的初始数据 data: { userInfo: {}, hasUserInfo: false, }, //获取头像昵称 getUserProfile(e) { wx.getUserProfile({ desc: '获取用户信息', success: (res) => { console.log(res) this.setData({ userInfo: res.userInfo, hasUserInfo: true }); } }) }, 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监听页面卸载'); } })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。