目录结构

根目录生成:项目配置文件project.config.json。setting属性下包含:

配置层

小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。

  • app有 app.js / app.json / app.wxss 文件。
  • 小程序页面有 js/wxml/json/wxss 文件。

app.json:文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
(pages, window, tabBar, networkTimeout)
普通json:页面的配置只能设置 app.json 中部分 window 配置项的内容,页面中配置项会覆盖 app.json 的 window 中相同的配置项。

逻辑层

小程序框架的逻辑层并非运行在浏览器中,因此 JavaScript 在 web 中一些能力都无法使用,如 window,document 等。
app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 的 onLaunch 和 onShow,或wx.getLaunchOptionsSync 中获取场景值。
wx.getLaunchOptionsSync(path, scene, query, shareTicker, refererInfo) 获取小程序启动时的参数。

普通js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// index.js
Page({
data: {
text: 'This is page data.'
},
onLoad(options) {
// Do some initialize when page load.
},
onReady() {
// Do something when page ready.
},
onShow() {
// Do something when page show.
},
onHide() {
// Do something when page hide.
},
onUnload() {
// Do something when page close.
},
onPullDownRefresh() {
// Do something when pull down.
},
onReachBottom() {
// Do something when page reach bottom.
},
onShareAppMessage() {
// return custom share data when user share.
},
onPageScroll() {
// Do something when page scroll
},
onResize() {
// Do something when page resize
},
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// Event handler.
viewTap() {
this.setData({
text: 'Set some data for updating view.'
}, function () {
// this is setData callback
})
},
customData: {
hi: 'MINA'
}
})

生命周期:
onLoad() 页面加载时触发,只会调用一次,可获取当前页面路径中的参数。
onShow() 页面显示/切入前台时触发,一般用来发送数据请求;
onReady() 页面初次渲染完成时触发, 只会调用一次,代表页面已可和视图层进行交互。
onHide() 页面隐藏/切入后台时触发, 如底部 tab 切换到其他页面或小程序切入后台等。
onUnload() 页面卸载时触发,如redirectTo或navigateBack到其他页面时。

页面路由

Tab 切换:页面全部出栈,只留下新的 Tab 页面
初始化:新页面入栈
打开新页面:新页面入栈
页面重定向:当前页面出栈,新页面入栈
页面返回:页面不断出栈,直到目标返回页

getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。

navigateTo, redirectTo 只能打开非 tabBar 页面。
switchTab 只能打开 tabBar 页面。
reLaunch 可以打开任意页面。
页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
调用页面路由带的参数可以在目标页面的onLoad中获取

wx.navigateTo 或使用组件 <navigator open-type="navigateTo"/>
url: 类型是String 跳转非 tabBar 的页面的路径 , 路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 ‘path?key=value&key2=value2’
success: 类型Function 接口调用成功的回调函数
fail: 类型Function 接口调用失败的回调函数
complete: 类型Function 接口调用结束的回调函数(调用成功、失败都会执行)

1
2
3
4
5
6
wx.navigateTo({
url: 'test?id=1'
})
wx.switchTab({
url: '/index' //不能带参数
})

模块化

使用 require(path) 将公共代码引入
module.exports 导出

且有模板:

1
2
3
4
5
6
<template name="head">
<view class='head-container'>
{{ info }}
<image src='../../images/icons/back.png' class='back-icon' wx:if="{{hasBack}}" bindtap='toBack'></image>
</view>
</template>

使用模板:

1
2
3
<import src="../../components/head.wxml"/>

<template is="head" data="{{info:'灯光设置', hasBack: true}}"/>

C import B,B import A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template。
include 可以将目标文件除了 <template/> <wxs/> 外的整个代码引入,相当于是拷贝到 include 位置

1
2
3
4
<!-- index.wxml -->
<include src="header.wxml" />
<view>body</view>
<include src="footer.wxml" />

wxs:(像computer方法)
是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
<wxs module="m1">
var getMax = function(array) {
var max = undefined;
for (var i = 0; i < array.length; ++i) {
max = max === undefined ? array[i] : (max >= array[i] ? max : array[i]);
}
return max;
}
module.exports.getMax = getMax;
</wxs>

<!-- 调用 wxs 里面的 getMax 函数,参数为 page.js 里面的 array -->
<view>{{m1.getMax(array)}}</view>

在.wxs模块中引用其他 wxs 文件模块,可以使用 require 函数。

  • 只能引用 .wxs 文件模块,且必须使用相对路径。
  • wxs 模块均为单例,wxs 模块在第一次被引用时,会自动初始化为单例对象。多个页面,多个地方,多次引用,使用的都是同一个 wxs 模块对象。
  • 如果一个 wxs 模块在定义之后,一直没有被引用,则该模块不会被解析与运行。

存储与请求

1
2
wx.setStorageSync('key', 'value')  //同步
wx.getStorageSync('key')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
wx.request({
url: apiRoot + options.url,
data: options.data,
method: options.method ? options.method : 'POST',
header: {
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded',
'token': token,
'XX-Device-Type': 'wxapp',
'XX-Api-Version': this.API_VERSION
},
success: res => {

},
fail: function (res) {

},
complete: options.complete ? options.complete : null
});

wx.login({
success: loginRes => {
if (loginRes.code) { //开发者需要在开发者服务器后台调用 code2Session,使用 code 换取 openid 和 session_key 等信息
wx.getUserInfo({
withCredentials: true,
success: res => {
const userInfo = res.userInfo
const nickName = userInfo.nickName
const avatarUrl = userInfo.avatarUrl
const gender = userInfo.gender // 性别 0:未知、1:男、2:女
const province = userInfo.province
const city = userInfo.city
const country = userInfo.country
},
fail: (res) => {

}
});


} else {
}
},
fail: () => {
}
})

开发者可以使用 wx.getSetting 获取用户当前的授权状态。
wx.authorize(Object object) //提前向用户发起授权请求。
wx.getUserInfo使用前要获取授权。

如何封装数据请求:
在根目录下创建utils目录及api.js文件和apiConfig.js文件;
在apiConfig.js 封装基础的get, post 和 put, upload等请求方法,设置请求体,带上token和异常处理等;
在api中引入apiConfig.js封装好的请求方法,根据页面数据请求的urls, 设置对应的方法并导出;
在具体的页面中导入。

小程序只可以跟指定的域名与进行网络通信。包括普通 HTTPS 请求(wx.request)、上传文件(wx.uploadFile)、下载文件(wx.downloadFile) 和 WebSocket 通信(wx.connectSocket)

  • 域名只支持 https

视图wxss

和css一样,都是用来描述页面的样子;
WXSS 具有 CSS 大部分的特性,也做了一些扩充和修改;
WXSS新增了尺寸单位,WXSS 在底层支持新的尺寸单位 rpx;
WXSS 仅支持部分 CSS 选择器;
WXSS 提供全局样式与局部样式。

节点信息获取

节点信息查询 API 可以用于获取节点属性、样式、在界面上的位置等信息。
最常见的用法是使用这个接口来查询某个节点的当前位置,以及界面的滚动位置。

1
2
3
4
5
6
7
8
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect(function (res) {
res.top // #the-id 节点的上边界坐标(相对于显示区域)
})
query.selectViewport().scrollOffset(function (res) {
res.scrollTop // 显示区域的竖直滚动位置
})
query.exec()

原理

小程序本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口;
它的架构,是数据驱动的架构模式,它的UI和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现;
它从技术讲和现有的前端开发差不多,采用JavaScript、WXML、WXSS三种技术进行开发;
功能可分为webview和appService两个部分;
webview用来展现UI,appService有来处理业务逻辑、数据及接口调用;
两个部分在两个进程中运行,通过系统层JSBridge实现通信,实现UI的渲染、事件的处理等。

架构:
微信小程序的框架包含两部分View视图层、App Service逻辑层,View层用来渲染页面结构,AppService层用来逻辑处理、数据请求、接口调用,它们在两个进程(两个Webview)里运行。
小程序启动时会从CDN下载小程序的完整包。

实现:
UI视图和逻辑处理是用多个webview实现的,逻辑处理的JS代码全部加载到一个Webview里面,称之为AppService,整个小程序只有一个,并且整个生命周期常驻内存,而所有的视图(wxml和wxss)都是单独的Webview来承载,称之为AppView。所以一个小程序打开至少就会有2个webview进程,正式因为每个视图都是一个独立的webview进程。

AppService:
可以理解AppService即一个简单的页面,主要功能是负责逻辑处理部分的执行,底层提供一个WAService.js的文件来提供各种api接口,主要是以下几个部分:
消息通信封装为WeixinJSBridge(开发环境为window.postMessage, IOS下为WKWebview的window.webkit.messageHandlers.invokeHandler.postMessage,android下用WeixinJSCore.invokeHandler)
1、日志组件Reporter封装
2、wx对象下面的api方法
3、全局的App,Page,getApp,getCurrentPages等全局方法
4、还有就是对AMD模块规范的实现
然后整个页面就是加载一堆JS文件,包括小程序配置config,上面的WAService.js(调试模式下有asdebug.js),剩下就是我们自己写的全部的js文件,一次性都加载。

上线后是应用部分会打包为2个文件,名称app-config.json和app-service.js,然后微信会打开webview去加载。线上部分应该是微信自身提供了相应的模板文件

AppView:
可以理解为h5的页面,提供UI渲染,底层提供一个WAWebview.js来提供底层的功能:
消息通信封装为WeixinJSBridge(开发环境为window.postMessage, IOS下为WKWebview的window.webkit.messageHandlers.invokeHandler.postMessage,android下用WeixinJSCore.invokeHandler)
2、日志组件Reporter封装
3、wx对象下的api,这里的api跟WAService里的还不太一样,有几个跟那边功能差不多,但是大部分都是处理UI显示相关的方法
4、小程序组件实现和注册
5、VirtualDOM,Diff和Render UI实现
6、页面事件触发

通信:
使用消息publish和subscribe机制实现两个Webview之间的通信,实现方式就是统一封装一个WeixinJSBridge对象,而不同的环境封装的接口不一样。

web-view页面分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<web-view src="https://mp.weixin.qq.com/" bindmessage"bindGetMsg"></web-view>

//向小程序发送消息,会在特定时机(小程序后退、组件销毁、分享)触发组件的message事件
wx.miniProgram.postMessage({
data:'foo'
})
wx.miniProgram.postMessage({
data: {foo: 'bar'}
})
bindGetMsg(e) {
// e.detail = { data },data是多次 postMessage 的参数组成的数组
// e.detail.data[0]
let options = e.detail.data[0]
this.setData({
w_title: options.goods_name,
img: util.httpUrl + options.img,
share_url: '/pages/detailed/detailed?team_id=' + options.team_id + '&goods_id=' + options.goods_id
})
}
//分享的时候获取这些data
onShareAppMessage: function () {
return {
title: this.data.w_title,
imageUrl: this.data.img,
path: this.data.share_url
}
}