前言
最近在公司做小程序开发,做两个音视频的页面,解决方案使用的是腾讯云提供的trtc-room组件,看了一下组件代码,使用的是原生小程序实现的。公司开发小程序项目居多,wepy、mpvue框架都有项目在使用,加上前公司工作时,开发项目使用过uni-app开发小程序(当然,uni-app还可以打包成移动端app的包,安卓、iOS都可以),感觉开发小程序,总之都是基于原生小程序在搞事情,所以有些东西想实现的话,还是得看看原生是否支持,支持到什么程度,所以即使使用诸多框架,还是应该从原生小程序出发,之前感觉小程序开发嘛,代码往上罗就行了(不会写,照着写),要说不会开发小程序吧,还能写代码,要说会吧,有些东西了解的不是很清楚,所以在解决有些问题的时候,不清楚的地方,解决问题的效率降低,解决问题提出的解决方案也会受限,所以我想决定好好看一遍原生小程序的文档,对于前端开发来说,我觉得把小程序前面四个页签的内容看一遍应该就可以了(指南、框架、组件、API),至于工具(微信开发者工具)的使用,用着用着基本就会了。权当我个人总结。我打算指南,框架部分阅读的时候总结点我自己之前不了解的干货,至于组件和API部分,我觉得我总结的话也是搬砖不如直接看文档来的痛快 。接下来,开始我的Ctrl C V。
微信小程序开发文档:https://developers.weixin.qq.com/miniprogram/dev/framework/
微信公众平台:https://mp.weixin.qq.com/
注:总结归纳的应该不是很全面,是我自己在开发小程序过程中,没有详细看文档遗漏以及不了解的一些概念
小程序与普通网页开发的区别
小程序的主要开发语言是JavaScript,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的。
网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用各种浏览器暴露出来的DOM API进行DOM操作,而小程序的逻辑层和渲染层是分开的,逻辑层运行在JSCore中,并没有一个完整浏览器对象,因此缺少相关的DOM API和BOM API。这一区别导致前端开发非常熟悉的一些库,例如:jQuery、Zepto等,在小程序中无法运行。同时JSCore的环境同NodeJS环境也是不尽相同,所以一些NPM的包在小程序中也是无法运行的。
网页开发者需要面对的环境是各式各样的浏览器,PC端需要面对IE、Chrome、Firefox等。在移动端需要面对的是iOS、安卓系统中各式的webview,而小程序开发过程中需要面对的是两大操作系统的微信客户端,,以及用于辅助开发的小程序开发者工具。
小程序中三大运行环境也是有所区别的,如下所示:
运行环境 | 逻辑层 | 渲染层 |
iOS | JSCore |
WKWebView |
安卓 | V8 |
chromium定制内核 |
小程序开发工具 | NWJS |
Chrome WebView |
网页开发者在开发网页的时候,只需要使用浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序账号、安装小程序开发者工具。配置项目等过程方可完成。
申请账号
在小程序后台(微信公众平台),可以进行你的小程序的权限管理、查看数据报表、发布小程序的操作。在菜单”开发“-”开发设置“中,可以得到小程序的AppID。小程序的AppID相当于小程序平台的一个身份证(在开发者工具中使用)。
小程序的版本
权限 | 说明 |
开发版本 |
使用开发者工具,可将代码上传到开发者版本中。开发版本只保留每人最新的一份上传的代码。 点击提交审核,可将代码提交审核。开发版本可删除,不影响线上版本和审核中版本的代码。 |
体验版本 | 可以选择某个开发版本作为体验版,并且选取一份体验版。 |
审核中版本 | 只能有一份代码处于审核中。有审核结果后可以发布到线上,也可以直接提交审核,覆盖原审核版本。 |
线上版本 | 线上所有用户使用的代码版本,该版本代码在新版本代码发布后被覆盖更新。 |
小程序的发布
审核通过之后,管理员的微信总会收到小程序通过审核的通知。
小程序提供了两种发布模式:全量发布和分阶段发布。全量发布是指当点击发布之后,所有用户访问小程序时都会使用当前最新的发布版本。分阶段发布是指分不同时间段来控制部分用户使用最新的发布版本,分阶段发布我们也称为灰度发布。一般来说,普通小程序发布时采用全量发布即可,当小程序承载的功能越来越多,使用的用户也越来越多时,采用分阶段发布是一个非常好的控制风险的办法。
场景值
场景值用来描述用户进入小程序的路径,完整场景值的含义请查看场景值列表。
获取场景值
对于小程序,可以在App的onLaunch和onShow,或wx.getLaunchOptionsSync中获取上述场景值。
获取来源信息的场景
部分场景值还可以获取来源应用、公众号或小程序的appId。
场景值 | 场景 | AppID含义 |
1020 | 公众号profile页相关的小程序列表 | 来源公众号 |
1035 | 公众号自定义菜单 | 来源公众号 |
1036 | APP分享消息卡片 | 来源APP |
1037 | 小程序打开小程序 | 来源小程序 |
1038 | 从另一个小程序返回 | 来源小程序 |
1043 | 公众号模板信息 | 来源公众号 |
逻辑层App Service
小程序开发框架的逻辑层使用JavaScript引擎作为小程序提供开发者JavaScript代码的运行环境以及微信小程序的特有功能。
逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
开发者写的所有代码最终会打包成一份JavaScript文件,并且在小程序启动的时候运行,直到小程序销毁。
在JavaScript的基础上,我们增加了一些功能,以便小程序的开发:
- 增加App和Page方法,进行程序注册和页面注册。
- 增加getApp和getCurrentPages方法,分别用来获取App实例和当前页面栈。
- 提供丰富的API,如扫一扫、支付等微信特有的功能。
- 提供模块化能力,每个页面有独立的作用域。
注意:小程序框架的逻辑层并非运行在浏览器中,因此JavaScript在web中的一些能力都无法使用,如window、document等。
尺寸单位
rpx(responsive pixel):可以根据屏幕宽度进行自适应。规定屏幕宽度为750rpx。如在iPhone6上,屏幕宽度为375px,共有750个物理像素,
则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
建议:开发微信小程序时设计师可以用iPhone6作为视觉稿的标准。
注意:在较小的屏幕上不可避免的会有一些毛刺,请在开发室尽量避免这种情况。
事件的使用方式
{ "type":"tap",//代表事件的类型 "timeStamp":895,//页面打开到触发事件所经过的毫秒数 "target": {//触发事件的源组件 "id": "tapTest",//事件源组件的ID "dataset": {//事件源组件上由data- 开头的自定义属性组成的集合 "hi":"WeChat"//执行方法参数的解决方式 } }, "currentTarget": {//事件绑定的当前组件 "id": "tapTest",//当前组件的ID "dataset": {//当前组件上由data- 开头自定义属性组成的集合 "hi":"WeChat" } }, "detail": {//自定义事件所携带的数据。 "x":53, "y":14 }, "touches":[{ "identifier":0,//触摸点的标识 "pageX":53,//距离文档左上角的距离文档的左上角为原点,横向为x轴,纵向为y轴。 "pageY":14, "clientX":53,距离页面可显示区域(屏幕去除导航条)左上角距离,横向为x轴,纵向为y轴。 "clientY":14 }], "changedTouches":[{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14 }] }
使用wxs函数响应事件
支持使用wxs函数绑定事件,wxs函数接受2个参数,第一个是event,在原有的event的基础上加了event.instance对象,第二个参数是ownerInstance,和event.instance一样是一个ComponentDescriptor对象。
绑定并阻止事件冒泡
除bind外,也可以使用catch来绑定事件。与bind不同,catch会阻止事件向上冒泡。
响应显示区域变化
在手机上启用屏幕旋转支持
从小程序基础库版本2.4.0开始,小程序在手机上支持屏幕旋转。使小程序中的页面支持屏幕旋转的方法是:在app.json的window段中设置"pageOrientation":"auto",或者是在页面的json文件中配置"pageOrientation":"auto"(意味着手机上单页面配置屏幕旋转)。从小程序2.5.0开始,pageOrientation还可以设置为landscape,表示固定为横屏显示。
在iPad上启用屏幕旋转支持
从小程序2.3.0开始,在iPad上运行的小程序可以支持屏幕旋转。使小程序支持iPad屏幕旋转的方法是在app.json中添加"resizable":true。注意:在iPad上不能单独配置某个页面是否支持屏幕旋转。
JavaScript支持情况
运行限制
基于安全考虑,小程序中不支持动态执行js代码:
- 不支持使用eval执行代码。(上家公司为了实现业务需求,也找到了一个替代eval方法的解决方法。)
- 不支持使用new Function 创建函数。
小程序运行机制
前台/后台状态
小程序启动后,界面被展示给用户,此时小程序处于前台状态。
当用户点击右上角胶囊按钮关闭小程序,或者按了设备home键离开微信时,小程序并没有完全终止运行,而是进入了后台状态,小程序还可以运行一小段时间。
当用户再次进入微信或者再次打开小程序,小程序又会从后台进入前台。但如果用户很久没有进入小程序,或者系统资源紧张,小程序可能被销毁,即完全终止运行。
小程序启动
这样,小程序启动可以分为两种情况,一种是冷启动,一种是热启动。
- 冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。
- 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。
小程序更新机制
未启动时更新
开发者在管理后台发布最新版小程序后,如果某个用户本地有小程序的历史版本,此时打开的可能还是旧版本。微信客户端会有若干时机去检查本地缓存的小程序有没有更新版本,如果有则会静默更新到新版本。总的来说,开发者在后台发布新版本后,无法立刻影响到所有现网用户,但是最差的情况下,也会在发布之后24小时之内下发新版本信息到用户。用户下次打开时会先更新最新版本再打开。
启动时更新
小程序每次冷启动时都会检查是否有最新版本,如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地包进行启动,即新版本的小程序需要等下一次冷启动才可以应用。
如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理
自定义组件
Component构造器
Component构造器可用于自定义组件,调用Component构造器可以致电过组件的属性、数据、方法。
- properties 组件的对外属性,是属性名到属性设置的映射表
- data 组件的内部数据,和properties一同用于组件的模板渲染
- observers 组件数据字段监听器 用于监听properties 和data的变化
- methods 组件的方法 包括事件响应函数和任意的自定义方法
- behaviors 类似于mixins 和traits的组件间代码复用机制
- created 组件生命周期函数-在组件实例刚刚被创建时执行,注意:此时不能调用setData
- attached 组件生命周期函数-在组件实例进入页面节点节点树时执行
- ready 组件声明周期函数-在组件布局完成后执行
- moved 组件生命周期函数-在组件实例被移动到节点树的另一个位置时执行
- detached 组件生命周期函数-在组件实例被从页面节点树移除时执行
- relations 组件间关系定义
- externalClasses 组件接受的外部样式类
- options 一些选项
- lifetimes 组件生命周期对象
- pageLifetimes 组件所在页面的生命周期对象
- definitionFilter 定义段过滤器,用于自定义组件扩展
Component({ behaviors: [], properties: { myProperty: { // 属性名 type: String, value: '' }, myProperty2: String // 简化的定义方式 }, data: {}, // 私有数据,可用于模板渲染 lifetimes: { // 生命周期函数,可以为函数,或一个在methods段中定义的方法名 attached: function () { }, moved: function () { }, detached: function () { }, }, // 生命周期函数,可以为函数,或一个在methods段中定义的方法名 attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖 ready: function() { }, pageLifetimes: { // 组件所在页面的生命周期函数 show: function () { }, hide: function () { }, resize: function () { }, }, methods: { onMyButtonTap: function(){ this.setData({ // 更新属性和数据的方法与更新页面数据的方法类似 }) }, // 内部方法建议以下划线开头 _myPrivateMethod: function(){ // 这里将 data.A[0].B 设为 'myPrivateData' this.setData({ 'A[0].B': 'myPrivateData' }) }, _propertyChange: function(newVal, oldVal) { } } })
组件间通信与事件
- 数据绑定:用于父组件向子组件的指定属性设置数据
- 事件:用于子组件向父组件传递数据,可以传递任意数据
- 父组件还可以通过this.selectComponent()方法获取子组件实例对象,这样就可以直接访问组建的任意数据和方法
自定义组件触发事件需要使用triggerEvent方法,指定事件名、detail对象和事件选项。
this.triggerEvent('myEvent',myEventDetail,myEventOption)
触发事件的选项包括:
选项名 | 类型 | 是否必填 | 默认值 | 描述 |
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越组建边界,为false时,事件只能在引用组件的节点树上触发,不进入其他任何组件内部 |
capturePhase | Boolean | 否 | false | 事件是否拥有捕获阶段 |
当组件触发生命周期时,上例生命周期函数执行顺序为:
[my-behavior] created
[my-component] created
[my-behavior] attached
[my-component] attached
[my-behavior] ready
[my-component] ready
存储
同一个微信用户,同一个小程序 storage 上限为 10MB。
开放能力
小程序可以通过微信官方提供的登录能力方便的获取微信提供的用户身份标识,快速的建立小程序内的用户体系。(MS的时候,问到小程序可能会被问到)
登录流程时序
说明:
- 调用wx.login()获取临时登录凭证code,并回传到开发者服务器。
- 调用auth.code2Session接口,换取用户唯一标识OpenID和会话秘钥session_key
之后开发者服务器可以根据表示来生成自定义登录状态,用于后续业务逻辑中前后端交互时识别用户身份
注意:
- 会话密钥
session_key
是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。 - 临时登录凭证 code 只能使用一次
UnionID 机制说明
如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。
只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。
UnionID获取途径
绑定了开发者帐号的小程序,可以通过以下途径获取 UnionID。
-
调用接口 wx.getUserInfo,从解密数据中获取 UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。
-
如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过 wx.login +
code2Session
获取到该用户 UnionID,无须用户再次授权。 -
如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过 wx.login +
code2Session
获取到该用户 UnionID ,无须用户再次授权。 -
用户在小程序(暂不支持小游戏)中支付完成后,开发者可以直接通过
getPaidUnionId
接口获取该用户的 UnionID,无需用户授权。注意:本接口仅在用户支付完成后的5分钟内有效,请开发者妥善处理。 -
小程序端调用云函数时,如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号,可在云函数中通过 cloud.getWXContext 获取 UnionID。
-
小程序端调用云函数时,如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用,也可在云函数中通过 cloud.getWXContext 获取 UnionID。
授权
部分接口需要经过用户授权同意才能调用。我们把这些接口按使用范围分成多个 scope
,用户选择对 scope
来进行授权,当授权给一个 scope
之后,其对应的所有接口都可以直接使用。
此类接口调用时:
- 如果用户未接受或拒绝过此权限,会弹窗询问用户,用户点击同意后方可调用接口;
- 如果用户已授权,可以直接调用接口;
- 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调。请开发者兼容用户拒绝授权的场景。
获取用户授权设置
开发者可以使用 wx.getSetting 获取用户当前的授权状态。
打开设置界面
用户可以在小程序设置界面(「右上角」 - 「关于」 - 「右上角」 - 「设置」)中控制对该小程序的授权状态。
开发者可以调用 wx.openSetting 打开设置界面,引导用户开启授权。
提前发起授权请求
开发者可以使用 wx.authorize 在调用需授权 API 之前,提前向用户发起授权请求。
scope 列表
scope | 对应接口 | 描述 |
---|---|---|
scope.userInfo | wx.getUserInfo | 用户信息 |
scope.userLocation | wx.getLocation, wx.chooseLocation | 地理位置 |
scope.userLocationBackground | wx.startLocationUpdateBackground | 后台定位 |
scope.address | wx.chooseAddress | 通讯地址 |
scope.invoiceTitle | wx.chooseInvoiceTitle | 发票抬头 |
scope.invoice | wx.chooseInvoice | 获取发票 |
scope.werun | wx.getWeRunData | 微信运动步数 |
scope.record | wx.startRecord | 录音功能 |
scope.writePhotosAlbum | wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum | 保存到相册 |
scope.camera | camera 组件 | 摄像头 |
示例代码
// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope wx.getSetting({ success(res) { if (!res.authSetting['scope.record']) { wx.authorize({ scope: 'scope.record', success () { // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问 wx.startRecord() } }) } } })
第一次进入小程序执行wx.authorize()方法会调起授权弹框,当误操作为允许授权,只能由开发者调用 wx.openSetting 打开设置界面,引导用户开启授权。
打开 App
此功能需要用户主动触发才能打开 APP,所以不由 API 来调用,需要用 open-type
的值设置为 launchApp
的 button 组件的点击来触发。
当小程序从 APP 分享消息卡片的场景打开(场景值 1036,APP 分享小程序文档 iOS / Android) 或从 APP 打开的场景打开时(场景值 1069),小程序会获得打开 APP 的能力,此时用户点击按钮可以打开分享该小程序卡片/拉起该小程序的 APP。即小程序不能打开任意 APP,只能 跳回
APP。
<button open-type="launchApp" app-parameter="wechat" binderror="launchAppError">打开APP</button>
Page({ launchAppError (e) { console.log(e.detail.errMsg) } })
引用
WXML 提供两种文件引用方式import
和include
。
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。
如:C import B,B import A,在C中可以使用B定义的template
,在B中可以使用A定义的template
,但是C不能使用A定义的template
。
<!-- A.wxml --> <template name="A"> <text> A template </text> </template>
<!-- B.wxml --> <import src="a.wxml"/> <template name="B"> <text> B template </text> </template>
<!-- C.wxml --> <import src="b.wxml"/> <template is="A"/> <!-- Error! Can not use tempalte when not import A. --> <template is="B"/>
include
include
可以将目标文件除了 <template/>
<wxs/>
外的整个代码引入,相当于是拷贝到 include
位置,如:
<!-- index.wxml --> <include src="header.wxml"/> <view> body </view> <include src="footer.wxml"/>
<!-- header.wxml --> <view> header </view>
<!-- footer.wxml --> <view> footer </view>
未完待续...