【微信小程序】微信小程序学习笔记

一,基本配置

一个小程序里边有下列不同类型的文件:

  1. .json后缀的JSON配置文件
  2. .wxml后缀的WXML模板文件
  3. .wxss后缀的WXSS样式文件
  4. .js后缀的JS脚本逻辑文件

JSON配置

JSON是一种数据格式,并不是编程语言,在小程序中,JSON扮演静态配置的角色。

app.json中进行全局配置,包括小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。

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
{
"pages": [
"pages/index/index",
"pages/logs/logs",
"pages/weixin/weixin",
"pages/wo/wo"
],
"window": {
"navigationBarBackgroundColor": "#FFCF00",
"navigationBarTitleText": "我的应用",
"navigationBarTextStyle": "black",

"enablePullDownRefresh":true,
"backgroundColor": "pink",
"backgroundTextStyle": "dark"
},
"tabBar":{
"color": "#000000",
"selectedColor": "#ddd",
"list":[
{
"text":"微信",
"iconPath":"images/weixin.png",
"selectedIconPath":"images/weixin2.png",
"pagePath":"pages/index/index"
},
{
"text": "我",
"iconPath": "images/wo.png",
"selectedIconPath": "images/wo2.png",
"pagePath": "pages/wo/wo"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}

注意:

  1. json中不能写注释
  2. pages中第一个字段为小程序的首页
  3. 可以通过在pages中写代码添加页面
  4. tabBar中list的第一项必须是pages中的第一项

JSON语法

JSON文件都是被包裹在一个大括号{},通过key-value的方式表达数据。key必须包裹在一个双引号中。JSON的值只能是一下几种数据格式,其他任何格式都会触发报错。

1
2
3
4
5
6
1. 数字,包含浮点数和整数
2. 字符串,需要包裹在双引号中
3. Bool值,true或false
4. 数组,需要包裹在方括号中[]
5. 对象,需要包裹在大括号中{}
6. Null

WXML模板

WXML充当类似HTML的角色。将div标签替换为view标签。

多了wx:if 之类的渲染语法,以及{{ }}表达式。将渲染与逻辑分离。比如:

1, 数据绑定

1
2
<!--WXML-->
<text>{{msg}}</text>
1
2
/*JS*/
this.setData({msg:"Hello World"})

JS只需要管理状态即可。

通过{{ }}的语法把一个变量绑定到界面上,称为数据绑定。

2,列表渲染

1
2
<!--WXML-->
<view wx:for="{{array}}">{{item}}</view>
1
2
3
4
5
6
/*Page.js*/
Page{
data:{
array:[1,2,3,4,5]
}
}

3,条件渲染

1
2
3
4
<!--wxml-->
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view>
<view wx:elif="{{view == 'APP'}}"> APP </view>
<view wx:else="{{view == 'MINA'}}"> MINA </view>
1
2
3
4
5
6
/*Page*/
Page{
data:{
view:"MINA"
}
}

WXSS样式

WXSS具有CSS大部分的特性,小程序在WXSS也做了一些扩充与修改。

  1. WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算像素的烦恼。规定所有的屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

    1582438221515
  2. 提供了全局的样式和局部样式,支持样式导入(@import语句

    1
    2
    3
    4
    /** common.wxss **/
    .small-p{
    padding:5px;
    }
    1
    2
    3
    4
    5
    /** app.wxss **/
    @import "common.wxss";
    .middle-p{
    padding:15px;
    }

常见的选择器有且不限于以下几种:

1582438446115

JS逻辑交互

仅有界面展示是不够的,通过编写JS脚本文件来处理用户的操作。比如:

1
2
<view>{{ msg }}</view>
<button bindtap="clickMe">点击我</button>

点击button按钮时,我们希望把上面msg的内容显示成"Hello World",于是我们在button上声明一个属性bindtap,在JS文件中声明了clickMe方法来响应这次点击操作:

1
2
3
4
5
Page({
clickMe: function() {
this.setData({ msg: "Hello World" })
}
})

二,小程序宿主环境

我们称微信客户端给小程序所提供的环境为宿主环境。小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能。

渲染层和逻辑层

小程序运行环境分成渲染层和逻辑层,其中WXML模板和WXSS样式工作在渲染层,JS脚本工作在逻辑层。

小程序的渲染层和逻辑层分别由两个线程管理:渲染层界面使用WebView进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程。

这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。

1582425605881

通过app.json的pages字段可以知道当前小程序的所有页面路径:

1
2
3
4
5
6
7
8
{
"pages": [
"pages/index/index",
"pages/logs/logs",
"pages/weixin/weixin",
"pages/wo/wo"
]
}

其中,第一个字段表示小程序的首页。于是微信客户端把首页代码装载,通过小程序底层机制,渲染出首页。

小程序启动之后,在app.js定义的App实例的onLaunch回调会被执行:

1
2
3
4
5
App({
onLaunch: function () {
// 小程序启动之后 触发
}
})

整个小程序只有一个App实例,是全部页面共享的。

下面来说说小程序的一个页面是怎么写的:

1
2
3
4
5
6
7
8
Page({
data: { // 参与页面渲染的数据
logs: []
},
onLoad: function () {
// 页面渲染后 执行
}
})

Page是一个页面构造器,这个构造器生成了一个页面。生成页面的时候,小程序框架会把data数据和index.wxml一起渲染出最终的结构,于是就得到了小程序的样子。

在渲染界面之后,页面实例就会收到一个onLoad的回调,可以在这个回调处理自己的逻辑。

组件

小程序提供了丰富的基础组件给开发者,开发者可以像搭积木一样,组合各种组件拼合成自己的小程序。

API

为了让开发者可以很方便的调起微信提供的能力,例如获取用户信息、微信支付等等,小程序提供了很多 API 给开发者去使用。

需要注意的是:多数 API 的回调都是异步,你需要处理好代码逻辑的异步问题。

三,事件

什么是事件?

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

事件的使用方式

  1. 在组件中绑定一个事件处理函数

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

    1
    <view id="tapTest" data-hi="WeChat" bindtap="tapName">Click me!</view>
  2. 在相应的Page定义中写上相应的事件处理函数,参数是event

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

事件分类

事件分为冒泡事件和非冒泡事件

  1. 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递
  2. 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递

WXML冒泡事件列表:

类型 触发条件 最低版本
touchstart 手指触摸动作开始
touchmove 手指触摸后移动
touchcancel 手指触摸动作被打断,如来电提醒,弹窗
touchend 手指触摸动作结束
tap 手指触摸后马上离开
longpress 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 [1.5.0]
longtap 手指触摸后,超过350ms再离开(推荐使用longpress事件代替)
transitionend 会在 WXSS transition 或 wx.createAnimation 动画结束后触发
animationstart 会在一个 WXSS animation 动画开始时触发
animationiteration 会在一个 WXSS animation 一次迭代结束时触发
animationend 会在一个 WXSS animation 动画完成时触发
touchforcechange 在支持 3D Touch 的 iPhone 设备,重按时会触发 [1.9.90]

注:除上表以外的其他组件自定义事件如无特殊声明都是非冒泡事件,如form的submit事件,input的input事件,scroll-view的scroll事件等

绑定数据

在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。在WXML中,这些自定义数据以data-开头,多个单词由连字符-连接。

这种写法中,连字符写法会转换成驼峰写法,而大写子符会自动转成小写字符,如:

  • data-element-type,最终呈现为 event.currentTarget.dataset.elementType;
  • data-elementType,最终呈现为 event.currentTarget.dataset.elementtype.

四,云开发

云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。

初始化云程序

  1. 在project.config.json中添加"cloudfunctionRoot": "<云函数目录>/"

  2. 项目根目录处创建文件夹 <云函数目录>,并配置当前环境

  3. 在app.js的onLaunch中进行云函数初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 云开发初始化
    if (!wx.cloud) {
    console.error('请使用 2.2.3 或以上的基础库以使用云能力')
    } else {
    wx.cloud.init({
    env: 'zzmine-3cgx9',
    traceUser: true,
    })
    }

云数据库

云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。

一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。每个数据表都有权限设置。

每条记录都有一个 _id 字段用以唯一标志一条记录、一个 _openid 字段用以标志记录的创建者,即小程序的用户。需要特别注意的是,在管理端(控制台和云函数)中创建的不会有 _openid 字段,因为这是属于管理员创建的记录。

数据库 API 分为小程序端和服务端两部分,小程序端 API 拥有严格的调用权限控制,开发者可在小程序内直接调用 API 进行非敏感数据的操作。对于有更高安全要求的数据,可在云函数内通过服务端 API 进行操作。云函数的环境是与客户端完全隔离的,在云函数上可以私密且安全的操作数据库。

注意:在小程序端直接使用数据库API一次最多拿到20条数据;要一次取出超过20条数据那么就得写成云函数(云函数中没有wx前缀)

使用 API 操作数据库只需三步:获取数据库引用、构造查询/更新条件、发出请求。以下是一个在小程序中查询数据库的发表于美国的图书记录的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 构造查询语句
// collection 方法获取一个集合的引用
// where 方法传入一个对象,数据库返回集合中字段等于指定值的 JSON 文档。API 也支持高级的查询条件(比如大于、小于、in 等),具体见文档查看支持列表
// get 方法会触发网络请求,往数据库取数据
db.collection('books').where({
publishInfo: {
country: 'United States'
}
}).get({
success: function(res) {
// 输出 [{ "title": "The Catcher in the Rye", ... }]
console.log(res)
}
})

上传数据至云数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const DB = wx.cloud.database().collection("list")
addData:function(){
DB.add({
data:{
name:this.data.name,
age:this.data.age
},
success(res){
console.log("添加成功",res)
},
fail(res){
console.log("添加失败",res)
}
})
}

查询数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//可以用where也可以.doc("_id")
getData:function(){
DB.get({
success(res){
console.log("查询成功",res)
// 弹出模态框
wx.showModal({
title: '提示',
content: 'name:'+res.data[0].name+"\r\nage:"+res.data[0].age,
success (res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
fail(res){
console.log("查询失败",res)
}
})
}

删除云数据库中的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
delData:function(){
//根据姓名获得id
DB.where({
name:this.data.delname
})
.get({
success(res){
console.log("查询id",res);
var delid = res.data[0]._id
console.log(delid)
DB.doc(delid).remove({
success(res){
console.log("删除成功",res)
},
fail(res){
console.log("删除失败",res)
}
})
},
fail(res){
console.log("删除失败",res)
}
})
}

更新云端数据:

1
2
3
4
5
6
7
8
9
10
updateInfo:function(){
DB.doc(this.data.upd_id).update({
data:{
age:this.data.upd_age
},
success:function(res){
console.log("更新成功",res)
}
})
}

云开发数据库提供以下几种数据类型:

1
2
3
4
5
6
7
8
String:字符串
Number:数字
Object:对象
Array:数组
Bool:布尔值
Date:时间
Geo:多种地理位置类型,详见下
Null

从云函数调用一次获取20条以上信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 云函数
const cloud = require('wx-server-sdk')

cloud.init({
env: 'zzmine-3cgx9'
})

// 云函数入口函数
exports.main = async (event, context) => {
return cloud.database().collection("meihao").get({
success:function(res){
return res
},
fail:function(err){
return err
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 调用云函数获取云端数据
// 获取云端数据(合美好)
getMeihaoData:function(){
let that = this // 防止异步处理造成this指向改变
wx.cloud.callFunction({
name:"get_meihaodata",
success:function(res){
console.log("小程序获取数据",res)
that.setData({meihaoList:res.result.data})
console.log(that.data.meihaoList)
},
fail:function(res){
console.log("小程序获取美好数据失败",res)
}
})
}

注意适当地修改云数据库的权限

云存储

图片上传:

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
uploadImage:function(){
let that = this
console.log("文件上传")
// 图片选择
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success (res) {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFilePaths
console.log(tempFilePaths)
let timestamp = (new Date()).valueOf() // 时间戳
wx.cloud.uploadFile({
cloudPath: timestamp+'.png', // 上传至云端的路径
filePath: tempFilePaths[0], // 小程序临时文件路径
success: res => {
// 返回文件 ID
console.log(res.fileID)
console.log("文件上传成功")
that.setData({imageFileId:res.fileID})
// 上传成功后会获得文件唯一标识符,即文件 ID,后续操作(如回显)都基于文件 ID 而不是 URL
},
fail: console.error
})
}
})
}

视频上传:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uploadVideo:function(){
let that = this
// 选择视频
wx.chooseVideo({
sourceType: ['album','camera'],
maxDuration: 60, //最长时长 单位 s
camera: 'back',
success(res) {
console.log("选择视频成功",res.tempFilePath)
wx.cloud.uploadFile({
cloudPath: 'video.mp4', // 上传至云端的路径
filePath: res.tempFilePath, // 小程序临时文件路径
success: res => {
// 返回文件 ID
console.log(res.fileID)
console.log("文件视频成功")
that.setData({imageFileId:res.fileID})
// 上传成功后会获得文件唯一标识符,即文件 ID,后续操作(如回显)都基于文件 ID 而不是 URL
},
fail: console.error
})
}
})
}

带有云后台的小程序进行多人协作

  1. 将所有开发者在微信小程序后台里面设置权限
  2. 在微信开发工具导入项目的时候使用同一个appid(wx9e2d10f6dc0c617f)
  3. 这样子之后,新的项目人员需要在cloudfunction文件夹右键配置云环境,过个几分钟应该就可以编译预览了
  4. 使用git作为版本管理工具,同步多人的代码。需添加github账号成为共同开发者

注意:上传多张图片到云后台需要加同步锁Promise

穷举获取云函数的ip

有时我们希望能够获取到服务器的公网IP,比如用于IP地址的白名单,或者想根据IP查询到服务器所在的地址,ipify就是一个免费好用的依赖,通过它我们也可以获取到云函数所在服务器的公网IP。

建立云函数getip, 然后输入以下代码。同时在package.json的”dependencies”里新增 "ipify":"latest"。

1
2
3
4
5
6
7
8
const cloud = require('wx-server-sdk')
const ipify = require('ipify');
cloud.init({
env: "自己的云环境id",
})
exports.main = async (event, context) => {
return await ipify({ useIPv6: false })
}
0%