小程序框架
小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
整个小程序框架系统分为两部分:逻辑层和视图层。小程序提供了自己的视图层描述语言 WXML
和 WXSS
,以及基于 JavaScript
的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。
框架 管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据、方法、生命周期函数注册到 框架 中,其他的一切复杂的操作都交由 框架 处理。
渲染层和逻辑层
小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。
逻辑层 App Service
在 JavaScript 的基础上,我们增加了一些功能,以方便小程序的开发:
- 增加
App
和Page
方法,进行程序注册和页面注册。 - 增加
getApp
和getCurrentPages
方法,分别用来获取App
实例和当前页面栈。 - 提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。
- 提供模块化能力,每个页面有独立的作用域。
注册小程序
每个小程序都需要在 app.js 中调用 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 上的函数。
注意事项
- 不要在定义于 App() 内的函数中,或调用 App 前调用 getApp() ,使用 this 就可以拿到 app 实例。
- 通过 getApp() 获取实例之后,不要私自调用生命周期函数。
注册页面
对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等。
注意:请只在需要的时候才在 page 中定义此方法,不要定义空方法。以减少不必要的事件派发对渲染层-逻辑层通信的影响。 注意:请避免在 onPageScroll 中过于频繁的执行
setData
等引起逻辑层-渲染层通信的操作。尤其是每次传输大量数据,会影响通信耗时。
onShareTimeline()
监听右上角菜单“分享到朋友圈”按钮的行为,并自定义分享内容。
注意:只有定义了此事件处理函数,右上角菜单才会显示“分享到朋友圈”按钮。
Page.route
到当前页面的路径,类型为String。
Page({
onShow: function() {
console.log(this.route)
}
})
在页面中使用 behaviors
页面可以引用 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.'
}
})
模块化
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports
或者 exports
才能对外暴露接口。
注意:
exports
是module.exports
的一个引用,因此在模块里边随意更改exports
的指向会造成未知的错误。所以更推荐开发者采用module.exports
来暴露模块接口,除非你已经清晰知道这两者的关系。
响应的数据绑定
框架的核心是一个响应的数据绑定系统,可以让数据与视图非常简单地保持同步。当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。
this.setData({
name: 'MINA'
})
WXML
- 列表渲染
<view wx:for="{{array}}"> {{item}} </view>
- 条件渲染
<!--wxml-->
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view>
<view wx:elif="{{view == 'APP'}}"> APP </view>
<view wx:else="{{view == 'MINA'}}"> MINA </view>
- 模板
<!--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'}
}
})
尺寸单位
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
全局样式与局部样式
定义在 app.wxss 中的样式为全局样式,作用于每一个页面。在 page 的 wxss 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器。
什么是事件
- 事件是视图层到逻辑层的通讯方式。
- 事件可以将用户的行为反馈到逻辑层进行处理。
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
- 事件对象可以携带额外信息,如 id, dataset, touches。
导航
小程序提供了两种页面路由方式:
- navigator 组件
- 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch 建议使用 navigator 组件。
navigator组件
- target:在哪个⽬标上发⽣跳转,默认当前⼩程序,可选值 self/miniProgram
- url:当前⼩程序内的跳转链接
- open-type:跳转方式
- delta:当 open-type 为 'navigateBack' 时有效,表示回退的层数
- app-id:当target="miniProgram"时有效,要打开的小程序 appId
结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。
页面栈
框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,页面栈的表现如下:
路由方式 | 页面表现 | 调用 API |
---|---|---|
初始化 | 新页面入栈 | |
打开新页面 | 新页面入栈 | wx.navigateTo |
页面重定向 | 当前页面出栈,新页面入栈 | wx.redirectTo |
页面返回 | 页面不断出栈,直到目标返回页 | wx.navigateBack |
Tab 切换 | 页面全部出栈,只留下新的 Tab 页面 | wx.switchTab |
重加载 | 页面全部出栈,只留下新的页面 | wx.reLaunch |
路由方式
路由方式 | 调用 API | 使用组件 |
---|---|---|
初始化 | 小程序打开的第一个页面 | |
打开新页面 | wx.navigateTo | <navigator open-type="navigateTo"/> |
页面重定向 | wx.redirectTo | <navigator open-type="redirectTo"/> |
页面返回 | wx.navigateBack | <navigator open-type="navigateBack"> |
Tab 切换 | wx.switchTab | <navigator open-type="switchTab"/> |
重启动 | wx.reLaunch | <navigator open-type="reLaunch"/> |
注意事项
- navigateTo, redirectTo 只能打开非 tabBar 页面。
- switchTab 只能打开 tabBar 页面。
- reLaunch 可以打开任意页面。
- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的onLoad中获取。
监听路由变化
App({
onLaunch: function () {
wx.onAppRoute(res => {
console.log(res.path)
})
}
})
自定义组件
因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
使用 usingComponents 时会多一些方法,如 selectComponent 。
出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj }) 后 this.data.field === obj 。(深复制会在这个值被组件间传递时发生。)
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
hide: function () { },
resize: function () { },
},
组件间通信
组件间的基本通信方式有以下几种:
- WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 [2.0.9]开始,还可以在数据中包含函数)。
- 事件:用于子组件向父组件传递数据,可以传递任意数据。
- 如果以上两种方式不足以满足需要,父组件还可以通过
this.selectComponent
方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。
监听事件
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。
自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项。
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,提供给事件监听函数
var myEventOption = {} // 触发事件的选项
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
})
组件生命周期
最重要的生命周期是 created attached detached
lifetimes: {
attached: function() {
// 在组件实例进入页面节点树时执行
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
},
在 behaviors 中也可以编写生命周期方法,同时不会与其他 behaviors 中的同名生命周期相互覆盖。但要注意,如果一个组件多次直接或间接引用同一个 behavior ,这个 behavior 中的生命周期函数在一个执行时机内只会执行一次。
组件所在页面的生命周期
还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义。
Component({
pageLifetimes: {
show: function() {
// 页面被展示
},
hide: function() {
// 页面被隐藏
},
resize: function(size) {
// 页面尺寸变化
}
}
})
behaviors
behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的 “mixins” 或 “traits”。
每个 behavior 可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。 每个组件可以引用多个 behavior ,behavior 也可以引用其它 behavior 。
my-behavior 结构为:
- 属性:myBehaviorProperty
- 数据字段:myBehaviorData
- 方法:myBehaviorMethod
- 生命周期函数:attached、created、ready
若组件本身有这个属性或方法,则组件的属性或方法会覆盖 behavior 中的同名属性或方法;
数据监听器
数据监听器可以用于监听和响应任何属性和数据字段的变化。
有时,在一些数据字段被 setData 设置时,需要执行一些操作。
Component({
attached: function() {
this.setData({
numberA: 1,
numberB: 2,
})
},
observers: {
'numberA, numberB': function(numberA, numberB) {
// 在 numberA 或者 numberB 被设置时,执行这个函数
this.setData({
sum: numberA + numberB
})
}
}
})
一次 setData 最多触发每个监听器一次。
特别地,仅使用通配符 ** 可以监听全部 setData 。
- 数据监听器监听的是 setData 涉及到的数据字段,即使这些数据字段的值没有发生变化,数据监听器依然会被触发。
- 如果在数据监听器函数中使用 setData 设置本身监听的数据字段,可能会导致死循环,需要特别留意。
- 与属性的 observer 相比,数据监听器更强大且通常具有更好的性能。
监听数据变化:
observers: {
'para.region_id': function(region) {
if (region) {
this.getCouponList();
}
}
},
或者
observers: {
'para.region_id'(region) {
if (region) {
this.getCouponList();
}
}
},
this可以正常使用。
组件样式隔离
默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。
指定特殊的样式隔离选项 styleIsolation。
Component({
options: {
styleIsolation: 'isolated'
}
})
styleIsolation
支持以下取值:
-
isolated
表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值); -
apply-shared
表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面; -
shared
表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了apply-shared
或shared
的自定义组件。(这个选项在插件中不可用。)
通常使用 apply-shared
即可。
页面间通信
如果一个页面由另一个页面通过 wx.navigateTo
打开,这两个页面间将建立一条数据通道:
- 被打开的页面可以通过
this.getOpenerEventChannel()
方法来获得一个EventChannel
对象; -
wx.navigateTo
的success
回调中也包含一个EventChannel
对象。
这两个 EventChannel
对象间可以使用 emit
和 on
方法相互发送、监听事件。
wx.navigateTo({
url: './test',
events: {
acceptDataFromOpenedPage: function (data) {
console.log(data)
},
},
success: function (res) {
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'send from opener page' })
}
})
onLoad: function (option) {
const eventChannel = this.getOpenerEventChannel()
eventChannel.emit('acceptDataFromOpenedPage', { data: 'send from opened page' });
eventChannel.on('acceptDataFromOpenerPage', function (data) {
console.log(data)
})
}
usingComponents
在 app.json 中声明的自定义组件视为全局自定义组件,在小程序内的页面或自定义组件中可以直接使用而无需再声明。
注意:全局声明的自定义组件会视为被所有页面依赖,会在所有页面启动时进行初始化,且会占用主包大小。只被个别页面或分包引用的自定义组件应尽量在页面配置中声明。
wx.pageScrollTo(Object object)
将页面滚动到目标位置,支持选择器和滚动距离两种方式定位。
selector类似于 CSS 的选择器,但仅支持下列语法。
- ID选择器:#the-id
- class选择器(可以连续指定多个):.a-class.another-class
- 子元素选择器:.the-parent > .the-child
- 后代选择器:.the-ancestor .the-descendant
- 跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant
- 多选择器的并集:#a-node, .some-other-nodes
wx.pageScrollTo({
scrollTop: 0,
duration: 300
})
picker点击没反应
picker里面必须要有元素点击才会响应。要设置宽高。