指南
起步
小程序简介
-
发展史
WeixinJSBridge => JS-SDK => 小程序
WeixinJSBridge :腾讯内部一些业务使用,没有对外开放。
JS-SDK:开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个API。并且由对内开放转为了对所有开发者开放。
缺点:页面切换的生硬和点击的迟滞感。加载慢,会出现白屏状态。
小程序: -
小程序与普通网页开发的区别
线程:
网页开发渲染线程
和脚本线程
是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API
,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API
。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。
运行环境:
网页开发者需要面对的环境是各式各样的浏览器
,PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端
,以及用于辅助开发的小程序开发者工具
。
开发流程:
网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程方可完成。
开始
申请账号,安装开发者工具,新建项目 ,编译
注:创建项目的时候要选择一个空的目录。
点击工具上的编译
按钮,可以在工具的左侧模拟器界面看到这个小程序的表现,也可以点击预览
按钮,通过微信的扫一扫在手机上体验你的第一个小程序。
代码构成
.json,.wxml,.wxss,.js
- json:
- 项目配置:
project.config.json
:开发者工具的一些个性化配置,例如界面颜色、编译配置等等。 - 全局配置:
app.json
是当前小程序的全局配置
,包括了小程序的所有页面路径
、界面表现
、网络超时时间
、底部 tab
等。包含:pages,window,networkTimeout,tabBar,等。 - 页面配置:
page.json
:页面中配置项会覆盖 app.json 的 window 中相同的配置项。(page.json即单独的页面的json) - 是否允许被微信索引:
sitemap.json
:小程序根目录下的 sitemap.json 文件用来配置小程序及其页面是否允许被微信索引。
小程序宿主环境
我们称微信客户端给小程序所提供的环境为宿主环境。小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能。
- 渲染层和逻辑层
小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView
进行渲染;逻辑层采用JsCore
线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native
来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发。
- 程序与页面
微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地。
写在 pages 字段的第一个页面就是这个小程序的首页(打开小程序看到的第一个页面)。
小程序启动之后,在 app.js
定义的 App
实例的 onLaunch
回调会被执行,整个小程序只有一个 App 实例,是全部页面共享的。
Page
是一个页面构造器,这个构造器就生成了一个页面。
-
组件
-
API
eg:要获取用户的地理位置时,只需要:
wx.getLocation({
type: 'wgs84',
success: (res) => {
var latitude = res.latitude // 纬度
var longitude = res.longitude // 经度
}
})
调用微信扫一扫能力,只需要:
wx.scanCode({
success: (res) => {
console.log(res)
}
})
- 小程序开发指南
教程:https://developers.weixin.qq.com/ebook?action=get_post_info&docid=0000286f908988db00866b85f5640a
小程序框架
整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。
场景值
场景值用来描述用户进入小程序的路径。完整场景值的含义请查看:https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html
- 获取场景值
对于小程序,可以在 App 的 onLaunch 和 onShow,或wx.getLaunchOptionsSync 中获取上述场景值。
逻辑层 App Service
在 JavaScript 的基础上,我们增加了一些功能,以方便小程序的开发:
a. 增加 App 和 Page 方法,进行程序注册和页面注册。
b. 增加 getApp 和 getCurrentPages 方法,分别用来获取 App 实例和当前页面栈。
c. 提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。
d. 提供模块化能力,每个页面有独立的作用域。
注:小程序框架的逻辑层并非运行在浏览器中,因此 JavaScript 在 web 中一些能力都无法使用,如 window,document 等。
- 注册小程序
每个小程序都需要在 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 上的函数。
// xxx.js
const appInstance = getApp()
console.log(appInstance.globalData) // I am global data
- 注册页面
对于小程序中的每个页面,都需要在页面对应的 js 文件中调用 Page 方法注册页面示例,指定页面的初始数据、生命周期回调、事件处理函数等。
//index.js
Page({
// 页面的初始数据
data: {
text: "This is page data."
},
生命周期回调—监听页面加载,页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
onLoad: function(options) {
// Do some initialize when page load.
},
生命周期回调—监听页面初次渲染完成,页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。注意:对界面内容进行设置的 API 如wx.setNavigationBarTitle,请在onReady之后进行。
onReady: function() {
// Do something when page ready.
},
生命周期回调—监听页面显示,页面显示/切入前台时触发。
onShow: function() {
// Do something when page show.
},
生命周期回调—监听页面隐藏,页面隐藏/切入后台时触发。 如 wx.navigateTo 或底部 tab 切换到其他页面,小程序切入后台等。
onHide: function() {
// Do something when page hide.
},
生命周期回调—监听页面卸载,页面卸载时触发。如wx.redirectTo或wx.navigateBack到其他页面时。
onUnload: function() {
// Do something when page close.
},
监听用户下拉动作,监听用户下拉刷新事件。
onPullDownRefresh: function() {
// Do something when pull down.
},
页面上拉触底事件的处理函数
onReachBottom: function() {
// Do something when page reach bottom.
},
用户点击右上角转发
onShareAppMessage: function () {
// return custom share data when user share.
},
页面滚动触发事件的处理函数
onPageScroll: function() {
// Do something when page scroll
},
页面尺寸改变时触发,小程序屏幕旋转时触发。
onResize: function() {
// Do something when page resize
},
当前是 tab 页时,点击 tab 时触发。
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// Event handler. 自定义的一些事件函数。
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
}, function() {
// this is setData callback
})
},
customData: {
hi: 'MINA'
}
})
生命周期钩子(五个):
onLoad,onShow,onReady,onHide,onUnload。
页面事件处理函数(六个):
onPullDownRefresh,onReachBottom,onPageScroll,onShareAppMessage,onResize,onTabItemTap。
- 页面路由
tips:
a. navigateTo, redirectTo 只能打开非 tabBar 页面。
b. switchTab 只能打开 tabBar 页面。
c. reLaunch 可以打开任意页面。
d. 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
e. 调用页面路由带的参数可以在目标页面的onLoad中获取。
- 模块化
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports 或者 exports 才能对外暴露接口。推荐开发者采用 module.exports 来暴露模块接口。
- API
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。详细介绍请参考 API 文档。eg:
https://developers.weixin.qq.com/miniprogram/dev/api/
通常,在小程序 API 有以下几种类型:
- 事件监听 API
我们约定,以 on 开头的 API 用来监听某个事件是否触发,如:wx.onSocketOpen,wx.onCompassChange 等。
这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。eg:
wx.onCompassChange(function (res) {
console.log(res.direction)
})
- 同步 API
我们约定,以 Sync 结尾的 API 都是同步 API, 如 wx.setStorageSync,wx.getSystemInfoSync 等。
同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。
try {
wx.setStorageSync('key', 'value')
} catch (e) {
console.error(e)
}
- 异步 API
大多数 API 都是异步 API,如 wx.request,wx.login 等。这类 API 接口通常都接受一个 Object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果:
success
:接口调用成功的回调函数。
fail
:接口调用失败的回调函数。
complete
:接口调用结束的回调函数(调用成功、失败都会执行)。
wx.login({
success(res) {
console.log(res.code)
}
})
视图层
WXML:
- 数据绑定:
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
c: 3,
d: 4
}
}
})
最终组合成的对象是 {a: 1, b: 2, c: 3, d: 4, e: 5}。
注意: 花括号和引号之间如果有空格,将最终被解析成为字符串,eg:
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
- 列表渲染
为index和item指定别名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
- 条件渲染
<block/>
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
b. 如果直接写boolean值的话,也得在双括号里面。
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
- 模板
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
定义模板:使用 name 属性,作为模板的名字。然后在内定义代码片段,如:
<!--
index: int
msg: string
time: string
-->
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
使用模板:使用模板
<template is="msgItem" data="{{...item}}"/>
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
- 引用
- import
import可以在该文件中使用目标文件定义的template,如:
在 item.wxml 中定义了一个叫item的template:
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
在 index.wxml 中引用了 item.wxml,就可以使用item模板:
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
import 的作用域:
import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template。
- include:
可以将目标文件除了 外的整个代码引入,相当于是拷贝到 include 位置,如:
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>
WXSS:
与 CSS 相比,WXSS 扩展的特性有:
- 尺寸单位,rpx(responsive pixel)
- 样式导入,import,eg:
@import "common.wxss";
框架组件上支持使用 style、class 属性来控制组件的样式。
注:
style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。
<view style="color:{{color}};" />
选择器:
类,id,元素,::after,::before,
事件系统:
<view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>
Page({
tapName: function(event) {
console.log(event)
}
})
- 事件分类
事件分为冒泡事件和非冒泡事件:
冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML的冒泡事件列表:
touchstart:手指触摸动作开始
touchmove:手指触摸后移动
touchcancel:手指触摸动作被打断,如来电提醒,弹窗
touchend:手指触摸动作结束
tap:手指触摸后马上离开
longpress:手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发
logtap:手指触摸后,超过350ms再离开(推荐使用longpress事件代替)
transitionend:会在 WXSS transition 或 wx.createAnimation 动画结束后触发
animationstart:会在一个 WXSS animation 动画开始时触发
animationend:会在一个 WXSS animation 动画完成时触发
等。。。
注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的submit事件,input 的input事件,scroll-view 的scroll事件,(详见各个组件)
事件绑定和冒泡:
事件绑定的写法同组件的属性,以 key、value 的形式。
key 以bind或catch开头,然后跟上事件的类型,如bindtap、catchtouchstart。value 是一个字符串,需要在对应的 Page 中定义同名的函数。
注:bind事件绑定不会阻止冒泡事件向上冒泡,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>
事件对象:
type:事件类型
timeStamp:事件生成时的时间戳,页面打开到触发事件所经过的毫秒数。
target:触发事件的组件的一些属性值集合(触发事件的源组件。)
currentTarget:当前组件的一些属性值集合(事件绑定的当前组件)
- dataset
连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。如:
data-element-type ,最终会呈现为 event.currentTarget.dataset.elementType ;
data-elementType ,最终会呈现为 event.currentTarget.dataset.elementtype 。
mark:事件标记数据
组件基础
一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内。
<tagname property="value">
Content goes here ...
</tagname>
注:所有组件与属性都是小写,以连字符-连接
公共属性:
id
:
class
:
style
:可以动态设置的内联样式
hidden
:所有组件默认显示
data-*
:组件上触发的事件时,会发送给事件处理函数
bind*/catch*
:组件的事件
所有组件都有以上的属性
获取界面上的节点信息
- 节点信息查询 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()
wx.createSelectorQuery():创建一个query;
query.select(’#the-id’):选择id为the-id的元素;选择第一个
匹配选择器 selector 的节点
query.selectAll(’.the-class’):在当前页面下选择匹配选择器 selector 的所有节点
。
boundingClientRect:跟html中的getBoundingClientRect
属性一样,获取元素的一些位置信息;
scrollOffset:获取元素的scroll信息;
exec:上面的按顺序执行,然后上面的信息会按顺序的放在res里,是一个list;
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function(res){
res[0].top // #the-id节点的上边界坐标
res[1].scrollTop // 显示区域的竖直滚动位置
})
- 节点布局相交状态 API
wx.createIntersectionObserver
响应显示区域变化
显示区域尺寸:
显示区域指小程序界面中可以自由布局展示的区域。在默认情况下,小程序显示区域的尺寸自页面初始化起就不会发生变化。但以下两种方式都可以改变这一默认行为。
- 在手机上启用屏幕旋转支持
在 app.json 的 window 段中设置 “pageOrientation”: “auto” ,或在页面 json 文件中配置 “pageOrientation”: “auto” 。 - 在 iPad 上启用屏幕旋转支持
在 app.json 中添加 “resizable”: true 。
注意:在 iPad 上不能单独配置某个页面是否支持屏幕旋转。
屏幕旋转事件:
在 js 中读取页面的显示区域尺寸,可以使用 selectorQuery.selectViewport 。
页面尺寸发生改变的事件,可以使用页面的 onResize 来监听。
Page({
onResize(res) {
res.size.windowWidth // 新的显示区域宽度
res.size.windowHeight // 新的显示区域高度
}
})
动画
在小程序中,通常可以使用 CSS 渐变 和 CSS 动画 来创建简易的界面动画。
同时,还可以使用 wx.createAnimation 接口来动态创建简易的动画效果。
动画过程中,可以使用 bindtransitionend
bindanimationstart
bindanimationiteration
bindanimationend
来监听动画事件。
小程序运行时
运行环境
微信小程序运行在三端:iOS(iPhone/iPad)、Android 和 用于调试的开发者工具。
三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的:
-
在 iOS 上,小程序逻辑层的 javascript 代码运行在
JavaScriptCore
中,视图层是由WKWebView
来渲染的,环境有 iOS8、iOS9、iOS10; -
在 Android 上,
a. 旧版本,小程序逻辑层的 javascript 代码运行中 X5 JSCore 中,视图层是由 X5 基于 Mobile Chrome 57 内核来渲染的;
b. 新版本,小程序逻辑层的 javascript 代码运行在 V8 中,视图层是由自研 XWeb 引擎基于 Mobile Chrome 67 内核来渲染的;
- 在 开发工具上,小程序逻辑层的 javascript 代码是运行在 NW.js 中,视图层是由 Chromium 60 Webview 来渲染的。
运行机制
小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。
- 热启动:假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动;
- 冷启动:用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动,即冷启动。
前台/后台状态:
当用户点击右上角胶囊按钮关闭小程序,或者按了设备 Home 键离开微信时,小程序并没有直接销毁,而是进入了后台状态;当用户再次进入微信或再次打开小程序,小程序又会从后台进入前台。
小程序销毁:
需要注意的是:只有当小程序进入后台一定时间(目前是5分钟),或者系统资源占用过高,才会被真正的销毁。
自定义组件
- 创建自定义组件
类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可这一组文件设为自定义组件):
{
"component": true
}
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。
在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
- 使用自定义组件
使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>