❤️

微信小程序

代码构成

项目里边生成了不同类型的文件:
  1. .json 后缀的 JSON 配置文件
  1. .wxml 后缀的 WXML 模板文件
  1. .wxss 后缀的 WXSS 样式文件
  1. .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" } }
我们简单说一下这个配置各个项的含义:
  1. pages字段 —— 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。
  1. 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。
  1. 数字,包含浮点数和整数
  1. 字符串,需要包裹在双引号中
  1. Bool值,true 或者 false
  1. 数组,需要包裹在方括号中 []
  1. 对象,需要包裹在大括号中 {}
  1. 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 由标签、属性等等构成。但是也有很多不一样的地方,我们来一一阐述一下:
  1. 标签名字有点不一样
    1. 往往写 HTML 的时候,经常会用到的标签是 divpspan,开发者在写一个页面的时候可以根据这些基础的标签组合出不一样的组件,例如日历、弹窗等等。换个思路,既然大家都需要这些组件,为什么我们不能把这些常用的组件包装起来,大大提高我们的开发效率。
      从上边的例子可以看到,小程序的 WXML 用的标签是 viewbuttontext 等等,这些标签就是小程序给开发者包装好的基本能力,我们还提供了地图、视频、音频等等组件能力。
      更多详细的组件讲述参考下个章节 小程序的能力
  1. 多了一些 wx:if 这样的属性以及 {{ }} 这样的表达式
    1. 在网页的一般开发流程中,我们通常会通过 JS 操作 DOM (对应 HTML 的描述产生的树),以引起界面的一些变化响应用户的行为。例如,用户点击某个按钮的时候,JS 会记录一些状态到 JS 变量里边,同时通过 DOM API 操控 DOM 的属性或者行为,进而引起界面一些变化。当项目越来越大的时候,你的代码会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式,因此就有了 MVVM 的开发模式(例如 React, Vue),提倡把渲染和逻辑分离。简单来说就是不要再让 JS 直接操控 DOMJS 只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可。
      小程序的框架也是用到了这个思路,如果你需要把一个 Hello World 的字符串显示在界面上。
      WXML 是这么写 :
      <text>{{msg}}</text>
      JS 只需要管理状态即可:
      this.setData({ msg: "Hello World" })
      通过 {{ }} 的语法把一个变量绑定到界面上,我们称为数据绑定。仅仅通过数据绑定还不够完整的描述状态和界面的关系,还需要 if/elsefor等控制能力,在小程序里边,这些控制能力都用 wx: 开头的属性来表达。
更详细的文档可以参考 WXML

WXSS 样式

WXSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。
  1. 新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
  1. 提供了全局的样式和局部样式。和前边 app.jsonpage.json 的概念相同,你可以写一个 app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效。
  1. 此外 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转发,小程序的通信模型下图所示。
notion image
有关渲染层和逻辑层的详细文档参考 小程序框架 。

程序与页面

微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地。
紧接着通过 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 的 divp 等标签一样,在小程序里边,你只需要在 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 中获取上述场景值。

返回来源信息的场景

部分场景值下还可以获取来源应用、公众号或小程序的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 中一些能力都无法使用,如 windowdocument 等。

注册小程序

每个小程序都需要在 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 等高级特性。
具体细节请阅读 Component 构造器 章节。

生命周期

以下内容你不需要立马完全弄明白,不过以后它会有帮助。
下图说明了页面 Page 实例的生命周期。
notion image

页面路由

在小程序中所有页面的路由全部由框架进行管理。
页面栈
框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,页面栈的表现如下:
路由方式
页面栈表现
初始化
新页面入栈
打开新页面
新页面入栈
页面重定向
当前页面出栈,新页面入栈
页面返回
页面不断出栈,直到目标返回页
Tab 切换
页面全部出栈,只留下新的 Tab 页面
重加载
页面全部出栈,只留下新的页面
开发者可以使用 getCurrentPages() 函数获取当前页面栈。
路由方式
对于路由的触发方式以及页面生命周期函数如下:
路由方式
触发时机
路由前页面
路由后页面
初始化
小程序打开的第一个页面
onLoad, onShow
打开新页面
onHide
onLoad, onShow
页面重定向
onUnload
onLoad, onShow
页面返回
调用 API wx.navigateBack使用组件<navigator open-type="navigateBack">用户按左上角返回按钮
onUnload
onShow
Tab 切换
调用 API wx.switchTab使用组件 <navigator open-type="switchTab"/>用户切换 Tab
各种情况请参考下表
重启动
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()
注意事项
  • navigateToredirectTo 只能打开非 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
我们约定,以 on 开头的 API 用来监听某个事件是否触发,如:wx.onSocketOpenwx.onCompassChange 等。
这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。
代码示例
wx.onCompassChange(function (res) { console.log(res.direction) })
同步 API
我们约定,以 Sync 结尾的 API 都是同步 API, 如 wx.setStorageSyncwx.getSystemInfoSync 等。此外,也有一些其他的同步 API,如 wx.createWorkerwx.getBackgroundAudioManager 等,详情参见 API 文档中的说明。
同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。
代码示例
try { wx.setStorageSync('key', 'value') } catch (e) { console.error(e) }
异步 API
大多数 API 都是异步 API,如 wx.requestwx.login 等。这类 API 接口通常都接受一个 Object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果:
Object 参数说明
参数名
类型
必填
说明
success
function
接口调用成功的回调函数
fail
function
接口调用失败的回调函数
complete
function
接口调用结束的回调函数(调用成功、失败都会执行)
其他
Any
-
接口定义的其他参数
回调函数的参数
successfailcomplete 函数调用时会传入一个 Object 类型参数,包含以下字段:
属性
类型
说明
errMsg
string
错误信息,如果调用成功返回 ${apiName}:ok
errCode
number
错误码,仅部分 API 支持,具体含义请参考对应 API 文档,成功时为 0
其他
Any
接口返回的其他数据
异步 API 的执行结果需要通过 Object 类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能,如 wx.requestwx.connectSocket 等。
代码示例
wx.login({ success(res) { console.log(res.code) } })
异步 API 返回 Promise
基础库 2.10.2 版本起,异步 API 支持 callback & promise 两种调用方式。当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 promise,否则仍按回调方式执行,无返回值。
注意事项
  1. 部分接口如 downloadFilerequestuploadFileconnectSocketcreateCamera(小游戏)本身就有返回值, 它们的 promisify 需要开发者自行封装。
  1. 当没有回调参数时,异步接口返回 promise。此时若函数调用失败进入 fail 逻辑, 会报错提示 Uncaught (in promise),开发者可通过 catch 来进行捕获。
  1. wx.onUnhandledRejection 可以监听未处理的 Promise 拒绝事件。
代码示例
// callback 形式调用 wx.chooseImage({ success(res) { console.log('res:', res) } }) // promise 形式调用 wx.chooseImage().then(res => console.log('res: ', res))
云开发 API
开通并使用微信云开发,即可使用云开发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(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件事件系统,可以构建出页面的结构。
要完整了解 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函数响应事件,请点击查看详情
事件分类
事件分为冒泡事件和非冒泡事件:
  1. 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
  1. 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
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 设备,重按时会触发
注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的submit事件,input 的input事件,scroll-view 的scroll事件,(详见各个组件)
普通事件绑定
事件绑定的写法类似于组件的属性,如:
<view bindtap="handleTap"> Click here! </view>
如果用户点击这个 view ,则页面的 handleTap 会被调用。
事件绑定函数可以是一个数据绑定,如:
<view bindtap="{{ handlerName }}"> Click here! </view>
此时,页面的 this.data.handlerName 必须是一个字符串,指定事件处理函数名;如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)。
自基础库版本 1.5.0 起,在大多数组件和自定义组件中, bind 后可以紧跟一个冒号,其含义不变,如 bind:tap 。基础库版本 2.8.1 起,在所有组件中开始提供这个支持。
绑定并阻止事件冒泡
除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。
例如在下边这个例子中,点击 inner view 会先后调用handleTap3handleTap2(因为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-bindcapture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。
在下面的代码中,点击 inner view 会先后调用handleTap2handleTap4handleTap3handleTap1
<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
在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点。此外, mark 还可以用于承载一些自定义数据(类似于 dataset )。
当事件触发时,事件冒泡路径上所有的 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}}" />
这样,如果输入框的值被改变了, this.data.value 也会同时改变。同时, WXML 中所有绑定了 value 的位置也会被一同更新, 数据监听器 也会被正常触发。
用于双向绑定的表达式有如下限制:
  1. 只能是一个单一字段的绑定,如
<input model:value="值为 {{value}}" /> <input model:value="{{ a + b }}" />
都是非法的;
  1. 目前,尚不能 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
数字
12.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" }
如果页面添加了上述声明,则在屏幕旋转时,这个页面将随之旋转,显示区域尺寸也会随着屏幕旋转而变化。
从小程序基础库版本 2.5.0 开始, pageOrientation 还可以被设置为 landscape ,表示固定为横屏显示。
在 iPad 上启用屏幕旋转支持
从小程序基础库版本 2.3.0 开始,在 iPad 上运行的小程序可以支持屏幕旋转。使小程序支持 iPad 屏幕旋转的方法是:在 app.json 中添加 "resizable": true 。
代码示例:
{ "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 等能够以较大屏幕显示小程序的环境下,小程序支持以分栏模式展示。分栏模式可以将微信窗口分为左右两半,各展示一个页面。
notion image
目前, 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 。
页面大小不是固定值
启用分栏模式的同时,页面大小也是可能动态变化的了。请使用 响应显示区域变化 的方法来处理页面大小变化时的响应方式。

动画

界面动画的常见方式
在小程序中,通常可以使用 CSS 渐变 和 CSS 动画 来创建简易的界面动画。
动画过程中,可以使用 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) 。