下面结合开发文档以及我的开发经验对微信小程序关键部分进行解读(不是入门教程,具体入门读者能够看官网),但愿看完的读者对微信小程序有大概的认识或者有所启发。javascript
本文同步于我的博客 www.imhjm.com/article/597…css
官方开发文档 mp.weixin.qq.com/debug/wxado…
官方开发者社区 developers.weixin.qq.com/html
微信小程序运行在三端:iOS、Android 和 用于调试的开发者工具。
三端的脚本执行环境聚以及用于渲染非原生组件的环境是各不相同的:vue
正因为脚本执行环境的不一样,因此真机与开发者工具备些表现仍是差别挺大的,特别表如今原生组件方面(后面会讲到部分原生组件注意点),iOS以及Android都须要多加测试才能保证程序没有问题。
同时由于是在JsCore中执行,JsCore没有窗口对象,因此没有window、document等等(因此不少外部生态插件/库没法直接使用,须要稍做修改)node
小程序全局有App、Page内置的全局变量,用于注册小程序以及注册页面webpack
前台、后台定义: 当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并无直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。须要注意的是:只有当小程序进入后台必定时间,或者系统资源占用太高,才会被真正的销毁。git
具体读者能够看文档中的「Page实例生命周期」,左边是视图线程,右边是逻辑层线程
能够看到View Thread分四个阶段es6
AppSevice Thread也分四个阶段github
咱们从图中能够简单地分析出「Page实例生命周期」
从上面声明周期的分析,咱们能够获得如下几个结论:
从生命周期也能够看出微信小程序跟vue等框架相似,是数据驱动视图更新,在逻辑层修改数据,视图层响应数据更新
<view> {{ message }} </view>
Page({
data: {
message: 'Hello MINA!'
}
})复制代码
如上使用双括号,便实现数据与视图绑定
微信小程序一样是数据单向流动,而不是双向绑定,好比你传入它基础组件的某些数据,并不能同步到你的data中,而是调用某些监听函数去获取(好比scroll-view中scroll-top,你能经过视图传入data更新滚动位置,可是你在滚动的时候,并不能双向绑定去获取scroll-top,而是须要监听bindscroll去获取)
条件渲染以及列表渲染做为数据驱动视图的重要部分,值得一提
1.条件渲染的wx:if以及hidden
因此官网给出的结论是
通常来讲,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。所以,若是须要频繁切换的情景下,用 hidden 更好,若是在运行时条件不大可能改变则 wx:if 较好。
2.列表渲染
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="*this">
{{idx}}: {{itemName.message}}
</view>复制代码
这里其余for,index,item这些循环渲染基本的东西就不具体说了,谈谈这个wx:key
假如咱们更新array数组,预期来讲视图从新渲染,可是咱们假如只是在array中push更多的元素,咱们的想法应该是从新排序,不去重复建立视图原来已经有的元素,这里为了标识item,咱们就能够用wx:key,有助于提高渲染的效率,而且可以保持状态(如<input/>
中的输入内容,<switch/>
的选中状态)
小程序的路由管理部分均由框架处理,开发者只需调用API便可,可是仍是有一些地方须要注意
小程序的路由管理是用一个页面栈来维护,经过出栈以及入栈加载不一样页面,能够用getCurrentPages()获取一个栈数组
下面这个表格根据官网两个表格整合而成,注意区分各类触发时机以及页面栈的表现
路由方式 | 触发时机 | 页面栈表现 | 路由前页面 | 路由后页面调用方法 |
---|---|---|---|---|
初始化 | 小程序打开的第一个页面 | 新页面入栈 | onLoad, onSHow | |
打开新页面 | 调用 API wx.navigateTo 或使用组件 <navigator open-type="navigateTo"/> |
新页面入栈 | onHide | onLoad, onShow |
页面重定向 | 调用 API wx.redirectTo 或使用组件 <navigator open-type="redirectTo"/> |
当前页面出栈,新页面入栈 | onUnload | onLoad, onShow |
页面返回 | 调用 API wx.navigateBack 或使用组件<navigator open-type="navigateBack"> 或用户按左上角返回按钮 |
页面不断出栈,直到目标返回页,新页面入栈 | onUnload | onShow |
Tab 切换 | 调用 API wx.switchTab 或使用组件 <navigator open-type="switchTab"/> 或用户切换 Tab |
页面所有出栈,只留下新的 Tab 页面 | 具体看官网 | |
重启动 | 调用 API wx.reLaunch 或使用组件 <navigator open-type="reLaunch"/> |
页面所有出栈,只留下新的页面 | onUnload | onLoad, onShow |
注意区分页面重定向(redirectTo)以及打开新页面(navigateTo),由于小程序限制了也页面栈最多只有5个元素,因此当你深度达到5个,再调用navigateTo想让新页面再入栈就会报错,因此官方建议是
避免多层级的交互方式,或者使用wx.redirectTo
小程序默认使用CommonJs规范
使用module.exports(exports)以及require来实现模块化
固然也能够ES6转ES5使用import/export,小程序开发工具带有babel es6转es5设置,勾选便可
猜想最后也是使用webpack打包文件
这里简单说下模块化须要注意的吧,首先module.exports = exports, module就是一个对象{},exports就是对它的一个key的引用,因此须要区分下module.exports = xxx, 以及export.xxx = yyy;
还得注意区分ES6和commonjs的差别,前者模块静态编译,后者运行加载,因此表现上有不少不一样,ES6能够在编译时处理依赖关系,而且输出的值为引用,对循环引用支持比较好,不一样的是commonjs模块是运行加载,输出值为拷贝
这部分就很少说了,具体能够看 es6.ruanyifeng.com/#docs/modul…
不过这里的require加载机制不一样于nodejs,加了一些限制,好比不能用绝对路径,也不支持node_modules,因此若是要使用node_modules的内容须要手动拷贝到目录里
wxml经过template能够实现复用
经过is属性动态决定渲染哪一个模版
<template name="odd">
<view> odd </view>
</template>
<template name="even">
<view> even </view>
</template>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
<template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>复制代码
而且有本身的做用域,只能使用传入的data(这点跟组件很类似)
使用@import语句能够导入外联样式表,@import后跟须要导入的外联样式表的相对路径
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}复制代码
上述三个模块化的东西能够构成相似组件同样的部分,可是引入方面太不方便,wxml/wxss/js都得引入一份,而且js耦合程度太高,需在Page中引入“组件”太多的方法去调用,也没有本身的数据做用域,data都是在Page里,弊端仍是比较明显,像组件但不是组件。
小程序本身提供了一系列的基础组件,这些就是真正的组件了,可是小程序没有提供自定义组件的方式
这部分也很少说了,内容也挺多的,不少细节,具体看官方文档,后面也会讲到某些我的实战时遇到的一些经验
错误监控对于应用的稳定性相当重要,这部分也特意拿出来说下
一般应用可使用ravenjs使用window.onerror捕获错误,处理error.stack,而后接入sentry上报,固然在微信小程序也能够,可是须要作一些配置改动
在微信小程序该怎么作呢?
没有了window.onerror, 微信小程序能够在App传入onerror进行捕获错误,使用小程序的wx.request上报,而且能够附加小程序的systemInfo一块儿上报,得到更多错误信息,更好地修复bug
小程序上一段时间加了一个运维中心,能够在公众平台中设置
数据上报也是一个好应用中不可或缺的部分,去了解用户如何使用应用,了解怎么去更好地优化以及增长功能。
小程序自带数据上报接口
有两种上报方式,一种是使用API接口wx.reportAnalytics在代码中上报,一种是在微信公众平台直接配置事件,根据id/class和page来指定事件(好比点击事件等等)
上面微信小程序基本的也讲了挺多了,下面开始讲一些零散的开发细节和遇到的问题以及解决方案
若是不考虑引入像wepy这种组件化框架或者引入状态管理方案,以为采用如下开发结构也是一个良好的选择
|---model---------------跟业务逻辑相关的,跟数据交互的model
|---xxx.js
|---utils----------------可从业务逻辑中抽离处可复用工具
|---xxx.js
|---pages---------------微信小程序的各个page
|---xxx
|---xxx.wxml
|---xxx.wxss
|---xxx.json
|---xxx.js
|---components-----------可从page中抽离出的组件,有利于复用以及维护
|---xxx
|---xxx.wxml
|---xxx.wxss
|---xxx.js
|---static----------------静态资源文件
|---app.js
|---app.json
|---app.wxss
其余eslint、git相关等等就不放上去了复制代码
这个总体目录并不复杂,可是这样分层,每部分的职责就能够很清晰了,有利于代码维护以及复用
(稍微区分下model和utils,model便是一些跟后台交互数据的操做,能够依赖utils,utils是从业务逻辑中抽离出的可复用的工具库,但不能够依赖于model)
因为微信小程序wx.request有并发10个的限制,而且以前若是超出并发数就会报错从而中断了超出的请求,当时总体使用本身封装的request,支持超出并发数放入队列中,当有新的请求complete再检查队列,不空则取出原先的请求retry,而且加上了超时处理,代码大概以下
let RequestQ = {
retry: [],
emitRequest (obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let oldFail = obj.fail;
let oldComplete = obj.complete;
let oldSuccess = obj.success;
let timeId;
obj.timeout = obj.timeout || 10000;
// 假若有timeout开启定时器
if (obj.timeout) {
timeId = setTimeout(() => {
obj.over = true;
oldFail && oldFail.apply(obj, [{ isTimeout: true }]);
oldComplete && oldComplete.apply(obj, [{}]);
}, obj.timeout);
}
obj.success = (...args) => {
obj.end = +new Date();
// 在队列中或者因为超时结束的直接return
if (obj.inRetry || obj.over) {
return;
}
oldSuccess && oldSuccess.apply(obj, args);
};
obj.fail = (...args) => {
if (obj.over) {
return;
}
if (Array.from(args)[0].errMsg === 'request:fail exceed max task count' && !obj.inRetry) {
// 并发数超出则进入队列,不触发fail与complete
obj.inRetry = true;
this.retry.push(obj);
} else {
oldFail && oldFail.apply(obj, args);
}
};
obj.complete = (...args) => {
if (obj.inRetry || obj.over) {
return;
}
clearTimeout(timeId);
if (this.retry.length) {
// complete完成,检查队列有则拿出来执行
let newObj = this.retry.shift();
newObj.inRetry = false;
this.emitRequest(newObj);
}
oldComplete && oldComplete.apply(obj, args);
};
wx.request(obj);
},
};
function request (obj) {
RequestQ.emitRequest(obj);
}复制代码
不太小程序也在持续地完善,基础库在1.4.0更新了request, 队列处理也帮咱们作好了
U 更新 API request 超过并发限制作队列处理
U 更新 API request 返回 requestTask 支持 abort 操做
这里还得说个wx.request的注意点,微信小程序默认状况下dataType为'json',会尝试对响应的数据作一次JSON.parse,因此假如返回一张base64图等等数据,在真机上就会出现错误(这个错误还挺难找的)
将接口promise化能够减小回调,代码看起来也会更加清晰
记得要引入promise-polyfill,在某些机型中微信小程序对promise的支持并很差,可使用本身的promise
具体怎么编写promise化的接口就不详细说了,在success方法 resolve, 在error方法reject, 不管什么状况均返回promise
这里引一段网上的promisify
// 连接:http://www.jianshu.com/p/4433d46e6235
// 用Promise封装小程序的其余API
export const promisify = (api) => {
return (options, ...params) => {
return new Promise((resolve, reject) => {
api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);
});
}
}复制代码
官网介绍小程序这个rpx有句
注意: 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽可能避免这种状况。
遇到一个这样的问题,使用了
padding-bottom: 0rpx;
却发现padding-bottom有个微小的缝隙,只要将0rpx改为0便可
注意有时还会出现多个元素并排使用rpx,毛刺偏差累积起来可能会产生比较大的影响,假如出现这种状况,可使用白分比来替代解决
// index.json
{
"enablePullDownRefresh": true
}复制代码
为了实现跟原生应用接近的体验,采用手势左右滑动来实现频道切换
先讲讲swiper-view如何实现滑动的呢?
注意到一个absolute,因此swiper-item内部的内容是没法把外部给撑开的,因此没法实现自适应,必须本身指定高度
咱们的需求是要实现上面预留导航栏,全屏滑动,css上就能够这样
page {
box-sizing: border-box;
-webkit-box-sizing: border-box;
height: 100%;
/* 预留顶部导航栏 */
padding-top: 89rpx;
}
.swiper-container {
height: 100%;
}复制代码
假如你还想在里面放入可滚动的列表项,毫无疑问得使用scroll-view,而不是view(overflow:auto)了,否则reachBottom的触发就会出问题,由于原本就只有一屏了
加入scroll-view的话,Page下拉加载是跟scroll-view相冲突的,因此要么抛弃下拉加载,要么只能使用触顶加载
scroll-view有一个地方很容易让人忽视,就是你在绑定scrolltoupper以及bindscrolltolower方法,你会困惑为什么并非滑到顶部和底部再触发事件,而是接近的时候才触发,其实仔细看文档你会发现
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
upper-threshold | Number | 50 | 距顶部/左边多远时(单位px),触发 scrolltoupper 事件 |
lower-threshold | Number | 50 | 距底部/右边多远时(单位px),触发 scrolltolower 事件 |
它的默认值是50,因此距离50px就会触发,因此若是要真正地触顶(底),能够先设置它们为0
开发时用到video组件,遇到一些问题也拿出来说下
首先开发者须要记住的一个很重要的点
map、canvas、video、textarea 是由客户端建立的原生组件,原生组件的层级是最高的,因此页面中的其余组件不管设置 z-index 为多少,都没法盖在原生组件上。 原生组件暂时还没法放在 scroll-view 上,也没法对原生组件设置 css 动画。
其次video组件是没办法跟着屏幕滚动的,假如你放了一个video组件fixed在顶部,它也是没法跟着屏幕滚动的,开发者工具能够实现,可是真机滚动后是会出现黑影的,视频仍是一直定位在原来的位置(这个也体现了本文开头的环境的区别),要解决这个问题就只能是不能全屏滚动,用页面的一部分scroll-view滚动便可,让视频不用滚动
还有一个就是video组件其实你用wx:if去控制渲染隐藏是有问题的,当你屡次切换,会发如今某些机型上发热严重,抓包发现以前建立的video实例并无真正地随着wx:if销毁,还在请求数据,因此,假如须要控制渲染隐藏video组件的时候,能够尝试使用hidden属性配合wx.createVideoContext控制暂停来解决问题
近期官网也出来了一个优化建议,开发者务必要看看
大致上就是
不得不吐槽小程序的文档搜索功能实在是太差了,基本是没法使用的,建议直接当前页面command+F去搜索,看文档必须注意看文档中的tip,这样就能够躲过不少坑
谢谢阅读~
欢迎follow我哈哈github.com/BUPT-HJM
欢迎继续观光个人新博客~(老博客近期可能迁移)
欢迎关注