小程序宿主环境

渲染层和逻辑层

  小程序的运行环境分成渲染层和逻辑层:WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。渲染层和数据相关;逻辑层负责产生、处理数据,通过 Page 实例的 data 属性传递数据到渲染层。

通信模型

  小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型如下图所示:

小程序的通信模型

程序与页面

  从逻辑组成来说,一个小程序是由多个“页面”组成的“程序”。

程序

  “小程序”指的是产品层面的程序,而“程序”指的是代码层面的程序实例。

程序构造器App()

  宿主环境提供了 App() 构造器用来注册一个程序App。App() 构造器必须写在项目根目录的app.js里,App实例是单例对象,在其他JS脚本中可以使用宿主环境提供的 getApp() 来获取程序实例。

1 // other.js
2 var appInstance = getApp()

  App() 的调用方式如上所示,App构造器接受一个Object参数,参数说明如下:

1 App({
2   onLaunch: function(options) {},
3   onShow: function(options) {},
4   onHide: function() {},
5   onError: function(msg) {},
6   globalData: \'I am global data\'
7 })

  其中onLaunch / onShow / onHide 三个回调是App实例的生命周期函数;当小程序发生脚本错误,或者 API 调用失败时,会触发 onError 方法并带上错误信息;globalData 用于存放App实例的数据,可以添加任意的函数或数据到 Object 参数中,在App实例回调用 this 可以访问。

程序的生命周期和打开场景

  • 初次进入小程序的时候,微信客户端初始化好宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境,初始化完毕后,微信客户端就会给App实例派发onLaunch事件,App构造器参数所定义的onLaunch方法会被调用
  • 进入小程序之后,用户可以点击右上角的关闭,或者按手机设备的Home键离开小程序,此时小程序并没有被直接销毁,我们把这种情况称为“小程序进入后台状态”,App构造器参数所定义的onHide方法会被调用
  • 当再次回到微信或者再次打开小程序时,微信客户端会把“后台”的小程序唤醒,我们把这种情况称为“小程序进入前台状态”,App构造器参数所定义的onShow方法会被调用

  为了避免程序上的混乱,我们不应该从其他代码里主动调用App实例的生命周期函数。
  在微信客户端中打开小程序有很多途径:从群聊会话里打开,从小程序列表中打开,通过微信扫一扫二维码打开,从另外一个小程序打开当前小程序等,针对不同途径的打开方式,小程序有时需要做不同的业务处理,所以微信客户端会把打开方式带给onLaunch和onShow的调用参数options。要获取最新的场景值说明请查看官方文档:https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/app.html

1 App({
2   onLaunch: function(options) { console.log(options) },
3   onShow: function(options) { console.log(options) }
4 })

  options属性说明如下:

属性 类型 说明
path String 打开小程序的页面路径
query Object 打开小程序的页面参数query
scene Number 打开小程序的场景值,详细场景值请参考小程序官方文档
shareTicket String shareTicket,详见小程序官方文档
referrerInfo Object 当场景为由从另一个小程序或公众号或App打开时,返回此字段
referrerInfo.appId String 来源小程序或公众号或App的 appId,详见下方说明
referrerInfo.extraData Object 来源小程序传过来的数据,scene=1037或1038时支持

全局数据

  前边我们提到,小程序的JS脚本是运行在JsCore的线程里,小程序的每个页面各自有一个WebView线程进行渲染,所以小程序切换页面时,小程序逻辑层的JS脚本运行上下文依旧在同一个JsCore线程中。同时App实例又是单例的,因此不同页面直接可以通过App实例下的属性来共享数据。

1 // app.js
2 App({
3   globalData: \'I am global data\' // 全局共享数据
4 })
5 // 其他页面脚本other.js
6 var appInstance = getApp()
7 console.log(appInstance.globalData) // 输出: I am global data

  注意:所有页面的脚本逻辑都跑在同一个JsCore线程,页面使用setTimeout或者setInterval的定时器,然后跳转到其他页面时,这些定时器并没有被清除,需要开发者自己在页面离开的时候进行清理。

页面

  一个小程序可以有很多页面,每个页面承载不同的功能,页面之间可以互相跳转。

文件构成和路径

  一个页面是分三部分组成:界面、配置和逻辑。界面由WXML文件和WXSS文件来负责描述,配置由JSON文件进行描述,页面逻辑则是由JS脚本文件负责。一个页面的文件需要放置在同一个目录下,其中WXML文件和JS文件是必须存在的,JSON和WXSS文件是可选的

  页面路径需要在小程序代码根目录app.json中的pages字段声明,否则这个页面不会被注册到宿主环境中。脚本中的路径采用相对路径,app.json的pages字段的代码路径以pages文件夹开始。

1 {
2   "pages":[
3     "pages/index/page", // 第一项默认为首页
4     "pages/other/other"
5   ]
6 }

页面构造器Page()

  宿主环境提供了 Page() 构造器用来注册一个小程序页面,Page()在页面脚本page.js中调用。Page构造器接受一个Object参数,参数说明如下:

 1 Page({
 2   data: { text: "This is page data." },
 3   onLoad: function(options) { },
 4   onReady: function() { },
 5   onShow: function() { },
 6   onHide: function() { },
 7   onUnload: function() { },
 8   onPullDownRefresh: function() { },
 9   onReachBottom: function() { },
10   onShareAppMessage: function () { },
11   onPageScroll: function() { }
12 })

  其中data属性是当前页面WXML模板中可以用来做数据绑定的初始数据;onLoad / onReady / onShow / onHide /onUnload 5个回调是Page实例的生命周期函数;onPullDownRefresh / onReachBottom / onShareAppMessage / onPageScroll 4个回调是页面的用户行为。具体说明如下表:

参数属性 类型 描述
data Object 页面的初始数据
onLoad Function 生命周期函数–监听页面加载,触发时机早于onShow和onReady
onReady Function 生命周期函数–监听页面初次渲染完成
onShow Function 生命周期函数–监听页面显示,触发事件早于onReady
onHide Function 生命周期函数–监听页面隐藏
onUnload Function 生命周期函数–监听页面卸载
onPullDownRefresh Function 页面相关事件处理函数–监听用户下拉动作
onReachBottom Function 页面上拉触底事件的处理函数
onShareAppMessage Function 用户点击右上角转发
onPageScroll Function 页面滚动触发事件的处理函数
其他 Any 可以添加任意的函数或数据,在Page实例的其他函数中用 this 可以访问

页面的生命周期和打开参数

  • 页面初次加载的时候,微信客户端就会给Page实例派发onLoad事件,Page构造器参数所定义的onLoad方法会被调用,onLoad在页面没被销毁之前只会触发1次在onLoad的回调中,可以获取当前页面所调用的打开参数option
  • 页面显示之后,Page构造器参数所定义的onShow方法会被调用,一般从别的页面返回到当前页面时,当前页的onShow方法也会被调用
  • 页面初次渲染完成时,Page构造器参数所定义的onReady方法会被调用onReady在页面没被销毁前只会触发1次onReady触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。

  以上三个事件触发的时机是onLoad早于 onShowonShow早于onReady

  • 页面不可见时,Page构造器参数所定义的onHide方法会被调用,这种情况会在使用wx.navigateTo切换到其他页面、底部tab切换时触发
  • 当前页面使用wx.redirectTo或wx.navigateBack返回到其他页时当前页面会被微信客户端销毁回收,此时Page构造器参数所定义的onUnload方法会被调用

页面的打开参数query

  用于传递上一个页面传递到下一个页面的参数

 1 // pages/list/list.js
 2 // 列表页使用navigateTo跳转到详情页
 3 wx.navigateTo({ url: \'pages/detail/detail?id=1&other=abc\' })
 4 
 5 // pages/detail/detail.js
 6 Page({
 7   onLoad: function(option) {
 8         console.log(option.id)
 9         console.log(option.other)
10   }
11 })

  页面的打开路径定义被定义为页面URL,其组成格式和网页的URL类似,在页面路径后使用英文 ? 分隔path和query部分,query部分的多个参数使用 & 进行分隔,参数的名字和值使用 key=value 的形式声明。在页面Page构造器里onLoad的option可以拿到当前页面的打开参数,其类型是一个Object,其键值对与页面URL上query键值对一一对应。和网页URL一样,页面URL上的value如果涉及特殊字符(例如:&字符、?字符、中文字符等,详情参考URI的RFC3986说明 ),需要采用UrlEncode后再拼接到页面URL上。

页面的数据

  WXML可以通过数据绑定的语法绑定从逻辑层传递过来的数据字段,来自于页面Page构造器的data字段,data参数是页面第一次渲染时从逻辑层传递到渲染层的数据。

  宿主环境所提供的Page实例的原型中有setData函数,我们可以在Page实例下的方法调用this.setData把数据传递给渲染层,从而达到更新界面的目的。由于小程序的渲染层和逻辑层分别在两个线程中运行,所以setData传递数据实际是一个异步的过程,所以setData的第二个参数是一个callback回调,在这次setData对界面渲染完毕后触发。setData其一般调用格式是 setData(data, callback),其中data是由多个key: value构成的Object对象。

 1 // page.js
 2 Page({
 3   onLoad: function(){
 4     this.setData({
 5       text: \'change data\'
 6     }, function(){
 7       // 在这次setData对界面渲染完毕后触发
 8     })
 9   }
10 })

  实际开发中,通常只需要设置需要改变的属性即可。

 1 // page.js
 2 Page({
 3   data: {
 4     a: 1, b: 2, c: 3,
 5     d: [1, {text: \'Hello\'}, 3, 4]
 6   }
 7   onLoad: function(){
 8        // a需要变化时,只需要setData设置a字段即可
 9     this.setData({a : 2})
10   }
11 })
  • 直接修改 Page实例的this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致;
  • 由于setData是需要两个线程的一些通信消耗,为了提高性能,每次设置的数据不应超过1024kB;
  • 不要把data中的任意一项的value设为undefined,否则可能会有引起一些不可预料的bug;

页面的用户行为

  宿主环境提供了四个和页面相关的用户行为回调:

    1. 下拉刷新 onPullDownRefresh
      监听用户下拉刷新事件,需要在app.json的window选项中或页面配置page.json中设置enablePullDownRefresh为true。当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。
    2. 上拉触底 onReachBottom
      监听用户上拉触底事件。可以在app.json的window选项中或页面配置page.json中设置触发距离onReachBottomDistance。在触发距离内滑动期间,本事件只会被触发一次。
    3. 页面滚动 onPageScroll
      监听用户滑动页面事件,参数为 Object,包含 scrollTop 字段,表示页面在垂直方向已滚动的距离(单位px)。
    4. 用户转发 onShareAppMessage
      只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮,在用户点击转发按钮的时候会调用,此事件需要return一个Object,包含title和path两个字段,用于自定义转发内容。

 

 

页面跳转和路由

  一个小程序拥有多个页面,我们可以通过wx.navigateTo推入一个新的页面。在首页使用2次wx.navigateTo后,页面层级会有三层,我们把这样的一个页面层级称为页面栈。如下图所示:

使用2次wx.navigateTo后的页面栈

  为了表述方便,我们采用这样的方式进行描述页面栈:[ pageA, pageB, pageC ],其中pageA在最底下,pageC在最顶上。宿主环境限制了页面栈的最大层级为10层,也就是当页面栈到达10层之后就没有办法再推入新的页面了。

  使用 wx.navigateTo({ url: \’pageD\’ }) 可以往当前页面栈多推入一个 pageD,此时页面栈变成 [ pageA, pageB, pageC, pageD ]。
  使用 wx.navigateBack() 可以退出当前页面栈的最顶上页面,此时页面栈变成 [ pageA, pageB, pageC ]。
  使用wx.redirectTo({ url: \’pageE\’ }) 是替换当前页变成pageE,此时页面栈变成 [ pageA, pageB, pageE ],当页面栈到达10层没法再新增的时候,往往就是使用redirectTo这个API进行页面跳转

  小程序提供了原生的Tabbar支持,我们可以在app.json声明tabBar字段来定义Tabbar页(注:更多详细参数见Tabbar官方文档 )。

1 {
2   "tabBar": {
3     "list": [
4       { "text": "Tab1", "pagePath": "pageA" },
5       { "text": "Tab1", "pagePath": "pageF" },
6       { "text": "Tab1", "pagePath": "pageG" }
7     ]
8   }
9 }

  在刚刚的例子所在的页面栈中使用wx.switchTab({ url: \’pageF\’ }),此时原来的页面栈会被清空(除了已经声明为Tabbar页pageA外其他页面会被销毁),然后会切到pageF所在的tab页面,页面栈变成 [ pageF ],此时点击Tab1切回到pageA时,pageA不会再触发onLoad,因为pageA没有被销毁。

   wx.navigateTo和wx.redirectTo只能打开非TabBar页面,wx.switchTab只能打开Tabbar页面。我们还可以使用 wx. reLaunch({ url: \’pageH\’ }) 重启小程序,并且打开pageH,此时页面栈为 [ pageH ]。

路由方式 触发时机 路由前页面生命周期 路由后页面生命周期
初始化 小程序打开的第一个页面   onLoad, onShow
打开新页面 调用 API wx.navigateTo onHide onLoad, onShow
页面重定向 调用 API wx.redirectTo onUnload onLoad, onShow
页面返回 调用 API wx.navigateBack onUnload onShow
Tab 切换 调用 API wx.switchTab 请参考表3-6 请参考表3-6
重启动 调用 API wx.reLaunch onUnload onLoad, onShow

组件

  组件就是小程序页面的基本组成单元。组件是在WXML模板文件声明中使用的,WXML的语法和HTML语法相似,小程序使用标签名来引用一个组件,通常包含开始标签和结束标签,该标签的属性用来描述该组件。

1 <!-- page.wxml -->
2 <image mode="scaleToFill" src="img.png"></image>

  所有组件都拥有下表列举的属性:

组件共有属性
属性名 类型 描述 其他说明
id String 组件的唯一标示 保持整个页面唯一
class String 组件的样式类 在对应的WXSS中定义的样式类
style String 组件的内联样式 可以通过数据绑定进行动态设置的内联样式
hidden Boolean 组件是否显示 所有组件默认显示
data-* Any 自定义属性 组件上触发的事件时,会发送给事件处理函数
bind / catch EventHandler 事件 详情见3.5节

  更多相关属性,请在使用时前往官方文档进行查阅相关组件说明 https://mp.weixin.qq.com/debug/wxadoc/dev/component/

官方API

  几乎所有小程序的API都挂载在wx对象(小程序的宿主环境所提供的全局对象)底下(除了Page/App等特殊的构造器),所以本书谈到API概念时,通常指的是wx对象底下的方法。

  小程序提供的API按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口,我们介绍一下API一般调用的约定:

  1. wx.on* 开头的 API 是监听某个事件发生的API接口,接受一个 Callback 函数作为参数。当该事件触发时,会调用 Callback 函数。
  2. 如未特殊约定,多数 API 接口为异步接口 ,都接受一个Object作为参数。
  3. API的Object参数一般由success、fail、complete三个回调来接收接口调用结果,示例代码如代码清单3-17所示,详细说明如表3-9所示。
  4. wx.get* 开头的API是获取宿主环境数据的接口。
  5. wx.set* 开头的API是写入数据到宿主环境的接口。
 1 wx.request({
 2 url: \'test.php\',
 3 data: {},
 4 header: { \'content-type\': \'application/json\' },
 5 success: function(res) {
 6  // 收到https服务成功后返回
 7  console.log(res.data)
 8 },
 9 fail: function() {
10  // 发生网络错误等情况触发
11 },
12 complete: function() {
13  // 成功或者失败后触发
14 }
15 })

  可以在官方API文档 https://mp.weixin.qq.com/debug/wxadoc/dev/api/了解到对应的API参数细节。

事件

  我们把“用户在渲染层的行为反馈”以及“组件的部分状态反馈”抽象为渲染层传递给逻辑层的“事件”。

渲染层产生用户交互事件传递给逻辑层

事件类型和事件对象

  常见的事件类型:

类型 触发条件
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 动画完成时触发

 

  当事件回调触发的时候,会收到一个事件对象,对象的详细属性如下表所示:

属性 类型 说明
type String 事件类型
timeStamp Integer 页面打开到触发事件所经过的毫秒数
target Object 触发事件的组件的一些属性值集合
currentTarget Object 当前组件的一些属性值集合
detail Object 额外的信息
touches Array 触摸事件,当前停留在屏幕中的触摸点信息的数组
changedTouches Array 触摸事件,当前变化的触摸点信息的数组

事件绑定与冒泡捕获

 

事件绑定的写法和组件属性一致,以key=”value”的形式,其中:

  • key以bind或者catch开头,然后跟上事件的类型,如bindtap、catchtouchstart。自基础库版本1.5.0起,bind和catch后可以紧跟一个冒号,其含义不变,如bind:tap、catch:touchstart。同时bind和catch前还可以加上capture-来表示捕获阶段。
  • value是一个字符串,需要在对应的页面Page构造器中定义同名的函数,否则触发事件时在控制台会有报错信息。
  • bind和capture-bind的含义分别代表事件的冒泡阶段和捕获阶段

 

  以下示例中,点击 inner view 会先后调用handleTap1、handleTap2、handleTap3、handleTap4。

1 <view id="outer" bind:tap="handleTap4" capture-bind:tap="handleTap1">
2   outer view
3   <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2">
4     inner view
5   </view>
6 </view>

  bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。如果将以上代码的capture-bind:tap=”handleTap1″改成capture-catch:tap=”handleTap1″,点击inner view只会触发handleTap1(catch事件阻止了tap事件冒泡)。

1 <view id="outer" bind:tap="handleTap4" capture-catch:tap="handleTap1">
2   outer view
3   <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2">
4     inner view
5   </view>
6 </view>

注意:除前边表中列举的事件类型之外的其他组件自定义事件,如无特殊声明都是非冒泡事件,如<form/>的submit事件,<input/>的input事件,<scroll-view/>的scroll事件。

兼容

  针对不同手机进行程序上的兼容,此时可以使用 wx.getSystemInfo 或者 wx.getSystemInfoSync 来获取手机品牌、操作系统版本号、微信版本号以及小程序基础库版本号等,通过这个信息,我们可以针对不同平台做差异化的服务。

  可以通过wx.getSystemInfoSync获取宿主环境信息:

 1 wx.getSystemInfoSync()
 2 /*
 3   {
 4     brand: "iPhone",      // 手机品牌
 5     model: "iPhone 6",    // 手机型号
 6     platform: "ios",      // 客户端平台
 7     system: "iOS 9.3.4",  // 操作系统版本
 8     version: "6.5.23",    // 微信版本号
 9     SDKVersion: "1.7.0",  // 小程序基础库版本
10     language: "zh_CN",    // 微信设置的语言
11     pixelRatio: 2,        // 设备像素比
12     screenWidth: 667,    // 屏幕宽度
13     screenHeight: 375,     // 屏幕高度
14     windowWidth: 667,    // 可使用窗口宽度
15     windowHeight: 375,     // 可使用窗口高度
16     fontSizeSetting: 16   // 用户字体大小设置
17   }
18  */

  通过判断API是否存在做兼容:

1 if (wx.openBluetoothAdapter) {
2   wx.openBluetoothAdapter()
3 } else {
4   // 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
5   wx.showModal({
6     title: \'提示\',
7     content: \'当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。\'
8   })
9 }

小程序还提供了wx.canIUse这个API,用于判断接口或者组件在当前宿主环境是否可用,其参数格式为: ${API}.${method}.${param}.${options}或者${component}.${attribute}.${option}
各个段的含义如下:

  • ${API} 代表 API 名字
  • ${method} 代表调用方式,有效值为return, success, object, callback
  • ${param} 代表参数或者返回值
  • ${options} 代表参数的可选值
  • ${component} 代表组件名字
  • ${attribute} 代表组件属性
  • ${option} 代表组件属性的可选值
 1 // 判断接口及其参数在宿主环境是否可用
 2 wx.canIUse(\'openBluetoothAdapter\')
 3 wx.canIUse(\'getSystemInfoSync.return.screenWidth\')
 4 wx.canIUse(\'getSystemInfo.success.screenWidth\')
 5 wx.canIUse(\'showToast.object.image\')
 6 wx.canIUse(\'onCompassChange.callback.direction\')
 7 wx.canIUse(\'request.object.method.GET\')
 8 
 9  // 判断组件及其属性在宿主环境是否可用
10 wx.canIUse(\'contact-button\')
11 wx.canIUse(\'text.selectable\')
12 wx.canIUse(\'button.open-type.contact\')

  我们可以选择合适的判断方法来做小程序的向前兼容,以保证我们的小程序在旧版本的微信客户端也能工作正常。在不得已的情况下(小程序强依赖某个新的API或者组件时),还可以通过在小程序管理后台设置“基础库最低版本设置”来达到不向前兼容的目的。例如你选择设置你的小程序只支持1.5.0版本以上的宿主环境,那么当运行着1.4.0版本宿主环境的微信用户打开你的小程序的时候,微信客户端会显示当前小程序不可用,并且提示用户应该去升级微信客户端。


版权声明:本文为gidybzc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/gidybzc/p/10284807.html