微信小程序学习笔记

前言:

7月23号了,感觉读大学以来从来没有放开的耍这么久。更新的文章都是28天之前了。是时候回归正常的生活了。

小程序的运行机制

  • 小程序启动可以分为两种情况,一种是冷启动,一种是热启动

冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动

热启动:如果用户已经打开过某个小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,知识从后台状态进入前台状态

  • 小程序启动后,给用户展示的界面就是前台,此时小程序处于前台

  • 当用户回复微信消息的时候,小程序并没有被关闭,而是进入了后台状态,当用户再次打开的时候就又重新进入前台状态

  • 挂起:小程序进入后台 状态一段时间后(5秒),微信停止小程序JS线程执行,小程序进入挂起状态,当开发者使用了音乐播放或者地理位置等能力时小程序可以在后台持续运行,不会进入到挂起状态

  • 销毁:如果用户很久没有使用小程序,或者系统资源紧张,小程序会被销毁,即完全终止运行。当小程序进入后台并被挂起后如果很长时间(目前是30分钟)都未再次进入前台,小程序会被销毁当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收

小程序的更新机制

在访问小程序时,微信会将小程序代码包缓存到本地
开发者在发布了新的小程序版本后,微信客户端会检查本地缓存的小程序有没有新版本,,并进行小程序代码包的更新。
小程序的更新机制有两种:启动时更新和启动时异步更新

定期检查发现版本更新

微信运行时,会定期检查最近使用的小程序是否有更新。如果有更新,下次小程序启动时会同步进行更新,更新到最新版本后再打开小程序,尽可能保证用户能够尽快使用小程序的最新版本。
用户长时间未使用小程序
用户长时间未使用小程序时,为保障小程序版本的实时性,会强制同步检查版本更新,更新到最新版本后再打开小程序。

若用户处于弱网环境、下载最新版本失败等情况下,仍会启动本地的较低版本。

启动时异步更新

即使启动前未发现更新,小程序每次冷启动时,都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本的代码包。但当次启动仍会使用客户端本地的旧版本代码,即新版本的小程序需要等下一次冷启动才会使用。
开发者手动触发更新
在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用 wx.getUpdateManager API 进行处理。在有新版本时提示用户重启小程序更新新版本。

小程序 wx.getUpdateManager API使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
onLaunch(){
const updateManager= wx.getUpdateManager()

updateManager.onUpdateReady(function(){
wx.showModal({
title: '更新提示',
content: '新版本已经准备好了,是否重启应用',
complete: (res) => {
if (res.cancel) {

}

if (res.confirm) {
updateManager.applyUpdate()
}
}
})
})
}

小程序生命周期介绍

应用生命周期

小程序的生命周期是指小程序从启动到销毁的整个过程
一个完整的小程序的生命周期由应用生命周期页面生命周期组件生命周期 三部分组成

小程序生命周期伴随者一些函数,这些函数由小程序框架本身提供,被称为生命周期函数,生命周期函会按照顺序依次自动触发调用帮助程序员在特定的时机执行特定的操作,辅助程序员完成一些比较复杂的逻辑

应用生命周期伴随者一些函数,我们称为应用生命周期函数 ,应用生命周期函数需要在app.js 文件的APP() 方法中定义
APP()方法必须在app.js中进行调用,主要用来注册小程序

应用生命周期函数由 onLanunch , onShow, onHide 三个函数组成

这里在执行了onHide后,如果长时间未使用,被销毁后。onHide 跳转到小程序的启动(冷启动)

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
App({

/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
// 当进行小程序冷启动时,才会触发onLanunch钩子函数
console.log('onLanunch 小程序初始化完成时')
},

/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {
console.log('onshow 小程序初始化完成时')
},

/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {
console.log('onHide 小程序销毁')
},

/**
* 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
*/
onError: function (msg) {

}
})

页面生命周期

页面生命周期函数就是指小程序页面从加载-运行-销毁的整个过程

页面生命周期函数需要在Page()方法进行定义

  • onLoad:首次进入页面加载时触发,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
  • onShow:加载完成后、后台切到前台或重新进入页面时触发(页面跳转的时候) -> onHide(页面隐藏)
  • onReady:页面首次渲染完成时触发
  • onHide:从前台切到后台或进入其他页面触发
  • onUnload:页面卸载时触发(页面销毁)
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
Page({

/**
* 页面的初始数据
*/
data: {

},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// 只执行一次
console.log('onLoad 页面创建时执行')
},

/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
// 只执行一次
console.log('onReady 页面已经准备完成 ')
},

/**
* 生命周期函数--监听页面显示(后台切换为前台时会触发)
*/
onShow: function () {
console.log('onShow 页面在前台展示的时候执行')
},

/**
* 生命周期函数--监听页面隐藏(在当前小程序进入后台时会触发)
*/
onHide: function () {
console.log('onHide 页面被隐藏时触发')
},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log('onUnload 页面在销毁的时候触发')
},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {

},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {

},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {

}
})


小程序API

  • 小程序提供的API几乎都挂载在wx对象下,wx对象实际上就是小程序的宿主环境微信提供的全局对象

小程序API分类

  • 异步API:通常都接受一个objec类型的参数
  • 同步API:约定以Sync结尾
  • 时间监听API:约定以on开头

异步API支持 callback & Promise 两种调用方式:

  1. 当接口参数Object 对象中不包含 succsess/fail/complete 时将默认返回Promise
  2. 部分接口如 request , uploadFile 本身就有返回值,因此不支持Promise风格的调用方式,他们的promisify需要开发者自行封装

网络请求

这里如果要发起网络请求需要在域名列表中配置对应的域名,不然会报以下的错误

配置教程: 😎👉点击链接

也可以直接关闭校验

网络请求示例代码:

1
2

<button type="warn" bind:tap="getData">网络请求</button>
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
Page({
data:{
list:[]
},
//获取数据
getData(){
//如果需要发起网络请求,需要使用wx.request API
wx.request({
//接口地址
url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
//请求方式
method:'GET',
//请求参数
data:{},
//请求头
header:{},
//API 调用成功后执行的回调函数
success:(res)=>{
// console.log(res)
if(res.data.code==200){
this.setData({
list:res.data.data
})
}
},
//API 调用失败后执行的回调函数
fail:(err)=>{
console.log(err)
},
//不管调用成功还是失败以后,执行回调
complete:()=>{

}
})

}
})

界面交互-Loading

小程序提供了一些用于界面交互的API ,例如:loading提示框,消息提示框,模态对话框等API loading提示框常配合网络请求来使用,用于增加用户体验,对应的API有两个:

  1. wx.showLoading () 显示提示框

  2. wx.hideLoading() 关闭loading提示框

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
Page({
data:{
list:[]
},
//获取数据
getData(){

// 显示loading提示框
//提示的内容不会自动换行,如果提示的内容较多,因为在同一行展示
//多出来的一行会被隐藏
wx.showLoading({
title: '数据正在加载中...',
//是否展示透明蒙层,防止触摸穿透
mask:true
})

//如果需要发起网络请求,需要使用wx.request API
wx.request({
//接口地址
url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
//请求方式
method:'GET',
//请求参数
data:{},
//请求头
header:{},
//API 调用成功后执行的回调函数
success:(res)=>{
// console.log(res)
if(res.data.code==200){
this.setData({
list:res.data.data
})
}
},
//API 调用失败后执行的回调函数
fail:(err)=>{
console.log(err)
},
//不管调用成功还是失败以后,执行回调
complete:()=>{
//成功还是失败都要关闭Loading框
wx.hideLoading()
},

})

}

})

界面交互-模态对话框-消息提示框

wx.showModal():模态对话框,常用于询问用户是否执行一些操作。

例如:询问用户是否退出登录、是否删除该商品等。

1
<button type="primary" bind:tap="delHandler"> 删除商品</button>
1
2
3
4
5
6
7
//删除商品
delHandler(){
wx.showModal({
content: '提示',
title: '是否删除该商品?',
})
}

wx.showToast():消息提示框,根据用户的某些操作来告知操作的结果。

例如:退出成功给用户提示,提示删除成功等。

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
// 删除商品
async delHandler(){

// showModal 显示模态对话框
const { confirm } = await wx.showModal({
title: '提示',
content: '是否删除该商品?'
})
// console.log(res)
if(confirm){
// showToast消息提示框
wx.showToast({
title: '删除成功',
// 不显示图标
icon:"none",
// 消息提示框两秒后自动关闭
duration:2000
})
}else{
wx.showToast({
title: '取消删除',
icon:"error",
// 消息提示框两秒后自动关闭
duration:2000
})
}
}

本地存储

对象类型的数据,可以直接进行存储获取,无需使用 JSON.stringify()、JSON.parse()转换。

1
2
3
4
<button size="mini" plain type="warn" bind:tap="setStorage">存储</button>
<button size="mini" plain type="primary" bind:tap="getStorage">获取</button>
<button size="mini" plain type="warn" bind:tap="removeStorage">删除</button>
<button size="mini" plain type="primary" bind:tap="clearStorage">清空</button>
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
Page({
//将数据存储到本地
setStorage(){
//第一个参数:本地存储中指定的key
//第二个参数:需要存储的数据
wx.setStorageSync('num',1)
//在小程序中,如果存储的时对象类型数据,不需要使用JSON.stringify和JSON.parse进行转换
//直接进行存储和获取即可
wx.setStorageSync('obj',{name:'tom',age:12})
},
//获取本地存储的数据
getStorage(){
const num= wx.getStorageSync('num')
const obj= wx.getStorageSync('obj')

console.log(num)
console.log(obj)
},
//删除本地存储的数据
removeStorage(){
wx.removeStorageSync('num')
wx.removeStorageSync('obj')
},
//清空本地存储的全部数据
clearStorage(){
wx.clearStorageSync()
},
})

路由与通信

在小程序中实现页面的跳转,有两种方式:

  1. 声明式导航:navigator组件
  2. 编程式导航:使用小程序提供的API

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
Page({
navigateTo() {
//保留当前页面,跳转到应用中其他页面,不能跳转到tabBar页面
wx.navigateTo({
url: '/pages/index/index?id=1&name=tom',
})

},
redirectTo() {
//关闭当前页面(销毁当前页面)
wx.redirectTo({
url: '/pages/index/index?id=1&name=tom',
})
},
switchTab() {
//跳转到taBar页面,不能跳转到非tabBar页面,路径后面不能带参数
wx.switchTab({

url: '/pages/index/index',
})
},
reLaunch() {
// 关闭所有页面,然后跳转到某个页面
wx.reLaunch({
url: '/pages/index/index?id=1&name=tom',
})
},
navigateBack() {
//关闭当前页面返回上一级页面或者返回多级页面
//默认返回上一页
wx.navigateBack()
}
})

上拉加载

上拉加载是小程序中常见的一种加载方式,当用户滑动页面到底部时,会自动加载更多的内容,以便用户继续浏览小程序中实现上拉加载的方式:

  • 在app.json或者page.json中配置距离页面底部距离:onReachBottomDistance;默认50px。
  • 在页面.js中定义onReachBottom事件监听用户上拉加载。

随机找到或者创建一个空页面,我这里使用之前的页面将其中的内容注释掉,找到.json文件

1
2
3
4
5
{
"usingComponents": {},
"onReachBottomDistance":100
}

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
Page({
data: {
numList: [1, 2, 3]
},

//监听用户上拉加载
onReachBottom() {
// console.log('监听用户上拉加载')

//产品需求,当用户上拉,需要数字进行累加

wx.showLoading({
title: '数据正在加载中',
})
setTimeout(() => {
//当用户上拉加载时,需要对数字进行累加,每次累加3给数字
//获取数组最后一项
const lastNum = this.data.numList[this.data.numList.length - 1]
//定义需要追加的元素
const newArr = [lastNum + 1, lastNum + 2, lastNum + 3]

this.setData({
numList: [...this.data.numList, ...newArr]
})
wx.hideLoading()
}, 1500);
}
})

下拉加载

下拉刷新是小程序中常见的一种刷新方式,当用户下拉页面时,页面会自动刷新,以便用户获取最新的内容。

小程序中实现上拉加载更多的方式:

  • 在 app.json 或者 page.json 中开启允许下拉,同时可以配置 窗口、loading 样式等。

  • 在 页面.js 中定义 onPullDownRefresh 事件监听用户下拉刷新。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 监听用户下拉刷新
onPullDownRefresh(){
// console.log('监听用户下拉刷新')

// 产品需求:
// 当用户上拉加载更多以后,如果用户进行下拉刷新
// 需要将数据进行重置
this.setData({
numList: [1,2,3]
})


}

需要注意一点的是,下拉刷新以后,loading效果有可能不会回弹回去,一次我们可以添加:

1
2
3
4
// 下拉刷新以后,loading效果有可能不会回弹回去
if (this.data.numList.length === 3){
wx.stopPullDownRefresh()
}

自定义组件

小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;
也可以将复杂的页面拆分成多个低耦合的模块,有助于代码的维护

开发中常见的组件有两种:

  1. 公共组件:将页面内的功能模块抽取成自定义组件,以便在不同的页面中重复使用。(建议放在项目根目录的components文件夹中)
  2. 页面组件:将复杂的页面拆分成多个低耦合的模块,有助于代码维护(建议放在对应页面的目录下)

建议:一个组件一个文件夹

创建和注册组件

components中新建文件夹,然后右键文件夹选择新建Component

  • 注册组件

开发中常见的组件主要分为公共组件和页面组件两种,因此注册组件的方式也分为两种:

  • 全局注册:在app.json 文件中配置usingComponents进行注册,注册后可以在任意页面使用
  • 局部注册:在页面的json文件中配置usingComponents进行注册,注册后只能在当前页面使用

在usingCompinents中进行组件注册时,需要提供 自定义组件的组件名 和 自定义组件文件路径

如果是公共组件,建议将其放在小程序的目录下的 components 文件夹中

如果是页面组件,建议将其放在小程序对应页面目录下,当然你也可以放到页面的 components 文件夹中

同时建议:一个组件一个文件夹,文件夹名称和组件名称保持一致

📌 注意事项

  1. 自定义组件的需要在 json 文件中需要配置 component 字段设为 true
  2. 自定义组件通过 Component 构造器进行构建,在构造器中可以指定组件的属性、数据、方法等

创建组件

创建组件的步骤很简单,以公共组件为例,创建的步骤如下:

  1. 在小程序的目录下新建 components 文件夹

  2. components 文件夹上,点击右键,选择新建文件夹 ,然后输入文件夹名称,我们建议文件夹的名称和组件的名称保持一致,这样方便后期对组件进行维护。我们这里新的的组件名称叫做:custom-checkbox

  3. 在新建的组件文件夹上,点击右键,选择新建 Component,然后输入组件的名称,组件的名称建议和文件夹保持一致

  4. 此时就已经创建了一个功能组件

使用自定义组件

开发中常见的组件主要分为 公共组件 和 页面组件 两种,因此注册组件的方式也分为两种:

  1. 全局注册:在 app.json 文件中配置 usingComponents 节点进行引用声明,注册后可在任意组件使用
  2. 局部注册:在页面的 json 文件中配置 usingComponents 节点进行引用声明,只可在当前页面使用

在配置 usingComponents 节点进行引用声明时,需要提供自定义组件的标签名和对应的自定义组件文件路径,语法如下:

1
2
3
4
5
{
"usingComponents": {
"自定义组件的标签名": "自定义组件文件路径"
}
}

这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

1
2
3
4
5
{
"usingComponents": {
"custom-checkbox": "/components/custom-checkbox/custom-checkbox"
}
}
1
2
3
4
5
<!--pages/index/index.wxml-->
<view>
<!-- 将导入的自定义组件当成标签使用 -->
<custom-checkbox/>
</view>

自定义组件-数据和方法

在组件的 .js 中,需要调用 Component 方法创建自定义组件,Component 中有以下两个属性:

data 数据:组件的内部数据

methods 方法:在组件中事件处理程序需要写到 methods 中才可以

自定义组件-属性

属性 Properties 是指组件的对外属性,主要用来接收组件使用者传递给组件内部的数据,和 data 一同用于组件的模板渲染

📌 注意事项:

  1. 设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
  2. 属性类型可以为 String、Number、Boolean、Object、Array ,也可以为 null 表示不限制类型
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
Component({

+ /**
+ * 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据
+ */
+ properties: {
+ // 如果需要接收传递的属性,有两种方式:全写、简写
+ // label: String
+
+ label: {
+ // type 组件使用者传递的数据类型
+ // 数据类型:String、Number、Boolean、Object、Array
+ // 也可以设置为 null,表示不限制类型
+ type: String,
+ value: ''
+ },
+
+ // 文字显示位置
+ position: {
+ type: String,
+ value: 'right'
+ }
+ },

/**
* 组件的初始数据:用来定义当前组件内部所需要使用的数据
*/
data: {
isChecked: false
},

/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {

// 更新复选框的状态
updateChecked () {

this.setData({
isChecked: !this.data.isChecked,
+ // label: '在组件内部也可以修改 properties 中的数据'
})

+ // 在 JS 中可以访问和获取 properties 中的数据
+ // 但是一般情况下,不建议修改,因为会造成数据流的混乱
+ // console.log(this.properties.label)
// console.log(this.data.isChecked)
}

}

})

组件 wxml 的 slot

在使用基础组件时,可以给组件传递子节点传递内容,从而将内容展示到页面中,自定义组件也可以接收子节点内容

只不过在组件模板中需要定义 <slot /> 节点,用于承载组件引用时提供的子节点

默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。

同时需要给 slot 添加 name 来区分不同的 slot,给子节点内容添加 slot 属性来将节点插入到 对应的 slot 中

组件样式以及注意事项

类似于页面,自定义组件拥有自己的 wxss 样式,组件对应 wxss 文件的样式,只对组件wxml内的节点生效。

编写组件样式时,需要注意以下几点:

  1. app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式
    这些选择器会影响到页面和全部组件,通常情况下这是不推荐的做法

  2. 组件和引用组件的页面不能使用 id 选择器(#a)、属性选择器([a]) 和 标签名选择器,请改用 class 选择器

  3. 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用

  4. 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。

  5. 继承样式,如 font 、 color ,会从组件外继承到组件内。

  6. 除继承样式外, 全局中的样式、组件所在页面的的样式对自定义组件无效 (除非更改组件样式隔离选项)

1
2
3
4
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */

落地代码:

➡️ custom02.wxml

1
2
3
<text id="content" class="content son">
<text class="label">给自定义组件设置样式</text>
</text>

➡️ custom02.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
/* components/custom02/custom02.wxss */

/* 第一个注意事项:在自定义的 wxss 文件中,不允许使用标签选择器,ID 选择器,属性选择器 */
/* 请改为 class 选择器 */
/* text {
color: lightseagreen;
} */

/* #content {
color: lightseagreen;
} */

/* [id=content] {
color: lightseagreen;
} */

/* .content {
color: lightseagreen;
} */

/* 第二个注意事项:子选择器,只能用于 view 和 子组件,用于其他组件可能会出现样式失效的问题 */
/* .content > .label {
color: lightseagreen;
} */

/* 第三个注意事项:继承样式,例如:color\font 都会从组件外继承 */

/* 第四个注意事项:全局样式、组件所在页面的样式文件中的样式都对自定义组件无效 */

/* 第五个注意事项:官方不推荐做法 */
/* 不建议在 全局样式文件 以及 父级页面之间使用标签选择器设置样式 */
/* 如果是在全局样式文件中设置样式,会影响项目中全部的相同组件 */
/* 如果是再页面样式文件中设置样式,会影响当前页面所有的相同组件 */

/* 第六个注意事项: */
/* 组件和组件使用者,如果使用了后代选择器,可能会出现一些非预期情况 */
/* 如果出现,请避免 */

➡️ cate.wxml

1
2
3
4
5
6
7
<view class="custom parent">
<view>
<custom02 />

<view class="son test">我是父级页面中的结构</view>
</view>
</view>

➡️ cate.wxss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* pages/cate/cate.wxss */

/* .custom {
color: lightseagreen;
font-size: 50rpx;
} */

/* .label {
color: lightseagreen;
} */

/* text {
color: lightseagreen;
} */

.parent .son.test {
color: lightsalmon;
}

➡️ app.wxss

1
2
3
4
5
6
7
8
/* .label {
color: lightseagreen;
} */

/* text {
color: lightseagreen;
} */

组件样式隔离

默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。除非以下两种情况:

  1. app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式,这些选择器会影响到页面和全部组件。通常情况下这是不推荐的做法。

  2. 指定特殊的样式隔离选项 styleIsolation

    1
    2
    3
    4
    5
    Component({
    options: {
    styleIsolation: 'isolated'
    }
    })

styleIsolation 选项它支持以下取值:

  • isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
  • apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
  • shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-sharedshared 的自定义组件。

落地代码:

➡️ custom03.wxml

1
2
3
<!--components/custom03/custom03.wxml-->

<text class="label">演示组件样式隔离</text>

➡️ custom03.wxss

1
2
3
4
5
6
/* components/custom03/custom03.wxss */

.test {
color: lightseagreen;
font-size: 50rpx;
}

➡️ custom03.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// components/custom03/custom03.js
Component({

options: {

// styleIsolation:配置组件样式隔离

// isolated:开启样式隔离,默认值
// 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响

// apply-shared:表示组件使用者、页面的 wxss 样式能够影响到自定义组件
// 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式
// styleIsolation: "apply-shared"

// shared:表示组件使用者、页面的 wxss 样式能够影响到自定义组件
// 自定义组件的样式会影响组件使用者、页面的 wxss 样式
// 和其他使用了 apply-share 以及 share 属性的自定义组件
styleIsolation: 'shared'

}

})

➡️ cate.wxml

1
<custom03 />

➡️ cate.wxss

1
2
3
.label {
color: lightsalmon;
}

拓展-小程序修改checkbox样式

知识点:

技巧:在官方文档,找到官方提供的案例,审查元素,就能看到对应的类名

📌 注意事项

  1. .custom-checkbox .wx-checkbox-input {}:复选框没有选中时默认的样式
  2. .custom-checkbox .wx-checkbox-input-checked {}: 复选框选中时默认的样式
  3. .custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before {}:复选框选中时 √ 样式

这几个类名,在全局样式文件、页面样式文件都可以对修改复选框样式,

但是在自定义组件内部使用的时候,需要添加 styleIsolation: 'shared' 属性

落地代码:

➡️ components/custom-checkbox/custom-checkbox.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
/* 复选框组件是公共组件 */
/* 以后需要再多个页面或者需要再多个项目中进行使用 */
/* 所以呢,需要先给复选框组件准备、设置一些默认样式 */
/* 如果在其他页面或者其他项目中使用的时候,发现样式不符合产品需求 */
/* 可以进行修改、对默认的样式进行修改 */

/* 1. 需要给复选框设置默认样式 */
/* 需要先找到小程序给复选框提供的类名,通过小程序给提供的类名修改才可以 */
/* 需要先打开小程序开发文档,找到复选框文档,审查元素,进行查找 */

/* 在自定义组件中,不能直接修改复选框样式 */
/* 如果需要进行修改,需要设置 styleIsolation 才可以 */
/* shared:修改其他页面的样式、组件使用者的样式、以及其他使用了 share 以及 apply-share 的组件 */
/* 这时候,不是想要的结果 */
/* 需求是:只想影响当前组件,可以添加命名空间 */

/* 复选框没有选中时默认的样式 */
.custom-checkbox .wx-checkbox-input {
width: 24rpx !important;
height: 24rpx !important;
border-radius: 50% !important;
border: 1px solid #fda007 !important;
margin-top: -6rpx;
}

/* 复选框选中时默认的样式 */
.custom-checkbox .wx-checkbox-input-checked {
background-color: #fda007 !important;
}

/* 复选框选中时 √ 样式 */
.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before {
font-size: 22rpx;
color: #fff;
}

/* 2. 组件使用者也能够修改默认的样式 */

➡️ components/custom-checkbox/custom-checkbox.js

1
2
3
4
5
6
7
Component({

options: {
styleIsolation: 'shared'
}

})

➡️ index.wxss

1
2
3
4
5
6
7
8
9
/* 组件使用者修改复选框的样式 */
.custom .custom-checkbox .wx-checkbox-input {
border: 1px solid lightseagreen !important;
}

.custom .custom-checkbox .wx-checkbox-input-checked {
background-color: lightseagreen !important;
}

数据监听器

知识点:

数据监听器可以用于监听和响应任何属性和数据字段的变化,有时,需要在一些数据字段被 setData 设置时,需要执行一些操作。那么就可以使用 observers 数据监听器来实现。语法如下:

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
Component({
data: {
num: 10,
count: 1,
obj: { name: 'Tom', age: 10 },
arr: [1, 2, 3]
},
observers: {
// key 是需要检测数据
// value 是一个函数,函数接收一个形参作为参数,是最新的值
num: function(newNum) {
console.log(newNum)
},

// 数据监听器支持监听属性或内部数据的变化,可以同时监听多个
'num, count': function (newNum, newCount) {
console.log(newNum, newCount)
}

// 监听器可以监听子数据字段
'obj.age': function(newAge) {
console.log(newAge)
},

// 如果需要监听所有子数据字段的变化,可以使用通配符 **
'obj.**': function(newAge) {
console.log(newAge)
},

'arr[0]': function (val) {}
}
})

组件间通信与事件

父往子传值

知识点:

父组件如果需要向子组件传递指定属性的数据,在 WXML 中需要使用数据绑定的方式

与普通的 WXML 模板类似,使用数据绑定,这样就可以向子组件的属性传递动态数据。

父组件如果需要向子组件传递数据,只需要两个步骤:

1.在父组件 WXML 中使用 数据绑定 的方式向子组件传递动态数据

2.子组件内部使用 properties 接收父组件传递的数据即可

知识点代码:

1
2
3
4
<!-- 引用组件的页面模板 -->
<view>
<costom prop-a="{{ name }}" prop-b="{{ age }}" />
</view>

在组件内部,需要在 Component 构造器中通过 properties 接收传递的数据,接收方式有两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Component({
/**
* 组件的属性列表 props
*/
properties: {
propA: {
type: String, // 传递的数据类型
value: '' // 默认值
},
propB: Number // 简化的定义方式
},

// coding...
})

在子组件中也可以通过 this.setData()properties 中的数据进行修改,但是一般不建议修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// components/custom01/custom01.js
Component({

/**
* 组件的方法列表
*/
methods: {
// 修改列表中的数据
updateProp () {
this.setData({
propB: this.properties.propB + 1
})
}
}
})

复选框组件案例:

➡️ index.js

1
2
3
4
5
6
7
8
9
Page({

data: {
isChecked: true
},

// coding...

})

➡️ index.wxml

1
2
3
4
5
6
<custom-checkbox
label="我已阅读并同意 用户协议 和 隐私协议"
position="right"
+ checked="{{ isChecked }}">
我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

➡️ components/custom-checkbox/custom-checkbox.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
62
63
Component({

options: {
styleIsolation: 'shared'
},

properties: {

// coding...

// 复选框组件公共组件
// 需要再多个页面、在多个项目中进行使用
// 在使用的时候,有的地方希望默认是选中的效果,有的地方希望默认是没有被选中的效果
// 怎么处理 ?
// 首先让复选框默认还是没有被选中的效果
// 如果希望复选框默认被选中,这时候传递属性(checked=true)到复选框组件
+ checked: {
+ type: Boolean,
+ value: false
+ }
},

/**
* 组件的初始数据:用来定义当前组件内部所需要使用的数据
*/
data: {
isChecked: false
},

+ observers: {
+ // 如果需要将 properties 中的数据赋值给 data
+ // 可以使用 observers 进行处理
+ checked: function (newChecked) {
+ // console.log(newChecked)
+ this.setData({
+ isChecked: newChecked
+ })
+ }
+ },

/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {

// 更新复选框的状态
updateChecked () {

this.setData({
+ isChecked: !this.data.isChecked,
+ // checked: !this.properties.checked
// label: '在组件内部也可以修改 properties 中的数据'
})

// 在 JS 中可以访问和获取 properties 中的数据
// 但是一般情况下,不建议修改,因为会造成数据流的混乱
// console.log(this.properties.label)
// console.log(this.data.isChecked)
}

}

})

➡️ components/custom-checkbox/custom-checkbox.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
<view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
+ <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />

<view class="content">
<!-- lable 和 子节点内容都进行了展示 -->
<!-- 要么展示 lable 要么展示 子节点内容 -->
<!-- 如果用户传递了 lable 属性,就展示 lable -->
<!-- 如果用户没有传递 lable 属性,就展示 子节点内容 -->
<text wx:if="{{ label !== '' }}">{{ label }}</text>

<slot wx:else />
</view>
</view>
</view>

子往父传值

子组件如果需要向父组件传递数据,可以通过小程序提供的事件系统实现传递传递,可以传递任意数据。

事件系统是组件间通信的主要方式之一,自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件,流程如下:

  1. 自定义组件触发事件时,需要使用 triggerEvent 方法发射一个自定义的事件
  2. 自定义组件标签上通过 bind 方法监听发射的事件

触发事件:

1
2
<!-- 在自定义组件中 -->
<button type="primary" plain bindtap="sendData">传递数据</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// components/custom05/custom05.js
Component({

// 组件的初始数据
data: {
num: 666
},

// 组件的方法列表
methods: {

// 将数据传递给父组件
sendData () {

// 如果需要将数据传递给父组件
// 需要使用 triggerEvent 发射自定义事件
// 第二个参数,是携带的参数
this.triggerEvent('myevent', this.data.num)

}

}
})

监听事件:

1
2
3
<view>{{ num }}</view>
<!-- 需要在自定义组件标签上通过 bind 方法绑定自定义事件,同时绑定事件处理函数 -->
<custom05 bind:myevent="getData" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Page({

data: {
num: ''
},

getData (event) {
// 可以通过事件对象.detail 获取子组件传递给父组件的数据
// console.log(event)
this.setData({
num: event.detail
})
}

})

复选框组件案例:

➡️ components/custom-checkbox/custom-checkbox.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
Component({

/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {

// 更新复选框的状态
updateChecked () {

this.setData({
isChecked: !this.data.isChecked,
// label: '在组件内部也可以修改 properties 中的数据'
})

// 在 JS 中可以访问和获取 properties 中的数据
// 但是一般情况下,不建议修改,因为会造成数据流的混乱
// console.log(this.properties.label)
// console.log(this.data.isChecked)

+ // 目前复选框组件的状态是存储在复选框组件内部的、存储在自定义组件内部的
+ // 但是,在以后实际开发中,组件使用者、父组件有时候也需要获取到复选框内部的状态
+ // 怎么办 ?
+ // 这时候,自定义组件内部就需要发射一个自定义事件,
+ // 如果组件使用者、父组件需要使用数据,绑定自定义事件进行获取即可
+ this.triggerEvent('changechecked', this.data.isChecked)
}

}

})

➡️ index.html

1
2
3
4
5
6
7
8
9
<custom-checkbox
label="我已阅读并同意 用户协议 和 隐私协议"
position="right"
checked="{{ isChecked }}"
class="getchild"
+ bind:changechecked="getData"
>
我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

➡️ index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Page({

data: {
isChecked: true
},

getData (event) {
console.log(event.detail)

if (event.detail) {
console.log('提交')
} else {
console.log('请同意协议!')
}
}

})

组件生命周期

组件的生命周期:指的是组件自身的一些钩子函数,这些函数在特定的时间节点时被自动触发

组件的生命周期函数需要在 lifetimes 字段内进行声明

最重要的生命周期是 created attached detached 包含一个组件生命周期流程的最主要时间点

定义段 描述
created 在组件实例刚刚被创建时执行,注意此时不能调用 setData (还没有对模板解析)
attached 在组件实例进入页面节点树时执行 (模板已经解析完毕,并且挂载到页面上)
ready 在组件布局完成后执行
moved 在组件实例被移动到节点树另一个位置时执行
detached 在组件实例被从页面节点树移除时执行 (组件被销毁了)
  1. 【组件实例刚刚被创建好时】, created 生命周期被触发。此时,组件数据 this.data 就是在 Component 构造器中定义的数据 data此时还不能调用 setData 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。

  2. 【在组件完全初始化完毕】、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。

  3. 【在组件离开页面节点树后】, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Component({

lifetimes: {
created: function () {
// 在组件实例刚刚被创建时执行,注意此时不能调用 setData
// 一般用来为组件添加一些自定义属性字段。
},
attached: function() {
// attached 在组件完全初始化完毕、进入页面节点树后执行
// 模板已经解析完毕,并且挂载到页面上
// 一般都是在这里写对应的交互
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},

// coding...
}

// coding...
})

组件所在页面的生命周期

组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联

主要用于组件内部监听父组件的展示、隐藏状态,从而方便组件内部执行一些业务逻辑的处理

组件所在页面的生命周期有 4 个: show、 hide、 resize、 routeDone,需要在 pageLifetimes 字段内进行声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// components/custom06/custom06.js
Component({

// coding...

// 组件所在页面的生命周期
pageLifetimes: {

// 监听组件所在的页面展示(后台切前台)状态
show () {
console.log('组件所在的页面被展示')
},

// 监听组件所在的页面隐藏(前台切后台、点击 tabBar)状态
hide () {
console.log('组件所在的页面被隐藏')
}

}

})

小程序生命周期总结

小程序冷启动,钩子函数执行的顺序

保留当前页面(navigate) 以及 关闭当前页面(redirect)

切后台 以及 切前台(热启动)

拓展:使用 Component 构造页面

Component 方法用于创建自定义组件

小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法进行创建,从而实现复杂的页面逻辑开发

📌 注意事项:

  1. 要求对应 json 文件中包含 usingComponents 定义段

  2. 页面使用 Component 构造器创建,需要定义与普通组件一样的字段与实例方法

  3. 页面 Page 中的一些生命周期方法(如 onLoad() 等以“on”开头的方法),在 Component 中要写在 methods 属性中才能生效

  4. 组件的属性 Properties 可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数

落地代码:

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
Component({

// 为什么需要使用 Component 方法进行构造页面
// Component 方法功能比 Page 方法强大很多
// 如果使用 Component 方法构造页面,可以实现更加复杂的页面逻辑开发

// 小程序页面也可以使用 Component 方法进行构造
// 注意事项:
// 1. 要求 .json 文件中必须包含 usingComponents 字段
// 2. 里面的配置项需要和 Component 中的配置项保持一致
// 3. 页面中 Page 方法有一些钩子函数、事件监听方法,这些钩子函数、事件监听方法必须方法 methods 对象中
// 4. 组件的属性 properties 也可以接受页面的参数,在 onLoad 钩子函数中可以通过 this.data 进行获取

properties: {
id: String,
title: String
},

data: {
name: 'tom'
},

// onLoad () {
// console.log('页面加载 - 1')
// },

methods: {

// 更新 name
updateName() {
this.setData({
name: 'jerry'
})
},

onLoad (options) {
// console.log('页面加载 - 2')
// console.log(options)
console.log(this.data.id)
console.log(this.data.title)
console.log(this.properties.id)
},

}

})

拓展:behaviors

如果需要注册一个 behavior,需要借助 Behavior() 方法,接受一个 Object 类型的参数

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
// my-behavior.js

module.exports = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: 'my-behavior-data'
},
created: function () {
console.log('[my-behavior] created')
},
attached: function () {
console.log('[my-behavior] attached')
},
ready: function () {
console.log('[my-behavior] ready')
},

methods: {
myBehaviorMethod: function () {
console.log('[my-behavior] log by myBehaviorMehtod')
},
}
})

使用 behavior:

1
2
3
4
5
6
7
8
// my-component.js
const myBehavior = require('my-behavior')

Component({
behaviors: [myBehavior]

// coding...
})

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:

  1. 如果有同名的属性或方法,采用 “就近原则”,组件会覆盖 behavior 中的同名属性或方法

  2. 如果有同名的数据字段且都是对象类型,会进行对象合并,其余情况会 采用 “就近原则” 进行数据覆盖

  3. 生命周期函数和 observers 不会相互覆盖,会是在对应触发时机被逐个调用,也就是都会被执行

详细的规则:同名字段的覆盖和组合规则

拓展:外部样式类

默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响,组件使用者如果想修改组件的样式,需要就解除样式隔离,但是解除样式隔离以后,在极端情况下,会产生样式冲突、CSS 嵌套太深等问题,从而给我们的开发带来一定的麻烦。

外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。

如果需要使用外部样式类修改组件的样式,在 Component 中需要用 externalClasses 定义若干个外部样式类。

外部样式类的使用步骤:

1.在 Component 中用 externalClasses 定义段定义若干个外部样式类

2.自定义组件标签通过 属性绑定 的方式提供一个样式类,属性是 externalClasses 定义的元素,属性值是传递的类名

3.将接受到的样式类用于自定义组件内部

📌注意事项:

​ 在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的

​ 因此需要添加 !important 以保证外部样式类的优先级

落地代码:

➡️ custom09.js

1
2
3
4
5
6
// components/custom09/custom09.js
Component({

// 组件接受的外部样式类
externalClasses: ['extend-class']
})

➡️ custom09.wxml

1
2
3
4
5
<!-- 在同一个节点上,如果存在外部样式类 和 普通的样式类 -->
<!-- 两个类的优先级是未定义的 -->
<!-- 建议:在使用外部样式类的时,样式需要通过 !important 添加权重 -->
<view class="extend-class box">通过外部样式类修改组件的样式</view>

➡️ custom09.wxss

1
2
3
.box {
color: lightseagreen;
}

➡️ profile.wxml

1
2
3
<!-- 属性是在 externalClasses 里面定义的元素 -->
<!-- 属性值必须是一个类名 -->
<custom09 extend-class="my-class" />

➡️ profile.wxss

1
2
3
4
5
/* pages/index/index.wxss */

.my-class {
color: lightsalmon !important;
}

npm 支持

构建 npm

目前小程序已经支持使用 npm 安装第三方包,但是这些 npm 包在小程序中不能够直接使用,必须得使用小程序开发者工具进行构建后才可以使用。

为什么得使用小程序开发者工具需要构建呢❓

因为 node_modules 目录下的包,不会参与小程序项目的编译、上传和打包,因此。在小程序项目中要想使用 npm 包,必须走一遍 构建 npm 的过程。

在构建成功以后,默认会在小程序项目根目录,也就是 node_modules 同级目录下生成 miniprogram_npm目录,里面存放这构建打包后的 npm 包,也就是小程序运行过程中真正使用的包

微信开发者工具如何构建❓

我们以使用 Vant Weapp 小程序 UI 组件库为例,来说明小程序如何安装和构建 npm,构建 npm 的步骤如下:

  1. 初始化 package.json
  2. 通过 npm 安装项目依赖
  3. 通过微信开发者工具构建 npm

📌 注意事项

  1. 小程序运行在微信内部,因为运行环境的特殊性,这就导致 并不是所有的包都能够在小程序使用

  2. 我们在小程序中提到的包指专为小程序定制的 npm 包,简称小程序 npm 包,在使用包前需要先确定该包是否支持小程序

  3. 开发者如果需要发布小程序包,需要参考官方规范:https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html#发布-npm-包

构建的详细步骤:

  1. 初始化 package.json这一步至关重要,要不然后续的步骤都很难进行下去

    1
    npm init -y
  2. 通过 npm 安装 @vant/weapp

    1
    npm i @vant/weapp -S --production

  3. 构建 npm

  4. 修改 app.json

    到这一步 npm 的构建已经完成了,但是 Vant 组件库,会和基础组件的样式冲突,因此我们需要继续往下配置

    将 app.json 中的 "style": "v2" 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。

  5. 在页面中使用 vant 提供的小程序组件,这里以 Button 按钮组件为例

    • app.jsonindex.json中引入组件
    • app.json 中注册的组件为全局注册,可以在任意组件中进行使用
    • index.json 中注册组件为组件组件,只能在当前组件中进行使用
    • 按照组件提供的使用方式,在页面中使用即可
    1
    2
    3
    "usingComponents": {
    "van-button": "@vant/weapp/button/index"
    }
    1
    2
    3
    4
    5
    <van-button type="default">默认按钮</van-button>
    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
  6. 页面预览效果

自定义构建 npm

在实际的开发中,随着项目的功能越来越多、项目越来越复杂,文件目录也变的很繁琐,为了方便进行项目的开发,开发人员通常会对目录结构进行调整优化,例如:将小程序源码放到 miniprogram 目录下。

但是在调整目录以后,我们按照上一小节 Vant Weapp 的构建流程进行构建,发现没有构建成功,并且弹出构建失败的弹框

[错误提示翻译意思是] :没有找到可以构建的 npm 包

[解决方式]:

  1. 请确认需要参与构建的 npm 都在 miniprogramRoot 目录内
  2. 配置 project.config.jsonpackNpmManuallypackNpmRelationList 进行构建

产生这个错误的原因是因为小程序的构建方式有两种:

  1. 默认构建 npm
  2. 自定义构建 npm

默认构建 npm

默认情况下,不使用任何模版,miniprogramRoot 是小程序项目根目录,在 miniprogramRoot 内正确配置了 package.json 并执行 npm install 之后,在项目的根目录下就有 node_modules 文件夹,然后对 node_modules 中的 npm 进行构建,其构建 npm 的结果是,为 package.json 对应的 node_modules 构建一份 miniprogram_npm,并放置在对应 package.json 所在目录的子目录中

自定义构建 npm

默认的构建 npm 方式不一样,自定义构建 npm 的方式为了更好的优化目录结构,更好的管理项目中的代码。

需要开发者在 project.config.json 中指定 node_modules 的位置 和 目标 miniprogram_npm 的位置

project.config.json中详细的配置流程和步骤如下:

  1. 新增 miniprogramRoot 字段,指定调整后了的小程序开发目录
  2. 新增 setting.packNpmManually设置为 true,开启指定node_modules 的位置以及构建成功后文件的位置
  3. 新增 setting.packNpmRelationList 项,指定 packageJsonPathminiprogramNpmDistDir 的位置
    • packageJsonPath 表示 node_modules 源对应的 package.json
    • miniprogramNpmDistDir 表示 node_modules 的构建结果目标位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
// 指定调整后了的小程序开发目录
"miniprogramRoot": "miniprogram/",
"setting": {
// 开启自定义 node_modules 和 miniprogram_npm 位置的构建 npm 方式
"packNpmManually": true,
// 指定 packageJsonPath 和 miniprogramNpmDistDir 的位置
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./miniprogram"
}
]
}
}

落地代码:

  1. 将小程序核心源码放到 miniprogram 目录下

  2. project.config.json中进行配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "compileType": "miniprogram",

    + "miniprogramRoot": "miniprogram/",
    + "setting": {
    + "packNpmManually": true,
    + "packNpmRelationList": [
    + {
    + "packageJsonPath": "./package.json",
    + "miniprogramNpmDistDir": "./miniprogram"
    + }
    + ]
    + }

    // coding... 其他配置项
    }

Vant 组件的使用方式

Vant Weapp 是有赞前端团队开源的小程序 UI 组件库,基于微信小程序的自定义组件开发,可用来快速搭建小程序项目。

在使用 Vant 提供的组件时,只需要两个步骤:

1.将组件在 app.json 中进行全部注册 或者 index.json 中进行局部注册

2.在引入组件后,可以在 wxml 中直接使用组件

在前面我们以 image 组件为例,讲解 Vant 组件库的基本使用方式

首先还是需要将先将组件进行引入,这里我们进行全局引入

1
2
3
4
5
// app.json

"usingComponents": {
"van-image": "@vant/weapp/image/index"
}

引入组件后,可以在 wxml 中直接使用组件

1
2
3
4
5
6
<van-image width="100" height="100" src="https://img.yzcdn.cn/vant/cat.jpeg" />

<!-- 坑: -->
<!-- 在使用 van-image 图片组件时,如果需要渲染本地的图片,不能使用 ../ -->
<!-- 需要相对于小程序源码的目录来查找图片才可以 -->
<!-- <van-image width="100" height="100" src="../../assets/Jerry.png" /> -->

如果我们想给 van-field 添加一些属性,这时候我们需要查看 API 手册

1
2
<van-image width="100" height="100" round src="/assets/Jerry.png"/>

如果我们想给 van-field 添加一些事件,这时候我们需要查看 事件 手册

1
2
3
4
5
6
7
8
<van-image
width="100"
height="100"
round
src="/assets/Jerry.png"
bind:click="imageHandler"
/>

1
2
3
4
5
6
7
Page({

imageHandler () {
console.log('点击图片时触发点击事件,执行该事件处理函数~~~~')
}

}

如果我们想给 van-field 添加一些插槽,这时候我们需要查看 slot 手册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<van-image
width="100"
height="100"
round
+ use-loading-slot
+ use-error-slot
src="/assets/Jerry.png"
bind:click="imageHandler"
>
+ <!-- slot: loading -->
+ <van-loading slot="loading" type="spinner" size="20" vertical />
+
+ <!-- slot: error -->
+ <text slot="error">加载失败</text>
</van-image>

如果我们想给 van-field 添加一些外部样式类,这时候我们需要查看 外部样式类 手册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<van-image
width="100"
height="100"
round
use-loading-slot
use-error-slot
+ custom-class="custom-class"
src="/assets/Jerry.png"
bind:click="imageHandler"
>
<!-- slot: loading -->
<van-loading slot="loading" type="spinner" size="20" vertical />

<!-- slot: error -->
<text slot="error">加载失败</text>
</van-image>

1
2
3
4
5
/* pages/index/index.wxss */

.custom-class {
border: 10rpx solid lightseagreen !important;
}

Vant 组件的样式覆盖

Vant Weapp 基于微信小程序的机制,为开发者提供了以下 3 种修改组件样式的方法

  1. 解除样式隔离:在页面中使用 Vant Weapp 组件时,可直接在页面的样式文件中覆盖样式
  2. 使用外部样式类:需要注意普通样式类和外部样式类的优先级是未定义的,需要添加 !important 保证外部样式类的优先级
  3. 使用 CSS 变量:在页面或全局对多个组件的样式做批量修改以进行主题样式的定制

第 1 种:解除样式隔离

Vant Weapp 的所有组件都开启了addGlobalClass: true以接受外部样式的影响,因此我们可以通过审核元素的方式获取当前元素的类名,然后复制到组件的 .wxss 中进行修改

第 2 种:使用外部样式类

Vant Weapp 开放了大量的外部样式类供开发者使用,具体的样式类名称可查阅对应组件的 “外部样式类” 部分。

需要注意的是普通样式类和外部样式类的优先级是未定义的,因此使用时请添加!important以保证外部样式类的优先级。

第 3 种:使用 CSS 变量

Vant Weapp 可以通过 CSS 变量的方式多个组件的样式做批量修改。CSS 的变量基础用法如下:

  1. 声明一个自定义属性,属性名需要以两个减号(--)开始,属性值则可以是任何有效的 CSS 值
1
2
3
4
5
6
/* app.wxss */

/* 声明全局的变量,可在项目中任意组件中使用 */
page {
--main-bg-color: lightcoral;
}
  1. 使用一个局部变量时用 var() 函数包裹以表示一个合法的属性值
1
2
3
4
5
6
7
8
9
10
11
/* 声明局部的变量 */
/* 只有被当前类名容器包裹住的元素,使用该变量才生效 */
.container {
--main-bg-color: lightseagreen;
}

.custom-class {
/* 使用一个局部变量时用 var() 函数一个合法的属性值 */
background-color: var(--main-bg-color) !important;
color: #fff !important;
}
  1. 页面中使用该变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<view class="container">
<van-button
type="default"
custom-class="custom-class"
>
默认按钮
</van-button>
</view>

<van-button
type="default"
custom-class="custom-class"
>
默认按钮
</van-button>

![](http://8.131.91.46:6677/mina/base/CSS 变量修改演示.jpg)


也可以在按钮身上添加类名:

1
2
<!-- 使用 CSS 变量:如果需要再多个页面或者一个组件中 需要批量修改组件、定制主题 -->
<van-button type="primary" class="my-button">主要按钮</van-button>
1
2
3
4
5
6
7
8
9
10
.my-button {
--color: rgb(221, 152, 24);
}

.van-button--primary {
font-size: 28rpx !important;
background-color: var(--color) !important;
border: 1px solid var(--color) !important;
}

分包加载

什么是分包加载

什么是分包加载

小程序的代码通常是由许多页面、组件以及资源等组成,随着小程序功能的增加,代码量也会逐渐增加,体积过大就会导致用户打开速度变慢,影响用户的使用体验。

分包加载是一种小程序优化技术。将小程序不同功能的代码,分别打包成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载,在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。每个分包可以包含多个页面、组件、样式和逻辑等。当小程序需要使用某个分包时,才会加载该分包中的代码。

主包:包含默认启动页面 / TabBar 页面 以及 所有分包都需用到公共资源的包

分包:根据开发者的配置进行划分出来的子包

小程序分包后如何加载

在小程序启动时,默认会下载主包并启动主包内页面,在用户访问分包内某个页面时,微信客户端才会把对应分包下载下来,下载完成后再进行展示。

目前小程序分包大小有以下限制:**

  1. 整个小程序所有分包大小不超过 20MB
  2. 单个分包/主包大小不能超过 2MB

📌 注意事项

​ 整个小程序所有分包大小可能会随时调整,截止到目前整个小程序所有分包大小不超过 20M

###2. 分包的基本使用

知识点:

在进行分包加载之前,需要对小程序的业务逻辑进行分析,将代码划分成多个模块。每个模块应该有一个明确的功能,并与其他模块之间有明确的依赖关系

需要按照功能拆分分包,并且每个分包都需要与其他包有依赖关系(可以通过 a 分包跳转到 b 分包)

开发者在小程序的配置文件 app.json 中,通过 subPackages 或者 subpackages字段声明项目分包结构。

每个分包需要指定 root 字段、name 字段和 pages 字段

  1. root 字段指定了分包的根目录,该目录下的所有文件都会被打包成一个独立的包
  2. name 字段为分包的名称,用于在代码中引用该分包
  3. pages 字段指定了该分包中包含的页面,可以使用通配符 * 匹配多个页面

落地代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{

"subPackages": [
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": [
"pages/list/list",
"pages/detail/detail"
]
},
{
"root": "modules/marketModule",
"name": "marketModule",
"pages": [
"pages/market/market"
]
}
]

}

打包和引用原则(注意事项)

打包原则:

  1. tabBar 页面必须在主包内

  2. 最外层的 pages 字段,属于主包的包含的页面

  3. 按 subpackages 配置路径进行打包,配置路径外的目录将被打包到主包中

  4. 分包之间不能相互嵌套,subpackage 的根目录不能是另外一个 subpackage 内的子目录

引用原则:

  1. 主包不可以引用分包的资源,但分包可以使用主包的公共资源

  2. 分包与分包之间资源无法相互引用, 分包异步化时不受此条限制

独立分包的配置

什么是独立分包

独立分包:独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行

从独立分包中页面进入小程序时,不需要下载主包,但是当用户进入普通分包或主包内页面时,主包才会被下载 !

开发者可以将功能相对独立的页面配置到独立分包中,因为独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度

如果是独立分包,不需要下载主包,直接就能够访问,独立分包是自己独立运行的

而如果是其他分包,需要先下载主包,通过路径访问,才能加载对应路径的分包

📌 注意事项:

  1. 独立分包中不能依赖主包和其他分包中的资源

  2. 主包中的 app.wxss 对独立分包无效

  3. App 只能在主包内定义,独立分包中不能定义 App,会造成无法预期的行为

如何配置独立分包:

开发者在app.json中找到需要配置为独立分包的subpackages字段

在该字段配置项中定义independent字段声明对应分包为独立分包。

落地代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{

"subPackages": [
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": [
"pages/list/list",
"pages/detail/detail"
]
},
{
"root": "modules/marketModule",
"name": "marketModule",
"pages": [
"pages/market/market"
],
+ "independent": true
}
]
}

分包预下载

知识点:

分包预下载是指访问小程序某个页面时,预先下载分包中的代码和资源,以提高用户的使用体验。当用户需要访问分包中的页面时,已经预先下载的代码和资源可以直接使用,通过分包预下载加快了页面的加载速度和显示速度。

小程序的分包预下载需要在 app.json 中通过 preloadRule 字段设置预下载规则。preloadRule 是一个对象,对象的 key 表示访问哪个路径时进行预加载,value 是进入此页面的预下载配置,具有两个配置项:

字段 类型 必填 默认值 说明
packages StringArray 预下载的分包名称,进入页面后预下载分包的 rootname
__APP__ 表示主包。
network String wifi 在指定网络下预下载,
可选值为: all: 不限网络 wifi: 仅wifi下预下载
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
{
"subPackages": [
{
"root": "modules/goodModule",
"name": "goodModule",
"pages": [
"pages/list/list",
"pages/detail/detail"
]
},
{
"root": "modules/marketModule",
"name": "marketModule",
"pages": [
"pages/market/market"
],
"independent": true
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["modules/goodModule"]
},
"modules/marketModule/pages/market/market": {
"network": "all",
"packages": ["__APP__"]
}
}
}

落地代码

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
{
"pages": [
"pages/index/index",
"pages/user/user"
],
"subPackages": [
{
"root": "pages/music",
"name": "music",
"pages": [
"player/player",
"lyric/lyric"
]
},
{
"root": "pages/settings",
"name": "settings",
"pages": [
"theme/theme",
"language/language"
]
}
],
"preloadRule": {
"pages/music/player/player": {
"packages": ["settings"],
"network": "wifi"
}
}
}

小程序开发能力

获取用户头像

当小程序需要让用户完善个人资料时,我们可以通过微信提供的头像、昵称填写能力快速完善。如图:

想使用微信提供的头像填写能力,需要两步:

  1. button 组件 open-type 的值设置为 chooseAvatar
  2. 当用户选择需要使用的头像之后,可以通过 bindchooseavatar 事件回调获取到头像信息的临时路径。
1
2
3
4
5
<!-- 给 button 添加 open-type 属性,值为 chooseAvatar -->
<!-- 绑定 bindchooseavatar 事件获取回调信息 -->
<button open-type="chooseAvatar" bindchooseavatar="getAvatar">
按钮
</button>

落地代码:

1
2
3
4
5
<view class="avatar">
<button open-type="chooseAvatar" bindchooseavatar="getAvatar">
<image src="{{ avatarUrl }}" mode="" />
</button>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Page({
/**
* 页面的初始数据
*/
data: {
avatarUrl: '/assets/tom.png'
},

// 获取用户头像信息
getAvatar(e) {
// 获取选中的头像
const { avatarUrl } = e.detail

// 将获取到的头像赋值给 data 中变量同步给页面结构
this.setData({
avatarUrl
})
}

// coding...
}

获取用户昵称

知识点:

当小程序需要让用户完善个人资料时,我们可以通过微信提供的头像、昵称填写能力快速完善。如图:

想使用微信提供的昵称填写能力,需要三步:

  1. 通过 form 组件中包裹住 input 以及 form-type 为 submit 的 button 组件

  2. 需要将 input 组件 type 的值设置为 nickname,当用户输入框输入时,键盘上方会展示微信昵称

  3. 给 form 绑定 submit 事件,在事件处理函数中通过事件对象获取用户昵称

落地代码:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 需要使用 form 组件包裹住 input 以及 button 组件 -->
<form bindsubmit="onSubmit">

<!-- input 输入框组件的 type 属性设置为 nickname,用户点击输入框,键盘上方才会显示微信昵称 -->
<!-- 如果添加了 name 属性,form 组件就会自动收集带有 name 属性的表单元素的值 -->
<input type="nickname" name="nickname" placeholder="请输入昵称" />

<!-- 如果将 form-type="submit" ,就将按钮变为提交按钮 -->
<!-- 在点击提交按钮的时候,会触发 表单的 bindsubmit 提交事件 -->
<button type="primary" plain form-type="submit">点击获取昵称</button>
</form>

1
2
3
4
5
6
7
8
9
10
11
Page({


// 获取微信昵称
onSubmit (event) {
// console.log(event.detail.value)
const { nickname } = event.detail.value
console.log(nickname)
}

}

转发功能

转发功能,主要帮助用户更流畅地与好友分享内容和服务

想实现转发功能,有两种方式:

  1. 页面 js 文件 必须声明 onShareAppMessage 事件监听函数,并自定义转发内容。只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮

  2. 通过给 button 组件设置属性 open-type=“share“ ,在用户点击按钮后触发 Page.onShareAppMessage 事件监听函数

官方文档 onShareAppMessage:

落地代码:

1
2
3
4
<!--pages/cate/cate.wxml-->

<button open-type="share">转发</button>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Page({

// 监听页面按钮的转发 以及 右上角的转发按钮
onShareAppMessage (obj) {
// console.log(obj)

// 自定义转发内容
return {
// 转发标题
title: '这是一个非常神奇的页面~~~',
// 转发路径
path: '/pages/cate/cate',
// 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径
imageUrl: '../../assets/Jerry.png'
}

}

})

分享到朋友圈

小程序页面默认不能被分享到朋友圈,开发者需主动设置“分享到朋友圈”才可以,实现分享到朋友圈需满足两个条件:

  1. 页面 必须 设置允许“发送给朋友”,页面 js 文件声明 onShareAppMessage 事件监听函数

  2. 页面 必须 需设置允许“分享到朋友圈”,页面 js 文件声明 onShareTimeline 事件监听函数

官方文档 onShareTimeline:

落地代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Page({


// 监听右上角 分享到朋友圈 按钮
onShareTimeline () {

// 自定义分享内容。
return {
// 自定义标题,即朋友圈列表页上显示的标题
title: '帮我砍一刀~~~',
// 自定义页面路径中携带的参数,如 path?a=1&b=2 的 【 “?” 后面部分 】
query: 'id=1',
// 自定义图片路径,可以是本地文件或者网络图片
imageUrl: '../../assets/Jerry.png'
}

}

})

手机号验证组件

手机验证组件,用于帮助开发者向用户发起手机号申请,必须经过用户同意后,才能获得由平台验证后的手机号,进而为用户提供相应服务

  1. 手机号快速验证组件:平台会对号码进行验证,但不保证是实时验证

    1
    <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
  2. 手机号实时验证组件:在每次请求时,平台均会对用户选择的手机号进行实时验证

    1
    2
    3
    4
    <button
    open-type="getRealtimePhoneNumber"
    bindgetrealtimephonenumber="getrealtimephonenumber"
    />

📌注意事项:

1.目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)

2.两种验证组件需要付费使用,每个小程序账号将有 1000 次体验额度

其他要求和注意事项,参考文档:

手机号快速验证组件

手机号实时验证组件

落地代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--pages/cart/cart.wxml-->

<button
type="primary"
plain
open-type="getPhoneNumber"
bindgetphonenumber="getphonenumber"
>快速验证组件</button>


<button
type="warn"
plain
open-type="getRealtimePhoneNumber"
bindgetrealtimephonenumber="getrealtimephonenumber"
>实时验证组件</button>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Page({

// 手机号快速验证
getphonenumber (event) {
// 通过事件对象,可以看到,在 event.detail 中可以获取到 code
// code 动态令牌,可以使用 code 换取用户的手机号
// 需要将 code 发送给后端,后端在接收到 code 以后
// 也需要调用 API,换取用户的真正手机号
// 在换取成功以后 ,会将手机号返回给前端
console.log(event)
},

// 手机号实时验证
getrealtimephonenumber (event) {
console.log(event)
}

})

客服能力

小程序为开发者提供了客服能力,同时为客服人员提供移动端、网页端客服工作台便于及时处理消息

使用方式:

  1. 需要将 button 组件 open-type 的值设置为 contact,当用户点击后就会进入客服会话

    1
    <button type="warn" plain open-type="contact">联系客服</button>
  2. 在微信公众后台,绑定后的客服账号,可以登陆 网页端客服 或 移动端小程序 客服接收、发送客服消息

上线发布

假设我们目前已经使用微信开发者工具,按照小程序的开发规范完成了小程序的全部的全部开发工作,并且完成了本地测试,

这时候我们需要开发对小程序进行发布,小程序上线的流程如下:

开发版本:点击开发者工具上传后的版本,开发版本只保留每人最新的一份上传的代码,是供开发者和团队测试和调试的版本

体验版本:小程序开发者可以将开发版本转换为体验版本,由测试人员以及产品经理进行测试与体验,确认没问题可提交审核

审核版本:小程序开发者可以将开发版本转换为审核版本,由微信的审核团队进行审核,审核周期为1~7天,审核通过可提交发布

线上版本:通过微信小程序平台审核,并由开发者提交发布的正式版本,线上版本是用户可以正常使用的小程序版本

小程序开发成员在开发者工具中点击 上传 按钮,在弹出的界面中选择更新类型、版本号、项目备注,就能够将小程序代码上传至微信公众号后台审核。

在登录到微信公众后台以后,点击左侧的 管理版本管理,就能查看小程序的四个个版本