微信小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。对于开发者而言,微信小程序开发门槛相对较低,难度不及APP,能够满足简单的基础应用,适合生活服务类线下商铺以及非刚需低频应用的转换。微信小程序能够实现消息通知、线下扫码、公众号关联等七大功能。其中,通过公众号关联,用户可以实现公众号与微信小程序之间相互跳转。
小程序 VS HTML5
小程序并不是 HTML5 应用,而是更偏向于传统的 CS 架构,它是基于数据驱动的模式,一切皆组件(视图组件)。下面是小程序与普通 Web App 的对比。
- 普通 HTML5 都是执行在浏览器的宿主环境,浏览器提供
window
、document
等 BOM 对象,但小程序没有window
、document
,它更像是一个类似 Node.js 的宿主环境;因此在小程序内不能使用document.querySelector
这类 DOM 选择器,也不支持XMLHttpRequest
、location
、localStorage
等这些浏览器提供的 API,只能使用小程序自己实现的 API - 小程序并非是直接通过 URL 访问的,而是通过信道服务进行通信和会话管理,所以它不支持 Cookie 存储,同时访问资源使用
wx.request
则不存在跨域的问题 - 小程序在 JavaScript 的模块化上支持 CommonJS,通过 require 加载,跟 Node.js 类似
- 小程序的页面样式完全继承了 CSS 的语法,但是在选择器上面会少一些,布局支持 flex 布局
- 小程序的整体框架采用面向状态编程方式,状态管理从 API 来看采用类似 Redux 的设计方式;单向数据绑定方式,当 View 在 Action 操作后,只能通过 Action 的业务处理来更新 View
页面组件模块上,WXML 提供了一整套的「自定义 UI 组件标签」,有些组件实际是 HTML5 实现的,有些组件为了解决权限、性能和适配等问题实际是 Native 实现的(如 map、input、canvas、video)。
小程序目录结构
小程序项目由配置文件、页面文件、静态资源和其他相关(比如组件、小程序云函数等)内容组成,一般小程序会由四类文件组成:
.json
后缀的 JSON 配置文件.wxml
后缀的 WXML 模板文件.wxss
后缀的 WXSS 样式文件.js
后缀的 JS 脚本逻辑文件
小程序的配置
小程序有三个重要的配置,分别放在三个 JSON 文件内:project.config.json
(工具项目配置)、app.json
(小程序配置)、page.json
(单页面配置)
project.config.json
:这个是配置项目工具相关的,比如开发者工具的编译设置(是否使用 ES6 语法等)、界面设置,以及云函数相关的cloudfunctionRoot
,详细可以参考项目配置文件app.json
:小程序的全局配置,包括了所有页面路径、界面表现、网络超时时间、底部 tab、插件等,常用的两个配置是window
和pages
,详细配置参考全局配置page.json
:是相对于app.json
更细粒度的单页面配置,详细参考页面配置
组件
小程序内部定义了很多组件,可以对应 HTML5 的标签和基础能力来理解,小程序的组件根据实现不同,可以分为 Web 组件和 Native 组件,Web 组件是由 HTML5 原生 Web 组件封装的组件,比如 view
、image
等;Native 组件是为了增强小程序的体验,用客户端技术实现的组件,包括一些交互复杂、原生 Web 组件性能不高的组件,例如 input
、map
、canvas
、video
等。
小程序一共提供 8 大类 30 多个组件:
- 视图容器:主要是实现页面布局的,对常见的布局形式进行了封装,比如滚动 sroll-view 等
- 基本内容:类似 HTML5 中内容相关的 p、em 等
- 表单相关:要比 HTML5 的 form 表单丰富一些
- 导航:类似 a 标签
- 媒体:类比 HTML5 中的 video、audio 和 img 等,但是提供更标准的界面和更丰富的 API 支持
- 画布:Native 实现的 Canvas
- 地图:结合腾讯地图数据 Native 实现的组件
- 开放能力:这部分组件偏通用和小程序业务
自定义组件
小程序本身支持很多组件,比如地图、按钮等,开发者也可以自己做项目内公共组件,比如我们后面实战部分会介绍做一个 icon 组件,放在 components
目录下面,这样此小程序的任何页面如果要使用这个 icon 公共组件,只需在自己的 page.json
中添加如下字段:1
2
3"usingComponents": {
"icon": "../../components/icon/index"
}
添加完成之后,在页面代码中就可以直接使用 <icon>
的 tag 了。官方文档有更加详细的介绍。
插件
插件是对一组 JS 接口、自定义组件或页面的封装,用于提供给第三方小程序调用。简单来说,插件是组件的升级版本,组件只能在自己项目中使用,插件则更独立,是可以发布到全网,供其他开发者使用的。例如实战中,笔者使用了一款日历插件,则需要在 app.json
中增加 plugins
字段:1
2
3
4
5
6"plugins": {
"calendar": {
"version": "1.1.3",
"provider": "wx92c68dae5a8bb046"
}
}
如果想开发一个插件,则可以参考官方文档。
数据驱动
微信小程序是数据驱动模型,在 WXML 中可以对页面的数据进行绑定,小程序的 WXML 内使用的是 Mustache 语法,在 {{}}
内可以将变量名包裹起来。 例如:1
2
3
4
5
6<view>{{ message }}</view>
Page({
data: {
message: 'Hello MINA!'
}
})
但是小程序不支持复杂的表达式,目前支持简单的三元、加减和逻辑判断,如果要使用形如 {{parseInt(num)}}
的函数调用语法,需要 WXS
支持:WXML 内容:1
2<wxs src="./demo.wxs" module="tools" />
<view>{{ tools.toNumber(num) }}</view>
WXS 内容:1
2
3
4
5// demo.wxs
function toNumber(n){
return parseInt(n)
}
module.exports.toNumber = toNumber
小程序内对页面的数据修改只能通过 setData
方法,不能使用直接赋值的方式 this.data.key = value
:1
2
3
4
5
6
7
8
9
10Page({
data: {
message: 'Hello MINA!'
},
onLoad(){
this.setData({
message: 'hello world~'
})
}
})
记住:修改页面数据,只能使用 this.setData 修改!
路由
在小程序内,不能像 HTML5 中 a 标签那样,随便跳转,也不能像 location
对象中对应的属性那样随意跳转,小程序提供了对应 a 标签和 location
对象的方法:navigator 组件和 wx 中的导航相关函数。
在小程序中,路由是由路由栈来维护的,小程序的路由栈中最多维护 5 个页面,这样在 5 个页面内,小程序维护其渲染页面,可以实现快速的切换。
小程序中跳转页面可以通过下面两种方式:
使用 navigator 组件:
1
<navigator url="跳转页面URL" >跳转到新页面</navigator>
使用
wx
中的导航相关函数:
1 | <view bindtap="gotoUrl">跳转页面</view> |
JavaScript 的限制和增强
微信内的 JavaScript 相对于浏览器中的有限制也有增强,增强的部分是基于小程序 Native 端能力做的增强,比如增强的文件操作类(相册、录音等);除了增强,跟 HTML5 浏览器环境最大的不同是限制部分。
小程序的执行环境是没有浏览器了,所以浏览器环境特有的 window
对象、BOM 和 DOM 等相关 API 都存在缺失(有对应的补充 API),小程序的执行环境是类似于 Node.js 的一种执行环境。因为没有浏览器环境,所以跟浏览器相关的操作如 cookie、Ajax 请求(XMLHttpRequest
)、DOM 选择器、DOM 事件、路由 history、缓存、设备信息、位置等都不存在,与之相对应的是小程序的私有 API,比如我们在小程序中不能使用 XMLHttpRequest
,但是可以使用功能更加强大的 wx.request
方法。
小程序架构解密
小程序架构如上图所示,分为视图层和逻辑层,视图层是在 WebView 内渲染,逻辑层则有 JavaScriptCore 来渲染;其中视图层可以多个(考虑到整体性能,最多可以 5 个),逻辑层则全局只有一个(实际通过开启 X5 内核另起一个 JavascriptCore 线程)。
视图层是 WebView,逻辑层为 JavaScriptCore,证据如下:使用 Android 手机,开启 X5 内核 debug 之后,在 Chrome inspect 中可以看到下图所示的内容。
在小程序内,视图层负责页面渲染,逻辑层负责逻辑处理、全局状态管理、请求和接口调用。逻辑层在小程序中称为 APP Service
,视图层称为 View
。
逻辑层和视图层通过微信的 JSBridge
来实现通信的,逻辑层数据变化通过 JSBridge
通知视图层,触发视图层更新;当视图层触发事件,则继续通过 JSBridge
将事件通知到逻辑层做处理,如此交互进行。
JSBridge
在三个环境(开发者工具、iOS 和 Android)中实现机制不同,在调用 Native 能力时主要使用 invokeHandler
:
- 开发者工具:通过
window.postMessage
来封装 - iOS:通过 WKWebview 的
window.webkit.messageHandlers.invokeHandler.postMessage
- Android:通过
WeixinJSCore.invokeHandler
在消息分发的时候,则使用 publishHandler
:
- 开发者工具:通过
addEventListener('message')
来监听消息,然后处理分发 - iOS:使用 WKWebview 的
window.webkit.messageHandlers.publishHandler.postMessage
- Android:通过
WeixinJSCore.publishHandler
其中,Android 的 WeixinJSCore
是 X5 内核暴露出来的对象,其作为 window
对象的一个属性,提供一些供 JavaScript 调用的能力。
WeixinJSBridge 提供的方法有 invoke
、publish
和 subscribe
等,invoke
就是关键的调用 Native 端能力的方法,publish
是消息分发的方法。
因为在一个小程序中可以打开多个视图层(webview),要保证发送的消息准确送到每个具体的 webview 中,需要通过每个 webview 唯一标识 webviewId 来实现。发送消息时,携带 webviewId,然后逻辑层处理完对应的逻辑,如果需要通知或者执行对应 webview 的代码,则可以通过 webviewId 找到对应的 webview,下发通知。
理解小程序生命周期
小程序生命周期包括应用的生命周期(逻辑层 App Service)和页面的生命周期(视图层 View),两者支持的事件不同,详见官方文档中的这张配图。
掌握了上面小程序实现原理的内容,再来看小程序的生命周期就很好理解了。
小程序启动时,会同时启动两个线程,一个负责页面渲染的 WebView(实际不止一个,后面讲解),一个负责逻辑的 JavaScriptCore。逻辑层初始化后会将初始化数据(app.js 中的 global data)通过 JSBridge 传递给渲染层进行渲染,渲染层 WebView 页面渲染完之后又会跟逻辑层通信。
cover-view/cover-image
在微信小程序经常会用到一些原生组件,比如map、video、canvas、camera,这些原生组件想让其他元素覆盖在其上,必须使用cover-view或者cover-image组件.
cover-view
覆盖在原生组件之上的文本视图,可覆盖的原生组件包括map、video、canvas、camera
,只支持嵌套cover-view、cover-image
。
注意:只支持基本的定位、布局、文本样式、背景色。不支持设置单边的border、background-image、shadow、overflow: visible
等。可用cover-image
替换background-image
cover-image
覆盖在原生组件之上的图片视图,可覆盖的原生组件同cover-view
,支持嵌套在cover-view
里,image组件基本一样.