闲谈微信小程序开发

什么是小程序

微信创始人张小龙说:小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫后者搜一下即可打开应用。也提现了“用完即走”的理念,用户不用关系是否安装太多应用的问题。应用将无处不在,随时可用,担又无需安装卸载。

了解小程序

相关文档

微信公众平台

分为服务号、订阅号、小程序三大账号体系。其中服务号是给企业和组织提供更强大的业务服务与用户管理能力,帮助企业快速实现全新的公众号服务平台。订阅号是为媒体和个人提供一种新的信息传播方式,构建与读者之间更好的沟通与管理模式。小程序是一种新开放能力,可以在微信内被便捷地获取和传播,同时具有出色的使用体验。

小程序开发文档

开发准备

代码构成

  • json 后缀的 JSON 配置文件
  • wxml 后缀的 WXML 模板文件
  • wxss 后缀的 WXSS 样式文件
  • js 后缀的 JS 脚本逻辑文件
json配置文件

app.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
}
}

index.json

1
2
3
{
"enablePullDownRefresh": false
}

WXML 模板文件

index.wxml

1
2
3
4
5
6
7
8
9
10
11
12
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>

WXSS 样式文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}

.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}

.userinfo-nickname {
color: #aaa;
}

.usermotto {
margin-top: 200px;
}
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
53
54
//index.js
//获取应用实例
const app = getApp()

Page({
data: {
motto: 'Hello World',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse){
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function(e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
}
})

生命周期

应用生命周期

属性 类型 描述 触发时机
onLaunch Function 生命周期函数–监听小程序初始化 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
onShow Function 生命周期函数–监听小程序显示 当小程序启动,或从后台进入前台显示,会触发 onShow
onHide Function 生命周期函数–监听小程序隐藏 当小程序从前台进入后台,会触发 onHide
onError Function 错误监听函数 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
onPageNotFound Function 页面不存在监听函数 当小程序出现要打开的页面不存在的情况,会带上页面信息回调该函数,详见下文
其他 Any 开发者可以添加任意的函数或数据到 Object 参数中,用 this 可以访问

页面生命周期、初始数据、事件处理函数

属性 类型 描述
data Object 页面的初始数据
onLoad Function 生命周期函数–监听页面加载
onReady Function 生命周期函数–监听页面初次渲染完成
onShow Function 生命周期函数–监听页面显示
onHide Function 生命周期函数–监听页面隐藏
onUnload Function 生命周期函数–监听页面卸载
onPullDownRefresh Function 页面相关事件处理函数–监听用户下拉动作
onReachBottom Function 页面上拉触底事件的处理函数
onShareAppMessage Function 用户点击右上角转发
onPageScroll Function 页面滚动触发事件的处理函数
onTabItemTap Function 当前是 tab 页时,点击 tab 时触发
其他 Any 开发者可以添加任意的函数或数据到 object 参数中,在页面的函数中用 this 可以访问

路由相关

路由方式 触发时机 路由前页面 路由后页面
初始化 小程序打开的第一个页面 onLoad, onShow
打开新页面 调用 API wx.navigateTo 或使用navigator组件 onHide onLoad, onShow
页面重定向 调用 API wx.redirectTo 或使用navigator组件 onUnload onLoad, onShow
页面返回 调用 API wx.navigateBack 或使用navigator组件 或用户按左上角返回按钮 onUnload onShow
Tab 切换 调用 API wx.switchTab 或使用navigator组件 或用户切换 Tab 各种情况请参考下表
重启动 调用 API wx.reLaunch 或使用navigator组件 onUnload onLoad, onShow
1
2
3
4
5
<navigator open-type="navigateTo"/>
<navigator open-type="redirectTo"/>
<navigator open-type="navigateBack">
<navigator open-type="switchTab"/>
<navigator open-type="reLaunch"/>

基本语法

数据绑定

数据绑定使用 Mustache 语法(双大括号)将变量包起来。

1
<view> {{ message }} </view>

1
2
3
4
5
Page({
data: {
message: 'Hello MINA!'
}
})

三元运算

1
<view hidden="{{flag ? true : false}}"> Hidden </view>

算数运算

1
<view> {{a + b}} + {{c}} + d </view>

1
2
3
4
5
6
7
Page({
data: {
a: 1,
b: 2,
c: 3
}
})

逻辑判断

1
<view wx:if="{{length > 5}}"> </view>

列表渲染

1
2
3
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
1
2
3
4
5
6
7
8
9
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})

使用 wx:for-item 可以指定数组当前元素的变量名,
使用 wx:for-index 可以指定数组当前下标的变量名:

条件渲染

在框架中,使用 wx:if=”“ 来判断是否需要渲染该代码块:

1
<view wx:if="{{condition}}"> True </view>

也可以用 wx:elif 和 wx:else 来添加一个 else 块:

1
2
3
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。

1
2
3
4
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>

事件处理

  • 事件是视图层到逻辑层的通讯方式。
  • 事件可以将用户的行为反馈到逻辑层进行处理。
  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
  • 事件对象可以携带额外信息,如 id, dataset, touches。

如bindtap,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。

1
<view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>

在相应的Page定义中写上相应的事件处理函数,参数是event。

1
2
3
4
5
Page({
tapName: function(event) {
console.log(event)
}
})

可以看到log出来的信息大致如下:

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
{
"type":"tap",
"timeStamp":895,
"target": {
"id": "tapTest",
"dataset": {
"hi":"WeChat"
}
},
"currentTarget": {
"id": "tapTest",
"dataset": {
"hi":"WeChat"
}
},
"detail": {
"x":53,
"y":14
},
"touches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}],
"changedTouches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}]
}

引用

import可以在该文件中使用目标文件定义的template,如:
在 item.wxml 中定义了一个叫item的template:

1
2
3
4
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>

在 index.wxml 中引用了 item.wxml,就可以使用item模板:

1
2
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>

WXS 模块

每一个 .wxs 文件和 标签都是一个单独的模块。
每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。
一个模块要想对外暴露其内部的私有变量与函数,只能通过 module.exports 实现。

1
2
3
4
5
6
7
8
9
10
// /pages/tools.wxs
var foo = "'hello world' from tools.wxs";
var bar = function (d) {
return d;
}
module.exports = {
FOO: foo,
bar: bar,
};
module.exports.msg = "some msg";

1
2
3
<wxs src="./../tools.wxs" module="tools" />
<view> {{tools.msg}} </view>
<view> {{tools.bar(tools.FOO)}} </view>

页面输出:

1
2
some msg
'hello world' from tools.wxs

自定义组件

js配置文件

1
2
3
4
{
"component": true,
"usingComponents": {}
}

wxml布局文件编写

1
2
3
4
5
6
7
8
9
10
11
<view class='wx-dialog-container' hidden="{{!isShow}}">
<view class='wx-mask'></view>
<view class='wx-dialog'>
<view class='wx-dialog-title'>{{ title }}</view>
<view class='wx-dialog-content'>{{ content }}</view>
<view class='wx-dialog-footer'>
<view class='wx-dialog-btn' catchtap='_cancelEvent'>{{ cancelText }}</view>
<view class='wx-dialog-btn' catchtap='_confirmEvent'>{{ confirmText }}</view>
</view>
</view>
</view>

wxss样式文件

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
.wx-mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
}

.wx-dialog {
position: fixed;
z-index: 5000;
width: 80%;
max-width: 600rpx;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: #fff;
text-align: center;
border-radius: 3px;
overflow: hidden;
}

.wx-dialog-title {
font-size: 18px;
padding: 15px 15px 5px;
}

.wx-dialog-content {
padding: 15px 15px 5px;
min-height: 40px;
font-size: 16px;
line-height: 1.3;
word-wrap: break-word;
word-break: break-all;
color: #999;
}

.wx-dialog-footer {
display: flex;
align-items: center;
position: relative;
line-height: 45px;
font-size: 17px;
}

.wx-dialog-footer::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
border-top: 1px solid #d5d5d6;
color: #d5d5d6;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}

.wx-dialog-btn {
display: block;
-webkit-flex: 1;
flex: 1;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
position: relative;
}

.wx-dialog-footer .wx-dialog-btn:nth-of-type(1) {
color: #353535;
}

.wx-dialog-footer .wx-dialog-btn:nth-of-type(2) {
color: #3cc51f;
}

.wx-dialog-footer .wx-dialog-btn:nth-of-type(2):after {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 1px;
bottom: 0;
border-left: 1px solid #d5d5d6;
color: #d5d5d6;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}

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
53
54
55
56
57
58
59
60
61
Component({
options:{
multipleSlots: true
},
/**
* 组件的属性列表
*/
properties: {
title:{
type: String,
value: '标题'
},
content: {
type: String,
value: '弹窗内容'
},
cancelText: {
type: String,
value: '取消'
},
confirmText: {
type: String,
value: '确定'
}
},

/**
* 私有数据,组件的初始数据
*/
data: {
isShow: false
},

/**
* 公有方法:组件的方法列表
*/
methods: {
hideDialog() {
this.setData({
isShow: !this.data.isShow
})
},
showDialog() {
this.setData({
isShow: !this.data.isShow
})
},
/*
* 内部私有方法建议以下划线开头
* triggerEvent 用于触发事件
*/
_cancelEvent() {
//触发取消回调
this.triggerEvent("cancelEvent")
},
_confirmEvent() {
//触发成功回调
this.triggerEvent("confirmEvent");
}
}
})

断点调试

您的支持是我原创的动力