代码构成JSON 配置小程序配置 app.json工具配置 project.config.json页面配置 page.jsonJSON 语法WXML 模板WXSS 样式JS 逻辑交互宿主环境渲染层和逻辑层程序与页面组件API目录结构主体结构页面结构允许上传的文件程序配置全局配置页面配置框架详解介绍响应的数据绑定页面管理基础组件丰富的 API场景值获取场景值返回来源信息的场景逻辑层 App Service介绍注册小程序注册页面生命周期页面路由模块化API视图层 View介绍WXMLWXSSWXS事件简易双向绑定基础组件获取界面上的节点信息响应显示区域变化分栏模式动画初始渲染缓存
代码构成
项目里边生成了不同类型的文件:
.json
后缀的JSON
配置文件
.wxml
后缀的WXML
模板文件
.wxss
后缀的WXSS
样式文件
.js
后缀的JS
脚本逻辑文件
接下来我们分别看看这4种文件的作用。
JSON 配置
JSON 是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。
我们可以看到在项目的根目录有一个
app.json
和 project.config.json
,此外在 pages/logs
目录下还有一个 logs.json
,我们依次来说明一下它们的用途。小程序配置 app.json
app.json
是当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。QuickStart 项目里边的 app.json
配置内容如下:{ "pages":[ "pages/index/index", "pages/logs/logs" ], "window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "Weixin", "navigationBarTextStyle":"black" } }
我们简单说一下这个配置各个项的含义:
pages
字段 —— 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。
window
字段 —— 定义小程序所有页面的顶部背景颜色,文字颜色定义等。
其他配置项细节可以参考文档 小程序的配置 app.json 。
工具配置 project.config.json
通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。
考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个
project.config.json
,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等等一系列选项。其他配置项细节可以参考文档 开发者工具的配置 。
页面配置 page.json
这里的
page.json
其实用来表示 pages/logs 目录下的 logs.json
这类和小程序页面相关的配置。如果你整个小程序的风格是蓝色调,那么你可以在
app.json
里边声明顶部颜色是蓝色即可。实际情况可能不是这样,可能你小程序里边的每个页面都有不一样的色调来区分不同功能模块,因此我们提供了 page.json
,让开发者可以独立定义每个页面的一些属性,例如刚刚说的顶部颜色、是否允许下拉刷新等等。其他配置项细节可以参考文档 页面配置 。
JSON 语法
JSON配置的一些注意事项。
JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据。JSON的Key必须包裹在一个双引号中,在实践中,编写 JSON 的时候,忘了给 Key 值加双引号或者是把双引号写成单引号是常见错误。
JSON的值只能是以下几种数据格式,其他任何格式都会触发报错,例如 JavaScript 中的 undefined。
- 数字,包含浮点数和整数
- 字符串,需要包裹在双引号中
- Bool值,true 或者 false
- 数组,需要包裹在方括号中 []
- 对象,需要包裹在大括号中 {}
- Null
还需要注意的是 JSON 文件中无法使用注释,试图添加注释将会引发报错。
WXML 模板
从事过网页编程的人知道,网页编程采用的是 HTML + CSS + JS 这样的组合,其中
HTML
是用来描述当前这个页面的结构,CSS
用来描述页面的样子,JS
通常是用来处理这个页面和用户的交互。同样道理,在小程序中也有同样的角色,其中
WXML
充当的就是类似 HTML
的角色。打开 pages/index/index.wxml
,你会看到以下的内容:<view class="container"> <view class="userinfo"> <button wx:if="{{!hasUserInfo && canIUse}}"> 获取头像昵称 </button> <block wx:else> <image src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> </view> <view class="usermotto"> <text class="user-motto">{{motto}}</text> </view> </view>
和
HTML
非常相似,WXML
由标签、属性等等构成。但是也有很多不一样的地方,我们来一一阐述一下:- 标签名字有点不一样
往往写 HTML 的时候,经常会用到的标签是
div
, p
, span
,开发者在写一个页面的时候可以根据这些基础的标签组合出不一样的组件,例如日历、弹窗等等。换个思路,既然大家都需要这些组件,为什么我们不能把这些常用的组件包装起来,大大提高我们的开发效率。从上边的例子可以看到,小程序的
WXML
用的标签是 view
, button
, text
等等,这些标签就是小程序给开发者包装好的基本能力,我们还提供了地图、视频、音频等等组件能力。更多详细的组件讲述参考下个章节 小程序的能力
- 多了一些
wx:if
这样的属性以及 {{ }} 这样的表达式
在网页的一般开发流程中,我们通常会通过
JS
操作 DOM
(对应 HTML
的描述产生的树),以引起界面的一些变化响应用户的行为。例如,用户点击某个按钮的时候,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:
开头的属性来表达。更详细的文档可以参考 WXML
WXSS 样式
WXSS
具有 CSS
大部分的特性,小程序在 WXSS
也做了一些扩充和修改。- 新增了尺寸单位。在写
CSS
样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS
在底层支持新的尺寸单位rpx
,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
- 提供了全局的样式和局部样式。和前边
app.json
,page.json
的概念相同,你可以写一个app.wxss
作为全局样式,会作用于当前小程序的所有页面,局部页面样式page.wxss
仅对当前页面生效。
- 此外
WXSS
仅支持部分CSS
选择器
更详细的文档可以参考 WXSS 。
JS 逻辑交互
一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写
JS
脚本文件来处理用户的操作。<view>{{ msg }}</view><button bindtap="clickMe">点击我</button>
点击
button
按钮的时候,我们希望把界面上 msg
显示成 "Hello World"
,于是我们在 button
上声明一个属性: bindtap
,在 JS 文件里边声明了 clickMe
方法来响应这次点击操作:Page({ clickMe: function() { this.setData({ msg: "Hello World" }) } })
响应用户的操作就是这么简单,更详细的事件可以参考文档 WXML - 事件 。
此外你还可以在 JS 中调用小程序提供的丰富的 API,利用这些 API 可以很方便的调起微信提供的能力,例如获取用户信息、本地存储、微信支付等。在前边的 QuickStart 例子中,在
pages/index/index.js
就调用了 wx.getUserInfo 获取微信用户的头像和昵称,最后通过 setData
把获取到的信息显示到界面上。更多 API 可以参考文档 小程序的API 。宿主环境
我们称微信客户端给小程序所提供的环境为宿主环境。小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能。
上一章中我们把小程序涉及到的文件类型阐述了一遍,我们结合 QuickStart 这个项目来讲一下这些文件是怎么配合工作的。
渲染层和逻辑层
首先,我们来简单了解下小程序的运行环境。小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了
WebView
进行渲染;逻辑层采用JsCore
线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native
来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。有关渲染层和逻辑层的详细文档参考 小程序框架 。
程序与页面
微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地。
紧接着通过
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 实例,是全部页面共享的,更多的事件回调参考文档 注册程序 App 。
接下来我们简单看看小程序的一个页面是怎么写的。
你可以观察到
pages/logs/logs
下其实是包括了4种文件的,微信客户端会先根据 logs.json
配置生成一个界面,顶部的颜色和文字你都可以在这个 json
文件里边定义好。紧接着客户端就会装载这个页面的 WXML
结构和 WXSS
样式。最后客户端会装载 logs.js
,你可以看到 logs.js
的大体内容就是:Page({ data: { // 参与页面渲染的数据 logs: [] }, onLoad: function () { // 页面渲染后 执行 } })
Page
是一个页面构造器,这个构造器就生成了一个页面。在生成页面的时候,小程序框架会把 data
数据和 index.wxml
一起渲染出最终的结构,于是就得到了你看到的小程序的样子。在渲染完界面之后,页面实例就会收到一个
onLoad
的回调,你可以在这个回调处理你的逻辑。有关于
Page
构造器更多详细的文档参考 注册页面 Page 。组件
小程序提供了丰富的基础组件给开发者,开发者可以像搭积木一样,组合各种组件拼合成自己的小程序。
就像
HTML
的 div
, p
等标签一样,在小程序里边,你只需要在 WXML
写上对应的组件标签名字就可以把该组件显示在界面上,例如,你需要在界面上显示地图,你只需要这样写即可:<map></map>
使用组件的时候,还可以通过属性传递值给组件,让组件可以以不同的状态去展现,例如,我们希望地图一开始的中心的经纬度是广州,那么你需要声明地图的 longitude(中心经度) 和 latitude(中心纬度)两个属性:
<map longitude="广州经度" latitude="广州纬度"></map>
组件的内部行为也会通过事件的形式让开发者可以感知,例如用户点击了地图上的某个标记,你可以在
js
编写 markertap
函数来处理:<map bindmarkertap="markertap" longitude="广州经度" latitude="广州纬度"></map>
当然你也可以通过
style
或者 class
来控制组件的外层样式,以便适应你的界面宽度高度等等。更多的组件可以参考 小程序的组件。
API
为了让开发者可以很方便的调起微信提供的能力,例如获取用户信息、微信支付等等,小程序提供了很多 API 给开发者去使用。
要获取用户的地理位置时,只需要:
wx.getLocation({ type: 'wgs84', success: (res) => { var latitude = res.latitude // 纬度 var longitude = res.longitude // 经度 } })
调用微信扫一扫能力,只需要:
wx.scanCode({ success: (res) => { console.log(res) } })
需要注意的是:多数 API 的回调都是异步,你需要处理好代码逻辑的异步问题。
更多的 API 能力见 小程序的API。
通过这个章节你已经大概了解了小程序运行的一些基本概念,当你开发完一个小程序之后,你就需要发布你的小程序。在下个章节,你会知道发布前需要做什么准备。
目录结构
主体结构
小程序包含一个描述整体程序的
app
和多个描述各自页面的 page
。一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
文件 | 必需 | 作用 |
是 | 小程序逻辑 | |
是 | 小程序公共配置 | |
否 | 小程序公共样式表 |
页面结构
一个小程序页面由四个文件组成,分别是:
文件类型 | 必需 | 作用 |
是 | 页面逻辑 | |
是 | 页面结构 | |
否 | 页面配置 | |
否 | 页面样式表 |
注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。
允许上传的文件
在项目目录中,以下文件会经过编译,因此上传之后无法直接访问到:.js、app.json、.wxml、*.wxss(其中 wxml 和 wxss 文件仅针对在 app.json 中配置了的页面)。除此之外,只有后缀名在白名单内的文件可以被上传,不在白名单列表内文件在开发工具能被访问到,但无法被上传。具体白名单列表如下:
wxs png jpg jpeg gif svg json cer mp3 aac m4a mp4 wav ogg silk wasm br cert
程序配置
全局配置
小程序根目录下的
app.json
文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。完整配置项说明请参考小程序全局配置
以下是一个包含了部分常用配置选项的
app.json
:{ "pages": [ "pages/index/index", "pages/logs/index" ], "window": { "navigationBarTitleText": "Demo" }, "tabBar": { "list": [{ "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/index", "text": "日志" }] }, "networkTimeout": { "request": 10000, "downloadFile": 10000 }, "debug": true }
完整配置项说明请参考小程序全局配置
页面配置
每一个小程序页面也可以使用同名
.json
文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json
的 window
中相同的配置项。完整配置项说明请参考小程序页面配置
例如:
{ "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "微信接口功能演示", "backgroundColor": "#eeeeee", "backgroundTextStyle": "light" }
框架详解
介绍
小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。小程序提供了自己的视图层描述语言
WXML
和 WXSS
,以及基于 JavaScript
的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。响应的数据绑定
框架的核心是一个响应的数据绑定系统,可以让数据与视图非常简单地保持同步。当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。
通过这个简单的例子来看:
<!-- This is our View --> <view> Hello {{name}}! </view><button bindtap="changeName"> Click me! </button>
// This is our App Service. // This is our data. var helloData = { name: 'Weixin' } // Register a Page. Page({ data: helloData, changeName: function(e) { // sent data change to view this.setData({ name: 'MINA' }) } })
- 开发者通过框架将逻辑层数据中的
name
与视图层的name
进行了绑定,所以在页面一打开的时候会显示Hello Weixin!
;
- 当点击按钮的时候,视图层会发送
changeName
的事件给逻辑层,逻辑层找到并执行对应的事件处理函数;
- 回调函数触发后,逻辑层执行
setData
的操作,将data
中的name
从Weixin
变为MINA
,因为该数据和视图层已经绑定了,从而视图层会自动改变为Hello MINA!
。
页面管理
框架 管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据、方法、生命周期函数注册到 框架 中,其他的一切复杂的操作都交由 框架 处理。
基础组件
框架 提供了一套基础的组件,这些组件自带微信风格的样式以及特殊的逻辑,开发者可以通过组合基础组件,创建出强大的微信小程序 。
丰富的 API
框架 提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。
场景值
基础库 1.1.0 开始支持,低版本需做兼容处理。
场景值用来描述用户进入小程序的路径。完整场景值的含义请查看场景值列表。
由于Android系统限制,目前还无法获取到按 Home 键退出到桌面,然后从桌面再次进小程序的场景值,对于这种情况,会保留上一次的场景值。
获取场景值
开发者可以通过下列方式获取场景值:
- 对于小程序,可以在
App
的onLaunch
和onShow
,或wx.getLaunchOptionsSync 中获取上述场景值。
- 对于小游戏,可以在 wx.getLaunchOptionsSync 和 wx.onShow 中获取上述场景值
返回来源信息的场景
部分场景值下还可以获取来源应用、公众号或小程序的appId。获取方式请参考对应API的参考文档。
场景值 | 场景 | appId含义 |
1020 | 公众号 profile 页相关小程序列表 | 来源公众号 |
1035 | 公众号自定义菜单 | 来源公众号 |
1036 | App 分享消息卡片 | 来源App |
1037 | 小程序打开小程序 | 来源小程序 |
1038 | 从另一个小程序返回 | 来源小程序 |
1043 | 公众号模板消息 | 来源公众号 |
逻辑层 App Service
介绍
小程序开发框架的逻辑层使用
JavaScript
引擎为小程序提供开发 JavaScript
代码的运行环境以及微信小程序的特有功能。逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
开发者写的所有代码最终将会打包成一份
JavaScript
文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 ServiceWorker,所以逻辑层也称之为 App Service。在
JavaScript
的基础上,我们增加了一些功能,以方便小程序的开发:- 增加
getApp
和getCurrentPages
方法,分别用来获取App
实例和当前页面栈。
- 提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。
注意:小程序框架的逻辑层并非运行在浏览器中,因此
JavaScript
在 web 中一些能力都无法使用,如 window
,document
等。注册小程序
每个小程序都需要在
app.js
中调用 App
方法注册小程序实例,绑定生命周期回调函数、错误监听和页面不存在监听函数等。详细的参数含义和使用请参考 App 参考文档 。
// app.js App({ onLaunch (options) { // Do something initial when launch. }, onShow (options) { // Do something when show. }, onHide () { // Do something when hide. }, onError (msg) { console.log(msg) }, globalData: 'I am global data' })
整个小程序只有一个 App 实例,是全部页面共享的。开发者可以通过
getApp
方法获取到全局唯一的 App 实例,获取App上的数据或调用开发者注册在 App
上的函数。// xxx.js const appInstance = getApp() console.log(appInstance.globalData) // I am global data
注册页面
对于小程序中的每个页面,都需要在页面对应的
js
文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等。使用 Page 构造器注册页面
简单的页面可以使用
Page()
进行构造。代码示例:
//index.js Page({ data: { text: "This is page data." }, onLoad: function(options) { // 页面创建时执行 }, onShow: function() { // 页面出现在前台时执行 }, onReady: function() { // 页面首次渲染完毕时执行 }, onHide: function() { // 页面从前台变为后台时执行 }, onUnload: function() { // 页面销毁时执行 }, onPullDownRefresh: function() { // 触发下拉刷新时执行 }, onReachBottom: function() { // 页面触底时执行 }, onShareAppMessage: function () { // 页面被用户分享时执行 }, onPageScroll: function() { // 页面滚动时执行 }, onResize: function() { // 页面尺寸变化时执行 }, onTabItemTap(item) { // tab 点击时执行 console.log(item.index) console.log(item.pagePath) console.log(item.text) }, // 事件响应函数 viewTap: function() { this.setData({ text: 'Set some data for updating view.' }, function() { // this is setData callback }) }, // 自由数据 customData: { hi: 'MINA' } })
详细的参数含义和使用请参考 Page 参考文档 。
在页面中使用 behaviors
基础库 2.9.2 开始支持,低版本需做兼容处理。
页面可以引用 behaviors 。 behaviors 可以用来让多个页面有相同的数据字段和方法。
// my-behavior.js module.exports = Behavior({ data: { sharedText: 'This is a piece of data shared between pages.' }, methods: { sharedMethod: function() { this.data.sharedText === 'This is a piece of data shared between pages.' } } })
// page-a.js var myBehavior = require('./my-behavior.js') Page({ behaviors: [myBehavior], onLoad: function() { this.data.sharedText === 'This is a piece of data shared between pages.' } })
具体用法参见 behaviors 。
使用 Component 构造器构造页面
基础库 1.6.3 开始支持,低版本需做兼容处理。
Page
构造器适用于简单的页面。但对于复杂的页面, Page
构造器可能并不好用。此时,可以使用
Component
构造器来构造页面。 Component
构造器的主要区别是:方法需要放在 methods: { }
里面。代码示例:
Component({ data: { text: "This is page data." }, methods: { onLoad: function(options) { // 页面创建时执行 }, onPullDownRefresh: function() { // 下拉刷新时执行 }, // 事件响应函数 viewTap: function() { // ... } } })
这种创建方式非常类似于 自定义组件 ,可以像自定义组件一样使用
behaviors
等高级特性。生命周期
以下内容你不需要立马完全弄明白,不过以后它会有帮助。
下图说明了页面
Page
实例的生命周期。页面路由
在小程序中所有页面的路由全部由框架进行管理。
页面栈
框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,页面栈的表现如下:
路由方式 | 页面栈表现 |
初始化 | 新页面入栈 |
打开新页面 | 新页面入栈 |
页面重定向 | 当前页面出栈,新页面入栈 |
页面返回 | 页面不断出栈,直到目标返回页 |
Tab 切换 | 页面全部出栈,只留下新的 Tab 页面 |
重加载 | 页面全部出栈,只留下新的页面 |
开发者可以使用
getCurrentPages()
函数获取当前页面栈。路由方式
对于路由的触发方式以及页面生命周期函数如下:
路由方式 | 触发时机 | 路由前页面 | 路由后页面 |
初始化 | 小程序打开的第一个页面 | ㅤ | onLoad, onShow |
打开新页面 | 调用 API wx.navigateTo使用组件 <navigator open-type="navigateTo"/> | onHide | onLoad, onShow |
页面重定向 | 调用 API wx.redirectTo使用组件 <navigator open-type="redirectTo"/> | onUnload | onLoad, onShow |
页面返回 | 调用 API wx.navigateBack使用组件 <navigator open-type="navigateBack"> 用户按左上角返回按钮 | onUnload | onShow |
Tab 切换 | 调用 API wx.switchTab使用组件 <navigator open-type="switchTab"/> 用户切换 Tab | ㅤ | 各种情况请参考下表 |
重启动 | 调用 API wx.reLaunch使用组件 <navigator open-type="reLaunch"/> | onUnload | onLoad, onShow |
Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例):
当前页面 | 路由后页面 | 触发的生命周期(按顺序) |
A | A | Nothing happend |
A | B | A.onHide(), B.onLoad(), B.onShow() |
A | B(再次打开) | A.onHide(), B.onShow() |
C | A | C.onUnload(), A.onShow() |
C | B | C.onUnload(), B.onLoad(), B.onShow() |
D | B | D.onUnload(), C.onUnload(), B.onLoad(), B.onShow() |
D(从转发进入) | A | D.onUnload(), A.onLoad(), A.onShow() |
D(从转发进入) | B | D.onUnload(), B.onLoad(), B.onShow() |
注意事项
navigateTo
,redirectTo
只能打开非 tabBar 页面。
switchTab
只能打开 tabBar 页面。
reLaunch
可以打开任意页面。
- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的
onLoad
中获取。
模块化
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过
module.exports
或者 exports
才能对外暴露接口。注意:
exports
是module.exports
的一个引用,因此在模块里边随意更改exports
的指向会造成未知的错误。所以更推荐开发者采用module.exports
来暴露模块接口,除非你已经清晰知道这两者的关系。
- 小程序目前不支持直接引入
node_modules
, 开发者需要使用到node_modules
时候建议拷贝出相关的代码到小程序的目录中,或者使用小程序支持的 npm 功能。
// common.js function sayHello(name) { console.log(`Hello ${name} !`) } function sayGoodbye(name) { console.log(`Goodbye ${name} !`) } module.exports.sayHello = sayHello exports.sayGoodbye = sayGoodbye
在需要使用这些模块的文件中,使用
require
将公共代码引入var common = require('common.js') Page({ helloMINA: function() { common.sayHello('MINA') }, goodbyeMINA: function() { common.sayGoodbye('MINA') } })
文件作用域
在 JavaScript 文件中声明的变量和函数只在该文件中有效;不同的文件中可以声明相同名字的变量和函数,不会互相影响。
通过全局函数
getApp
可以获取全局的应用实例,如果需要全局的数据可以在 App()
中设置,如:// app.js App({ globalData: 1 })
// a.js // The localValue can only be used in file a.js. var localValue = 'a' // Get the app instance. var app = getApp() // Get the global data and change it. app.globalData++
// b.js // You can redefine localValue in file b.js, without interference with the localValue in a.js. var localValue = 'b' // If a.js it run before b.js, now the globalData shoule be 2. console.log(getApp().globalData)
API
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。详细介绍请参考 API 文档。
通常,在小程序 API 有以下几种类型:
事件监听 API
这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。
代码示例
wx.onCompassChange(function (res) { console.log(res.direction) })
同步 API
我们约定,以
Sync
结尾的 API 都是同步 API, 如 wx.setStorageSync,wx.getSystemInfoSync 等。此外,也有一些其他的同步 API,如 wx.createWorker,wx.getBackgroundAudioManager 等,详情参见 API 文档中的说明。同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。
代码示例
try { wx.setStorageSync('key', 'value') } catch (e) { console.error(e) }
异步 API
Object 参数说明
参数名 | 类型 | 必填 | 说明 |
success | function | 否 | 接口调用成功的回调函数 |
fail | function | 否 | 接口调用失败的回调函数 |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
其他 | Any | - | 接口定义的其他参数 |
回调函数的参数
success
,fail
,complete
函数调用时会传入一个 Object
类型参数,包含以下字段:属性 | 类型 | 说明 |
errMsg | string | 错误信息,如果调用成功返回 ${apiName}:ok |
errCode | number | 错误码,仅部分 API 支持,具体含义请参考对应 API 文档,成功时为 0 。 |
其他 | Any | 接口返回的其他数据 |
异步 API 的执行结果需要通过
Object
类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能,如 wx.request,wx.connectSocket 等。代码示例
wx.login({
success(res) {
console.log(res.code)
}
})
异步 API 返回 Promise
基础库 2.10.2 版本起,异步 API 支持 callback & promise 两种调用方式。当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 promise,否则仍按回调方式执行,无返回值。
注意事项
- 部分接口如
downloadFile
,request
,uploadFile
,connectSocket
,createCamera
(小游戏)本身就有返回值, 它们的 promisify 需要开发者自行封装。
- 当没有回调参数时,异步接口返回 promise。此时若函数调用失败进入 fail 逻辑, 会报错提示
Uncaught (in promise)
,开发者可通过 catch 来进行捕获。
- wx.onUnhandledRejection 可以监听未处理的 Promise 拒绝事件。
代码示例
// callback 形式调用 wx.chooseImage({ success(res) { console.log('res:', res) } }) // promise 形式调用 wx.chooseImage().then(res => console.log('res: ', res))
云开发 API
代码示例
wx.cloud.callFunction({ // 云函数名称 name: 'cloudFunc', // 传给云函数的参数 data: { a: 1, b: 2, }, success: function(res) { console.log(res.result) // 示例 }, fail: console.error }) // 此外,云函数同样支持promise形式调用
视图层 View
介绍
框架的视图层由 WXML 与 WXSS 编写,由组件来进行展示。
将逻辑层的数据反映成视图,同时将视图层的事件发送给逻辑层。
WXML(WeiXin Markup language) 用于描述页面的结构。
WXS(WeiXin Script) 是小程序的一套脚本语言,结合
WXML
,可以构建出页面的结构。WXSS(WeiXin Style Sheet) 用于描述页面的样式。
组件(Component)是视图的基本组成单元。
WXML
要完整了解 WXML 语法,请参考WXML 语法参考。
用以下一些简单的例子来看看 WXML 具有什么能力:
数据绑定
<!--wxml--> <view> {{message}} </view>
// page.js Page({ data: { message: 'Hello MINA!' } })
列表渲染
<!--wxml--> <view wx:for="{{array}}"> {{item}} </view>
// page.js Page({ data: { array: [1, 2, 3, 4, 5] } })
条件渲染
<!--wxml--> <view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view><view wx:elif="{{view == 'APP'}}"> APP </view><view wx:else="{{view == 'MINA'}}"> MINA </view>
// page.js Page({ data: { view: 'MINA' } })
模板
<!--wxml--> <template name="staffName"><view> FirstName: {{firstName}}, LastName: {{lastName}} </view></template><template is="staffName" data="{{...staffA}}"></template><template is="staffName" data="{{...staffB}}"></template><template is="staffName" data="{{...staffC}}"></template>
// page.js Page({ data: { staffA: {firstName: 'Hulk', lastName: 'Hu'}, staffB: {firstName: 'Shang', lastName: 'You'}, staffC: {firstName: 'Gideon', lastName: 'Lin'} } })
具体的能力以及使用方式在以下章节查看:
WXSS
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
WXSS 用来决定 WXML 的组件应该怎么显示。
为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。
与 CSS 相比,WXSS 扩展的特性有:
- 尺寸单位
- 样式导入
尺寸单位
- rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
设备 | rpx换算px (屏幕宽度/750) | px换算rpx (750/屏幕宽度) |
iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
iPhone6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |
建议: 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。
注意: 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况。
样式导入
使用
@import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用;
表示语句结束。示例代码:
/** common.wxss **/ .small-p { padding:5px; }
/** app.wxss **/ @import "common.wxss"; .middle-p { padding:15px; }
内联样式
框架组件上支持使用 style、class 属性来控制组件的样式。
- style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。
<view style="color:{{color}};" />
- class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上
.
,样式类名之间用空格分隔。
<view class="normal_view" />
选择器
目前支持的选择器有:
选择器 | 样例 | 样例描述 |
.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 组件前边插入内容 |
全局样式与局部样式
定义在 app.wxss 中的样式为全局样式,作用于每一个页面。在 page 的 wxss 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器。
WXS
WXS(WeiXin Script)是内联在 WXML 中的脚本段。通过 WXS 可以在模版中内联少量处理脚本,丰富模板的数据预处理能力。另外, WXS 还可以用来编写简单的 WXS 事件响应函数。
从语法上看, WXS 类似于有少量限制的 JavaScript 。要完整了解 WXS 语法,请参考WXS 语法参考。
以下是一些使用 WXS 的简单示例。
页面渲染
<!--wxml--> <wxs module="m1"> var msg = "hello world"; module.exports.message = msg; </wxs> <view> {{m1.message}} </view>
页面输出:
hello world
数据处理
// page.js Page({ data: { array: [1, 2, 3, 4, 5, 1, 2, 3, 4] } })
<!--wxml--> <!-- 下面的 getMax 函数,接受一个数组,且返回数组中最大的元素的值 --> <wxs module="m1"> var getMax = function(array) { var max = undefined; for (var i = 0; i < array.length; ++i) { max = max === undefined ? array[i] : (max >= array[i] ? max : array[i]); } return max; } module.exports.getMax = getMax; </wxs><!-- 调用 wxs 里面的 getMax 函数,参数为 page.js 里面的 array --> <view> {{m1.getMax(array)}} </view>
页面输出:
5
事件
什么是事件
- 事件是视图层到逻辑层的通讯方式。
- 事件可以将用户的行为反馈到逻辑层进行处理。
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
- 事件对象可以携带额外信息,如 id, dataset, touches。
事件的使用方式
- 在组件中绑定一个事件处理函数。
如
bindtap
,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。<view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view>
- 在相应的Page定义中写上相应的事件处理函数,参数是event。
Page({ tapName: function(event) { console.log(event) } })
- 可以看到log出来的信息大致如下:
{ "type":"tap", "timeStamp":895, "target": { "id": "tapTest", "dataset": { "hi":"Weixin" } }, "currentTarget": { "id": "tapTest", "dataset": { "hi":"Weixin" } }, "detail": { "x":53, "y":14 }, "touches":[{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14 }], "changedTouches":[{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14 }] }
使用WXS函数响应事件
基础库 2.4.4 开始支持,低版本需做兼容处理。
从基础库版本
2.4.4
开始,支持使用WXS函数绑定事件,WXS函数接受2个参数,第一个是event,在原有的event的基础上加了event.instance
对象,第二个参数是ownerInstance
,和event.instance
一样是一个ComponentDescriptor
对象。具体使用如下:- 在组件中绑定和注册事件处理的WXS函数。
<wxs module="wxs" src="./test.wxs"></wxs> <view id="tapTest" data-hi="Weixin" bindtap="{{wxs.tapName}}"> Click me! </view> **注:绑定的WXS函数必须用{{}}括起来**
- test.wxs文件实现tapName函数
function tapName(event, ownerInstance) { console.log('tap Weixin', JSON.stringify(event)) } module.exports = { tapName: tapName }
ownerInstance
包含了一些方法,可以设置组件的样式和class,具体包含的方法以及为什么要用WXS函数响应事件,请点击查看详情。事件分类
事件分为冒泡事件和非冒泡事件:
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML的冒泡事件列表:
类型 | 触发条件 | 最低版本 |
touchstart | 手指触摸动作开始 | ㅤ |
touchmove | 手指触摸后移动 | ㅤ |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | ㅤ |
touchend | 手指触摸动作结束 | ㅤ |
tap | 手指触摸后马上离开 | ㅤ |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 | |
longtap | 手指触摸后,超过350ms再离开(推荐使用longpress事件代替) | ㅤ |
transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 | ㅤ |
animationstart | 会在一个 WXSS animation 动画开始时触发 | ㅤ |
animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | ㅤ |
animationend | 会在一个 WXSS animation 动画完成时触发 | ㅤ |
touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 |
普通事件绑定
事件绑定的写法类似于组件的属性,如:
<view bindtap="handleTap"> Click here! </view>
如果用户点击这个 view ,则页面的
handleTap
会被调用。事件绑定函数可以是一个数据绑定,如:
<view bindtap="{{ handlerName }}"> Click here! </view>
此时,页面的
this.data.handlerName
必须是一个字符串,指定事件处理函数名;如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)。绑定并阻止事件冒泡
除
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>
互斥事件绑定
自基础库版本 2.8.2 起,除
bind
和 catch
外,还可以使用 mut-bind
来绑定事件。一个 mut-bind
触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind
绑定函数不会被触发,但 bind
绑定函数和 catch
绑定函数依旧会被触发。换而言之,所有
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>
事件的捕获阶段
自基础库版本 1.5.0 起,触摸类事件支持捕获阶段。捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用
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>
事件对象
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
BaseEvent 基础事件对象属性列表:
属性 | 类型 | 说明 | 基础库版本 |
String | 事件类型 | ㅤ | |
Integer | 事件生成时的时间戳 | ㅤ | |
Object | 触发事件的组件的一些属性值集合 | ㅤ | |
Object | 当前组件的一些属性值集合 | ㅤ | |
Object | 事件标记数据 |
CustomEvent 自定义事件对象属性列表(继承 BaseEvent):
属性 | 类型 | 说明 |
Object | 额外的信息 |
TouchEvent 触摸事件对象属性列表(继承 BaseEvent):
属性 | 类型 | 说明 |
Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 | |
Array | 触摸事件,当前变化的触摸点信息的数组 |
特殊事件: canvas 中的触摸事件不可冒泡,所以没有 currentTarget。
type
代表事件的类型。
timeStamp
页面打开到触发事件所经过的毫秒数。
target
触发事件的源组件。
属性 | 类型 | 说明 |
id | String | 事件源组件的id |
Object | 事件源组件上由 data- 开头的自定义属性组成的集合 |
currentTarget
事件绑定的当前组件。
属性 | 类型 | 说明 |
id | String | 当前组件的id |
Object | 当前组件上由 data- 开头的自定义属性组成的集合 |
说明: target 和 currentTarget 可以参考上例中,点击 inner view 时,
handleTap3
收到的事件对象 target 和 currentTarget 都是 inner,而 handleTap2
收到的事件对象 target 就是 inner,currentTarget 就是 middle。dataset
在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
在 WXML 中,这些自定义数据以
data-
开头,多个单词由连字符 -
连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。如:data-element-type
,最终会呈现为event.currentTarget.dataset.elementType
;
data-elementType
,最终会呈现为event.currentTarget.dataset.elementtype
。
示例:
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({ bindViewTap:function(event){ event.currentTarget.dataset.alphaBeta === 1 // - 会转为驼峰写法 event.currentTarget.dataset.alphabeta === 2 // 大写会转为小写 } })
mark
当事件触发时,事件冒泡路径上所有的
mark
会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark
。)代码示例:
<view mark:myMark="last" bindtap="bindViewTap"> <button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button> </view>
在上述 WXML 中,如果按钮被点击,将触发
bindViewTap
和 bindButtonTap
两个事件,事件携带的 event.mark
将包含 myMark
和 anotherMark
两项。Page({ bindViewTap: function(e) { e.mark.myMark === "last" // true e.mark.anotherMark === "leaf" // true } })
mark
和 dataset
很相似,主要区别在于: mark
会包含从触发事件的节点到根节点上所有的 mark:
属性值;而 dataset
仅包含一个节点的 data-
属性值。细节注意事项:
- 如果存在同名的
mark
,父节点的mark
会被子节点覆盖。
- 在自定义组件中接收事件时,
mark
不包含自定义组件外的节点的mark
。
- 不同于
dataset
,节点的mark
不会做连字符和大小写转换。
touches
touches 是一个数组,每个元素为一个 Touch 对象(canvas 触摸事件中携带的 touches 是 CanvasTouch 数组)。 表示当前停留在屏幕上的触摸点。
Touch 对象
属性 | 类型 | 说明 |
identifier | Number | 触摸点的标识符 |
pageX, pageY | Number | 距离文档左上角的距离,文档的左上角为原点 ,横向为X轴,纵向为Y轴 |
clientX, clientY | Number | 距离页面可显示区域(屏幕除去导航条)左上角距离,横向为X轴,纵向为Y轴 |
CanvasTouch 对象
属性 | 类型 | 说明 | 特殊说明 |
identifier | Number | 触摸点的标识符 | ㅤ |
x, y | Number | 距离 Canvas 左上角的距离,Canvas 的左上角为原点 ,横向为X轴,纵向为Y轴 | ㅤ |
changedTouches
changedTouches 数据格式同 touches。 表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。
detail
自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。
点击事件的
detail
带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。简易双向绑定
基础库 2.9.3 开始支持,低版本需做兼容处理。
双向绑定语法
在 WXML 中,普通的属性的绑定是单向的。例如:
<input value="{{value}}" />
如果使用
this.setData({ value: 'leaf' })
来更新 value
,this.data.value
和输入框的中显示的值都会被更新为 leaf
;但如果用户修改了输入框里的值,却不会同时改变 this.data.value
。如果需要在用户输入的同时改变
this.data.value
,需要借助简易双向绑定机制。此时,可以在对应项目之前加入 model:
前缀:<input model:value="{{value}}" />
用于双向绑定的表达式有如下限制:
- 只能是一个单一字段的绑定,如
<input model:value="值为 {{value}}" /> <input model:value="{{ a + b }}" />
都是非法的;
- 目前,尚不能 data 路径,如
<input model:value="{{ a.b }}" />
这样的表达式目前暂不支持。
在自定义组件中传递双向绑定
双向绑定同样可以使用在自定义组件上。如下的自定义组件:
// custom-component.js Component({ properties: { myValue: String } })
<!-- custom-component.wxml --> <input model:value="{{myValue}}" />
这个自定义组件将自身的
myValue
属性双向绑定到了组件内输入框的 value
属性上。这样,如果页面这样使用这个组件:<custom-component model:my-value="{{pageValue}}" />
当输入框的值变更时,自定义组件的
myValue
属性会同时变更,这样,页面的 this.data.pageValue
也会同时变更,页面 WXML 中所有绑定了 pageValue
的位置也会被一同更新。在自定义组件中触发双向绑定更新
自定义组件还可以自己触发双向绑定更新,做法就是:使用 setData 设置自身的属性。例如:
// custom-component.js Component({ properties: { myValue: String }, methods: { update: function() { // 更新 myValue this.setData({ myValue: 'leaf' }) } } })
如果页面这样使用这个组件:
<custom-component model:my-value="{{pageValue}}" />
当组件使用
setData
更新 myValue
时,页面的 this.data.pageValue
也会同时变更,页面 WXML 中所有绑定了 pageValue
的位置也会被一同更新。基础组件
框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。详细介绍请参考组件文档。
什么是组件:
- 组件是视图层的基本组成单元。
- 组件自带一些功能与微信风格一致的样式。
- 一个组件通常包括
开始标签
和结束标签
,属性
用来修饰这个组件,内容
在两个标签之内。
<tagname property="value"> Content goes here ... </tagname>
注意:所有组件与属性都是小写,以连字符
-
连接属性类型
类型 | 描述 | 注解 |
Boolean | 布尔值 | 组件写上该属性,不管是什么值都被当作 true ;只有组件上没有该属性时,属性值才为false 。如果属性值为变量,变量的值会被转换为Boolean类型 |
Number | 数字 | 1 , 2.5 |
String | 字符串 | "string" |
Array | 数组 | [ 1, "string" ] |
Object | 对象 | { key: value } |
EventHandler | 事件处理函数名 | "handlerName" 是 Page 中定义的事件处理函数名 |
Any | 任意属性 | ㅤ |
公共属性
所有组件都有以下属性:
属性名 | 类型 | 描述 | 注解 |
id | String | 组件的唯一标示 | 保持整个页面唯一 |
class | String | 组件的样式类 | 在对应的 WXSS 中定义的样式类 |
style | String | 组件的内联样式 | 可以动态设置的内联样式 |
hidden | Boolean | 组件是否显示 | 所有组件默认显示 |
data-* | Any | 自定义属性 | 组件上触发的事件时,会发送给事件处理函数 |
bind* / catch* | EventHandler | 组件的事件 | 详见事件 |
特殊属性
几乎所有组件都有各自定义的属性,可以对该组件的功能或样式进行修饰,请参考各个组件的定义。
获取界面上的节点信息
WXML节点信息
节点信息查询 API 可以用于获取节点属性、样式、在界面上的位置等信息。
最常见的用法是使用这个接口来查询某个节点的当前位置,以及界面的滚动位置。
示例代码:
const query = wx.createSelectorQuery() query.select('#the-id').boundingClientRect(function(res){ res.top // #the-id 节点的上边界坐标(相对于显示区域) }) query.selectViewport().scrollOffset(function(res){ res.scrollTop // 显示区域的竖直滚动位置 }) query.exec()
上述示例中,
#the-id
是一个节点选择器,与 CSS 的选择器相近但略有区别,请参见 SelectorQuery.select 的相关说明。在自定义组件或包含自定义组件的页面中,推荐使用
this.createSelectorQuery
来代替 wx.createSelectorQuery ,这样可以确保在正确的范围内选择节点。WXML节点布局相交状态
节点布局相交状态 API 可用于监听两个或多个组件节点在布局位置上的相交状态。这一组API常常可以用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。
这一组API涉及的主要概念如下。
- 参照节点:监听的参照节点,取它的布局区域作为参照区域。如果有多个参照节点,则会取它们布局区域的 交集 作为参照区域。页面显示区域也可作为参照区域之一。
- 目标节点:监听的目标,默认只能是一个节点(使用
selectAll
选项时,可以同时监听多个节点)。
- 相交区域:目标节点的布局区域与参照区域的相交区域。
- 相交比例:相交区域占参照区域的比例。
- 阈值:相交比例如果达到阈值,则会触发监听器的回调函数。阈值可以有多个。
以下示例代码可以在目标节点(用选择器
.target-class
指定)每次进入或离开页面显示区域时,触发回调函数。示例代码:
Page({ onLoad: function(){ wx.createIntersectionObserver().relativeToViewport().observe('.target-class', (res) => { res.id // 目标节点 id res.dataset // 目标节点 dataset res.intersectionRatio // 相交区域占目标节点的布局区域的比例 res.intersectionRect // 相交区域 res.intersectionRect.left // 相交区域的左边界坐标 res.intersectionRect.top // 相交区域的上边界坐标 res.intersectionRect.width // 相交区域的宽度 res.intersectionRect.height // 相交区域的高度 }) } })
以下示例代码可以在目标节点(用选择器
.target-class
指定)与参照节点(用选择器 .relative-class
指定)在页面显示区域内相交或相离,且相交或相离程度达到目标节点布局区域的20%和50%时,触发回调函数。示例代码:
Page({ onLoad: function(){ wx.createIntersectionObserver(this, { thresholds: [0.2, 0.5] }).relativeTo('.relative-class').relativeToViewport().observe('.target-class', (res) => { res.intersectionRatio // 相交区域占目标节点的布局区域的比例 res.intersectionRect // 相交区域 res.intersectionRect.left // 相交区域的左边界坐标 res.intersectionRect.top // 相交区域的上边界坐标 res.intersectionRect.width // 相交区域的宽度 res.intersectionRect.height // 相交区域的高度 }) } })
注意:与页面显示区域的相交区域并不准确代表用户可见的区域,因为参与计算的区域是“布局区域”,布局区域可能会在绘制时被其他节点裁剪隐藏(如遇祖先节点中 overflow 样式为 hidden 的节点)或遮盖(如遇 fixed 定位的节点)。
在自定义组件或包含自定义组件的页面中,推荐使用
this.createIntersectionObserver
来代替 wx.createIntersectionObserver ,这样可以确保在正确的范围内选择节点。响应显示区域变化
显示区域尺寸
显示区域指小程序界面中可以自由布局展示的区域。在默认情况下,小程序显示区域的尺寸自页面初始化起就不会发生变化。但以下三种方式都可以改变这一默认行为。
在手机上启用屏幕旋转支持
从小程序基础库版本 2.4.0 开始,小程序在手机上支持屏幕旋转。使小程序中的页面支持屏幕旋转的方法是:在
app.json
的 window
段中设置 "pageOrientation": "auto"
,或在页面 json 文件中配置 "pageOrientation": "auto"
。以下是在单个页面 json 文件中启用屏幕旋转的示例。
代码示例:
{ "pageOrientation": "auto" }
如果页面添加了上述声明,则在屏幕旋转时,这个页面将随之旋转,显示区域尺寸也会随着屏幕旋转而变化。
在 iPad 上启用屏幕旋转支持
代码示例:
{ "resizable": true }
如果小程序添加了上述声明,则在屏幕旋转时,小程序将随之旋转,显示区域尺寸也会随着屏幕旋转而变化。注意:在 iPad 上不能单独配置某个页面是否支持屏幕旋转。
启用大屏模式
从小程序基础库版本 2.21.3 开始,在 Windows、Mac、车机、安卓 WMPF 等大屏设备上运行的小程序可以支持大屏模式。可参考小程序大屏适配指南。方法是:在
app.json
中添加 "resizable": true
。代码示例:
{
"resizable": true
}
如果小程序添加了上述声明,在大屏设备上运行时,小程序的默认窗口尺寸将会变大,同时用户可以自由拉伸。
Media Query
有时,对于不同尺寸的显示区域,页面的布局会有所差异。此时可以使用 media query 来解决大多数问题。
代码示例:
.my-class { width: 40px; } @media (min-width: 480px) { /* 仅在 480px 或更宽的屏幕上生效的样式规则 */ .my-class { width: 200px; } }
在 WXML 中,可以使用 match-media 组件来根据 media query 匹配状态展示、隐藏节点。
此外,可以在页面或者自定义组件 JS 中使用
this.createMediaQueryObserver()
方法来创建一个 MediaQueryObserver
对象,用于监听指定的 media query 的匹配状态。屏幕旋转事件
有时,仅仅使用 media query 无法控制一些精细的布局变化。此时可以使用 js 作为辅助。
在 js 中读取页面的显示区域尺寸,可以使用 selectorQuery.selectViewport 。
页面尺寸发生改变的事件,可以使用页面的
onResize
来监听。对于自定义组件,可以使用 resize 生命周期来监听。回调函数中将返回显示区域的尺寸信息。(从基础库版本 2.4.0 开始支持。)代码示例:
Page({ onResize(res) { res.size.windowWidth // 新的显示区域宽度 res.size.windowHeight // 新的显示区域高度 } })
Component({ pageLifetimes: { resize(res) { res.size.windowWidth // 新的显示区域宽度 res.size.windowHeight // 新的显示区域高度 } } })
此外,还可以使用 wx.onWindowResize 来监听(但这不是推荐的方式)。
注意事项
- Bug: Android 微信版本 6.7.3 中,
live-pusher
组件在屏幕旋转时方向异常。
分栏模式
在 PC 等能够以较大屏幕显示小程序的环境下,小程序支持以分栏模式展示。分栏模式可以将微信窗口分为左右两半,各展示一个页面。
目前, Windows 微信 3.3 以上版本支持分栏模式。对于其他版本微信,分栏模式不会生效。
启用分栏模式
在 app.json 中同时添加
"resizable": true
和 "frameset": true
两个配置项就可以启用分栏模式。代码示例:
{ "resizable": true, "frameset": true }
启用分栏模式后,可以使用开发者工具的自动预览功能来预览分栏效果。
分栏占位图片
当某一栏没有展示任何页面时,会展示一张图片在此栏正中央。
如果代码包中的
frameset/placeholder.png
文件存在,这张图片将作为此时展示的图片。分栏适配要点
启用分栏模式后,一些已有代码逻辑可能出现问题。可能需要更改代码来使其能够在分栏模式下正确运行。
避免使用更改页面展示效果的接口
更改当前页面展示效果的接口,总是对最新打开的页面生效。
例如,在右栏打开一个新页面后,更改页面标题的接口 wx.setNavigationBarTitle 即使是在左栏的页面中调用,也将更改右栏内页面的标题!
因此,应当尽量避免使用这样的接口,而是改用 page-meta 和 navigation-bar 组件代替。
变更路由接口调用
如果在路由接口中使用相对路径,总是相对于最新打开的页面路径。
例如,在右栏打开一个新页面后,路由接口 wx.navigateTo 即使是在左栏的页面中调用,跳转路径也将相对于右栏内页面的路径!
因此,应当将这样的路由接口改成 Router 接口调用,如
this.pageRouter.navigateTo
。页面大小不是固定值
启用分栏模式的同时,页面大小也是可能动态变化的了。请使用 响应显示区域变化 的方法来处理页面大小变化时的响应方式。
动画
界面动画的常见方式
动画过程中,可以使用
bindtransitionend
bindanimationstart
bindanimationiteration
bindanimationend
来监听动画事件。事件名 | 含义 |
transitionend | CSS 渐变结束或 wx.createAnimation 结束一个阶段 |
animationstart | CSS 动画开始 |
animationiteration | CSS 动画结束一个阶段 |
animationend | CSS 动画结束 |
注意:这几个事件都不是冒泡事件,需要绑定在真正发生了动画的节点上才会生效。
同时,还可以使用 wx.createAnimation 接口来动态创建简易的动画效果。(新版小程序基础库中推荐使用下述的关键帧动画接口代替。)
关键帧动画
基础库 2.9.0 开始支持,低版本需做兼容处理。
从小程序基础库 2.9.0 开始支持一种更友好的动画创建方式,用于代替旧的 wx.createAnimation 。它具有更好的性能和更可控的接口。
在页面或自定义组件中,当需要进行关键帧动画时,可以使用
this.animate
接口:this.animate(selector, keyframes, duration, callback)
参数说明
属性 | 类型 | 默认值 | 必填 | 说明 |
selector | String | ㅤ | 是 | 选择器(同 SelectorQuery.select 的选择器格式) |
keyframes | Array | ㅤ | 是 | 关键帧信息 |
duration | Number | ㅤ | 是 | 动画持续时长(毫秒为单位) |
callback | function | ㅤ | 否 | 动画完成后的回调函数 |
keyframes 中对象的结构
属性 | 类型 | 默认值 | 必填 | 说明 |
offset | Number | ㅤ | 否 | 关键帧的偏移,范围[0-1] |
ease | String | linear | 否 | 动画缓动函数 |
transformOrigin | String | 否 | 基点位置,即 CSS transform-origin | ㅤ |
backgroundColor | String | ㅤ | 否 | 背景颜色,即 CSS background-color |
bottom | Number/String | ㅤ | 否 | 底边位置,即 CSS bottom |
height | Number/String | ㅤ | 否 | 高度,即 CSS height |
left | Number/String | ㅤ | 否 | 左边位置,即 CSS left |
width | Number/String | ㅤ | 否 | 宽度,即 CSS width |
opacity | Number | ㅤ | 否 | 不透明度,即 CSS opacity |
right | Number | ㅤ | 否 | 右边位置,即 CSS right |
top | Number/String | ㅤ | 否 | 顶边位置,即 CSS top |
matrix | Array | ㅤ | 否 | 变换矩阵,即 CSS transform matrix |
matrix3d | Array | ㅤ | 否 | 三维变换矩阵,即 CSS transform matrix3d |
rotate | Number | ㅤ | 否 | 旋转,即 CSS transform rotate |
rotate3d | Array | ㅤ | 否 | 三维旋转,即 CSS transform rotate3d |
rotateX | Number | ㅤ | 否 | X 方向旋转,即 CSS transform rotateX |
rotateY | Number | ㅤ | 否 | Y 方向旋转,即 CSS transform rotateY |
rotateZ | Number | ㅤ | 否 | Z 方向旋转,即 CSS transform rotateZ |
scale | Array | ㅤ | 否 | 缩放,即 CSS transform scale |
scale3d | Array | ㅤ | 否 | 三维缩放,即 CSS transform scale3d |
scaleX | Number | ㅤ | 否 | X 方向缩放,即 CSS transform scaleX |
scaleY | Number | ㅤ | 否 | Y 方向缩放,即 CSS transform scaleY |
scaleZ | Number | ㅤ | 否 | Z 方向缩放,即 CSS transform scaleZ |
skew | Array | ㅤ | 否 | 倾斜,即 CSS transform skew |
skewX | Number | ㅤ | 否 | X 方向倾斜,即 CSS transform skewX |
skewY | Number | ㅤ | 否 | Y 方向倾斜,即 CSS transform skewY |
translate | Array | ㅤ | 否 | 位移,即 CSS transform translate |
translate3d | Array | ㅤ | 否 | 三维位移,即 CSS transform translate3d |
translateX | Number | ㅤ | 否 | X 方向位移,即 CSS transform translateX |
translateY | Number | ㅤ | 否 | Y 方向位移,即 CSS transform translateY |
translateZ | Number | ㅤ | 否 | Z 方向位移,即 CSS transform translateZ |
示例代码
this.animate('#container', [ { opacity: 1.0, rotate: 0, backgroundColor: '#FF0000' }, { opacity: 0.5, rotate: 45, backgroundColor: '#00FF00'}, { opacity: 0.0, rotate: 90, backgroundColor: '#FF0000' }, ], 5000, function () { this.clearAnimation('#container', { opacity: true, rotate: true }, function () { console.log("清除了#container上的opacity和rotate属性") }) }.bind(this)) this.animate('.block', [ { scale: [1, 1], rotate: 0, ease: 'ease-out' }, { scale: [1.5, 1.5], rotate: 45, ease: 'ease-in', offset: 0.9}, { scale: [2, 2], rotate: 90 }, ], 5000, function () { this.clearAnimation('.block', function () { console.log("清除了.block上的所有动画属性") }) }.bind(this))
调用 animate API 后会在节点上新增一些样式属性覆盖掉原有的对应样式。如果需要清除这些样式,可在该节点上的动画全部执行完毕后使用
this.clearAnimation
清除这些属性。this.clearAnimation(selector, options, callback)
参数说明
属性 | 类型 | 默认值 | 必填 | 说明 |
selector | String | ㅤ | 是 | 选择器(同 SelectorQuery.select 的选择器格式) |
options | Object | ㅤ | 否 | 需要清除的属性,不填写则全部清除 |
callback | Function | ㅤ | 否 | 清除完成后的回调函数 |
滚动驱动的动画
我们发现,根据滚动位置而不断改变动画的进度是一种比较常见的场景,这类动画可以让人感觉到界面交互很连贯自然,体验更好。因此,从小程序基础库 2.9.0 开始支持一种由滚动驱动的动画机制。
基于上述的关键帧动画接口,新增一个
ScrollTimeline
的参数,用来绑定滚动元素(目前只支持 scroll-view)。接口定义如下:this.animate(selector, keyframes, duration, ScrollTimeline)
ScrollTimeline 中对象的结构
属性 | 类型 | 默认值 | 必填 | 说明 |
scrollSource | String | ㅤ | 是 | 指定滚动元素的选择器(只支持 scroll-view),该元素滚动时会驱动动画的进度 |
orientation | String | vertical | 否 | 指定滚动的方向。有效值为 horizontal 或 vertical |
startScrollOffset | Number | ㅤ | 是 | 指定开始驱动动画进度的滚动偏移量,单位 px |
endScrollOffset | Number | ㅤ | 是 | 指定停止驱动动画进度的滚动偏移量,单位 px |
timeRange | Number | ㅤ | 是 | 起始和结束的滚动范围映射的时间长度,该时间可用于与关键帧动画里的时间 (duration) 相匹配,单位 ms |
示例代码
this.animate('.avatar', [{ borderRadius: '0', borderColor: 'red', transform: 'scale(1) translateY(-20px)', offset: 0, }, { borderRadius: '25%', borderColor: 'blue', transform: 'scale(.65) translateY(-20px)', offset: .5, }, { borderRadius: '50%', borderColor: 'blue', transform: `scale(.3) translateY(-20px)`, offset: 1 }], 2000, { scrollSource: '#scroller', timeRange: 2000, startScrollOffset: 0, endScrollOffset: 85, }) this.animate('.search_input', [{ opacity: '0', width: '0%', }, { opacity: '1', width: '100%', }], 1000, { scrollSource: '#scroller', timeRange: 1000, startScrollOffset: 120, endScrollOffset: 252 })
高级的动画方式
在一些复杂场景下,上述的动画方法可能并不适用。
WXS 响应事件 的方式可以通过使用 WXS 来响应事件的方法来动态调整节点的 style 属性。通过不断改变 style 属性的值可以做到动画效果。同时,这种方式也可以根据用户的触摸事件来动态地生成动画。
连续使用 setData 来改变界面的方法也可以达到动画的效果。这样可以任意地改变界面,但通常会产生较大的延迟或卡顿,甚至导致小程序僵死。此时可以通过将页面的 setData 改为 自定义组件 中的 setData 来提升性能。下面的例子是使用 setData 来实现秒表动画的示例。
初始渲染缓存
基础库 2.11.1 开始支持,低版本需做兼容处理。
初始渲染缓存工作原理
小程序页面的初始化分为两个部分。
- 逻辑层初始化:载入必需的小程序代码、初始化页面 this 对象(也包括它涉及到的所有自定义组件的 this 对象)、将相关数据发送给视图层。
- 视图层初始化:载入必需的小程序代码,然后等待逻辑层初始化完毕并接收逻辑层发送的数据,最后渲染页面。
在启动页面时,尤其是小程序冷启动、进入第一个页面时,逻辑层初始化的时间较长。在页面初始化过程中,用户将看到小程序的标准载入画面(冷启动时)或可能看到轻微的白屏现象(页面跳转过程中)。
启用初始渲染缓存,可以使视图层不需要等待逻辑层初始化完毕,而直接提前将页面初始 data 的渲染结果展示给用户,这可以使得页面对用户可见的时间大大提前。它的工作原理如下:
- 在小程序页面第一次被打开后,将页面初始数据渲染结果记录下来,写入一个持久化的缓存区域(缓存可长时间保留,但可能因为小程序更新、基础库更新、储存空间回收等原因被清除);
- 在这个页面被第二次打开时,检查缓存中是否还存有这个页面上一次初始数据的渲染结果,如果有,就直接将渲染结果展示出来;
- 如果展示了缓存中的渲染结果,这个页面暂时还不能响应用户事件,等到逻辑层初始化完毕后才能响应用户事件。
利用初始渲染缓存,可以:
- 快速展示出页面中永远不会变的部分,如导航栏;
- 预先展示一个骨架页,提升用户体验;
- 展示自定义的加载提示;
- 提前展示广告,等等。
支持的组件
在初始渲染缓存阶段中,复杂组件不能被展示或不能响应交互。
目前支持的内置组件:
<view />
<text />
<button />
<image />
<scroll-view />
<rich-text />
自定义组件本身可以被展示(但它们里面用到的内置组件也遵循上述限制)。
静态初始渲染缓存
若想启用初始渲染缓存,最简单的方法是在页面的
json
文件中添加配置项 "initialRenderingCache": "static"
:{ "initialRenderingCache": "static" }
如果想要对所有页面启用,可以在
app.json
的 window
配置段中添加这个配置:{ "window": { "initialRenderingCache": "static" } }
添加这个配置项之后,在手机中预览小程序首页,然后杀死小程序再次进入,就会通过初始渲染缓存来渲染首页。
注意:这种情况下,初始渲染缓存记录的是页面 data 应用在页面 WXML 上的结果,不包含任何 setData 的结果。
例如,如果想要在页面中展示出“正在加载”几个字,这几个字受到
loading
数据字段控制:<view wx:if="{{loading}}">正在加载</view>
这种情况下,
loading
应当在 data
中指定为 true
,如:// 正确的做法 Page({ data: { loading: true } })
而不能通过
setData
将 loading
置为 true
:// 错误的做法!不要这么做! Page({ data: {}, onLoad: function() { this.setData({ loading: true }) } })
换而言之,这种做法只包含页面
data
的渲染结果,即页面的纯静态成分。在初始渲染缓存中添加动态内容
有些场景中,只是页面
data
的渲染结果会比较局限。有时会想要额外展示一些可变的内容,如展示的广告图片 URL 等。这种情况下可以使用“动态”初始渲染缓存的方式。首先,配置
"initialRenderingCache": "dynamic"
:{ "initialRenderingCache": "dynamic" }
此时,初始渲染缓存不会被自动启用,还需要在页面中调用
this.setInitialRenderingCache(dynamicData)
才能启用。其中, dynamicData
是一组数据,与 data
一起参与页面 WXML 渲染。Page({ data: { loading: true }, onReady: function() { this.setInitialRenderingCache({ loadingHint: '正在加载' // 这一部分数据将被应用于界面上,相当于在初始 data 基础上额外进行一次 setData }) } })
<view wx:if="{{loading}}">{{loadingHint}}</view>
从原理上说,在动态生成初始渲染缓存的方式下,页面会在后台使用动态数据重新渲染一次,因而开销相对较大。因而要尽量避免频繁调用
this.setInitialRenderingCache
,如果在一个页面内多次调用,仅最后一次调用生效。注意:
this.setInitialRenderingCache
调用时机不能早于Page
的onReady
或Component
的ready
生命周期,否则可能对性能有负面影响。
- 如果想禁用初始渲染缓存,调用
this.setInitialRenderingCache(null)
。