微信小程序开发深入解读

下面结合开发文档以及个人开发经验对微信小程序关键部分进行解读(不是入门教程,具体入门读者可以看官网),希望看完的读者对微信小程序有大概的认识或者有所启发。

本文同步于个人博客 www.imhjm.com/article/597…

官方开发文档 mp.weixin.qq.com/debug/wxado…
官方开发者社区 developers.weixin.qq.com/

运行环境

微信小程序运行在三端:iOS、Android 和 用于调试的开发者工具。
三端的脚本执行环境聚以及用于渲染非原生组件的环境是各不相同的:

  • 在 iOS 上,小程序的 javascript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的,环境有 iOS8、iOS9、iOS10
  • 在 Android 上,小程序的 javascript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 53 内核来渲染的
  • 在 开发工具上, 小程序的 javascript 代码是运行在 nwjs 中,是由 Chrome Webview 来渲染的

引用:mp.weixin.qq.com/debug/wxado…

正由于脚本执行环境的不同,所以真机与开发者工具有些表现还是差异挺大的,特别表现在原生组件方面(后面会讲到部分原生组件注意点),iOS以及Android都需要多加测试才能保证程序没有问题。
同时因为是在JsCore中执行,JsCore没有窗口对象,所以没有window、document等等(所以很多外部生态插件/库无法直接使用,需要稍作修改)

生命周期

小程序全局有App、Page内置的全局变量,用于注册小程序以及注册页面

App实例生命周期

  • onLaunch
    监听小程序初始化,当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
  • onShow
    监听小程序显示 当小程序启动,或从后台进入前台显示,会触发 onShow
  • onHide
    监听小程序隐藏 当小程序从前台进入后台,会触发 onHide

前台、后台定义: 当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。需要注意的是:只有当小程序进入后台一定时间,或者系统资源占用过高,才会被真正的销毁。

Page实例生命周期

具体读者可以看文档中的「Page实例生命周期」,左边是视图线程,右边是逻辑层线程
可以看到View Thread分四个阶段

  • Start
  • Inited
  • Ready
  • End

AppSevice Thread也分四个阶段

  • Start
  • Created
  • Active (Alive)
  • End

我们从图中可以简单地分析出「Page实例生命周期」

  • View Thread以及AppSevice Thread进入Start
  • AppSevice Thread调用Page方法传入配置Created后,调用onLoad(监听页面加载)以及onShow(监听页面显示)方法,AppSevice Thread等待View Thread的通知
  • View Thread进入初始化阶段(Inited)后,通知(Notify)AppSevice Thread已经初始化好了,然后AppSevice Thread传入App实例中的初始化数据,AppSevice Thread等待View Thread的下一次通知
  • View Thread收到初始化的数据之后,第一次渲染页面(First Render),进入Ready阶段,渲染完毕通知AppSevice Thread,AppSevice Thread调用onReady方法,进入Active阶段
  • 在AppSevice Thread Active阶段,会调用一些setData的方法,就是传递数据给View Thread中的渲染器(Rerender),进行视图更新
  • 当小程序切到后台或者当前Page跳转(具体看后面路由部分或文档)调用onHide方法,进入Alive阶段,再切回来前台调用onShow进入Active阶段
  • 最后Page销毁,调用onUnload方法,页面卸载

从上面声明周期的分析,我们可以得到以下几个结论:

  • onLoad只调用一次,onShow页面显示多次调用
  • First Render是Page传入的data数据进行Render,在onLoad阶段进行setData其实也是在进入Active阶段发送视图更新的(也就是在OnReady后),所以,假如在onLoad阶段setData跟Intial Data不一样的数据,是可以看到页面闪烁了一下的

Page实例生命周期

数据驱动(响应的数据绑定)

从生命周期也可以看出微信小程序跟vue等框架类似,是数据驱动视图更新,在逻辑层修改数据,视图层响应数据更新

双括号绑定数据

 {{ message }} 

Page({
  data: {
    message: 'Hello MINA!'
  }
})复制代码

如上使用双括号,便实现数据与视图绑定

数据单向流动

微信小程序同样是数据单向流动,而不是双向绑定,比如你传入它基础组件的某些数据,并不能同步到你的data中,而是调用某些监听函数去获取(比如scroll-view中scroll-top,你能通过视图传入data更新滚动位置,但是你在滚动的时候,并不能双向绑定去获取scroll-top,而是需要监听bindscroll去获取)

条件渲染&&列表渲染

条件渲染以及列表渲染作为数据驱动视图的重要部分,值得一提

1.条件渲染的wx:if以及hidden

  • wx:if会产生局部渲染,销毁条件块(或者重新渲染)
  • hidden就是直接控制display block/none了

所以官网给出的结论是

一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。

2.列表渲染

for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="*this">
  {{idx}}: {{itemName.message}}
复制代码

这里其他for,index,item这些循环渲染基本的东西就不具体说了,谈谈这个wx:key

假如我们更新array数组,预期来说视图重新渲染,但是我们假如只是在array中push更多的元素,我们的想法应该是重新排序,不去重复创建视图原来已经有的元素,这里为了标识item,我们就可以用wx:key,有助于提升渲染的效率,并且能够保持状态(如 中的输入内容, 的选中状态)

路由管理

小程序的路由管理部分均由框架处理,开发者只需调用API即可,但是还是有一些地方需要注意

文档: mp.weixin.qq.com/debug/wxado…

小程序的路由管理是用一个页面栈来维护,通过出栈以及入栈加载不同页面,可以用getCurrentPages()获取一个栈数组

下面这个表格根据官网两个表格整合而成,注意区分各种触发时机以及页面栈的表现

路由方式 触发时机 页面栈表现 路由前页面 路由后页面调用方法
初始化 小程序打开的第一个页面 新页面入栈 onLoad, onSHow
打开新页面 调用 API wx.navigateTo 或使用组件 新页面入栈 onHide onLoad, onShow
页面重定向 调用 API wx.redirectTo 或使用组件 当前页面出栈,新页面入栈 onUnload onLoad, onShow
页面返回 调用 API wx.navigateBack 或使用组件或用户按左上角返回按钮 页面不断出栈,直到目标返回页,新页面入栈 onUnload onShow
Tab 切换 调用 API wx.switchTab 或使用组件 或用户切换 Tab 页面全部出栈,只留下新的 Tab 页面 具体看官网
重启动 调用 API wx.reLaunch 或使用组件 页面全部出栈,只留下新的页面 onUnload onLoad, onShow

注意区分页面重定向(redirectTo)以及打开新页面(navigateTo),因为小程序限制了也页面栈最多只有5个元素,所以当你深度达到5个,再调用navigateTo想让新页面再入栈就会报错,所以官方建议是

避免多层级的交互方式,或者使用wx.redirectTo

模块化&&组件/模板

js模块化

小程序默认使用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模板

wxml通过template可以实现复用
通过is属性动态决定渲染哪个模版




for="{{[1, 2, 3, 4, 5]}}">