微信小程序开发学习篇
概述
相关信息
- 笔记制作时间:2022-9-25
- 参考视频:黑马视频
- 参考文档:微信小程序官方开发文档
文章目录
小程序基础
1、数据绑定与 Mustache(插值表达式) 语法
index.wxml:
<!--index.wxml-->
<view class="container">
<!-- 双花括号包裹一个变量,这种语法称作“Musa -->
{{info}}
</view>
<!-- 动态绑定属性值 -->
<image src="{{flower_path}}"></image>
<!-- Mustache 语法支持三元表达式 -->
<view>{{isMain ? '当前是首页' : '当前不是首页'}}</view>
index.js:
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
'info':'hello world112',
'flower_path':'/images/meinv.jpg',
isMain: false
},
})
2、事件绑定
什么是事件?
小程序中的常用事件
事件对象的属性列表
2.1 绑定 bindtap 事件
<!--index.wxml-->
<view>
<!-- 定义按钮组件,设置 tap(手指点击) 事件的处理函数为 btnBindHandler -->
<button type="warn" bindtap="btnBindHandler">按钮</button>
</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
'info':'hello world112',
'flower_path':'/images/meinv.jpg',
isMain: false
},
/**
* 被绑定的事件处理函数
* @param {*} event 事件对象
*/
btnBindHandler(event){
console.log('tap 事件被触发。。。');
console.log(event);
}
})
调试界面:
2.2 事件传参与数据同步
事件处理函数中为 data 对象中的数据赋值
示例:
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// data.count 原始值
count: 0
},
/**
* 被绑定的事件处理函数,与 data 对象同级
* @param {*} event 事件对象
*/
btnBindHandler(event) {
// 修改 data.count 的值,通过 this.data.count 获取旧值,相当于 this 指向了传递给 Page 函数的整个对象参数
console.log('原始值=',this.data.count);
// 修改
// 写法一:this.data.xxx = 新值
// this.data.count++
// 写法二:this.setData()
this.setData({
count: 100
})
console.log('新值=',this.data.count);
}
})
2.3 事件传参
示例:
<!--index.wxml-->
<view>
<!-- 如果此处 data-info 的属性值不使用双花括号包裹,则其数字 2 将被认为是字符串,而非数字 -->
<button type="warn" bindtap="btnBindHandler" data-info="{{2}}">按钮</button>
</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// data.count 原始值
count: 0
},
/**
* 被绑定的事件处理函数,与 data 对象同级
* @param {*} event 事件对象
*/
btnBindHandler(event) {
console.log('按钮被点击...');
// 事件传参含义:渲染层 wxml 的 button 组件被点击后,其属性 data-info="{{2}}" 被传递到逻辑层 .js 中的对应的处理函数 btnBindHandler 的 event.target.dataset 对象中,此过程称为“事件传参”
// event.target.dataset 是一个对象,存储了事件传递的参数
console.log(event.target.dataset);
console.log(event.target.dataset.info);
}
})
2.4 bindinput 事件
示例:
<!--index.wxml-->
<view>
<!-- input 组件用于用户输入文本,bindinput 属性即绑定了 input 事件,属性值为对应的事件处理函数 -->
<input bindinput="inputHandler" ></input>
</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
/**
* 被绑定的事件处理函数,与 data 对象同级
* @param {*} event 事件对象
*/
inputHandler(event) {
console.log('input 事件被调用。。。');
// event.detail.value 可获取 input 组件输入后的新的文本内容
console.log(event.detail.value);
}
})
2.5 实现文本框和 data 之间的数据同步
示例:
<!--index.wxml-->
<view>
<!-- input 组件用于用户输入文本,bindinput 属性即绑定了 input 事件,属性值为对应的事件处理函数 -->
<!-- input 的 value 属性指定在页面中的显示值,这里直接与 js 的 data 进行绑定 -->
<input value="{{msg}}" bindinput="inputHandler" ></input>
</view>
/**index.wxss**/
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
color: #aaa;
}
input {
border: 1px solid rgb(42, 110, 211);
padding: 3px;
border-radius: 4px;
}
.userinfo-avatar {
overflow: hidden;
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.usermotto {
margin-top: 200px;
}
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// 定义一个属性,使其被 input 组件进行解析渲染
msg: 'hello,'
},
/**
* 被绑定的事件处理函数,与 data 对象同级
* @param {*} event 事件对象
*/
inputHandler(event) {
console.log('input 事件被调用。。。');
// 获取输入框的值,并赋值给 data.msg
this.setData({
msg: event.detail.value
})
}
})
3、条件渲染(wx:if)
3.1 简单使用 wx:if
示例:
<!--index.wxml-->
<view>
<!-- 条件渲染 -->
<!-- 以下三个组件分别展示三个文本内容,但是它们使用 wx:if 语句来决定该组件是否被渲染,它们是一个逻辑的整体,相当于一个 if-elif-else 语句 -->
<view wx:if="{{type === 1}}">男</view>
<view wx:elif="{{type === 2}}">女</view>
<view wx:else>保密</view>
</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// 定义一个属性,使其决定 wxml 中应该渲染哪一个组件
type: 3
},
})
3.2 block 组件结合 wx:if
示例:
<!--index.wxml-->
<!-- 被渲染 -->
<block wx:if="{{true}}">
<view>a</view>
<view>b</view>
</block>
<!-- 不被渲染,且页面中不存在该组件,即该组件不被创建,而非被隐藏 -->
<block wx:if="{{false}}">
<view>c</view>
<view>d</view>
</block>
3.3 hidden 属性控制组件隐藏
示例:
<!--index.wxml-->
<!-- hidden 属性控制该组件是否被隐藏 ,true 则隐藏,false 则显示-->
<view hidden="{{flag}}">hello world</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// 定义一个属性,使其决定 wxml 中是否隐藏一个组件
flag: true
},
})
3.4 wx:if 与 hidden 属性的区别
4、列表渲染
4.1 wx:for
示例:
<!--index.wxml-->
<!-- 列表渲染,使用 wx:for 属性 -->
<view wx:for="{{arr1}}">
索引是:{{index}}, 值是:{{item}}
</view>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
// 定义一个数组,在 wxml 中使用 wx:for 来进行遍历渲染
arr1: ['apple','huawei','vivo']
},
})
模拟器界面:
4.2 手动指定 元素索引名和当前项变量名
4.3 wx:key
5、wxss 设置组件样式
- WXSS (WeiXin Style Sheets)是一套样式语言,用于美化WXML的组件样式,类似于网页开发中的CSS。
- WXSS 与 CSS 的关系
5.1 rpx 单位
-
rpx ( responsive pixel)是微信小程序独有的,用来解决屏适配的尺寸单位。
-
实现原理
-
rpx 与 px 单位的换算
因为使用 ipone6 设备进行单位换算时,两者总是两倍的关系,因此比较容易计算一些,建议使用它。
5.2 @import 进行样式导入
- 使用WXSS提供的@import语法,可以导入外联的样式表。
- 语法格式
示例:
/* common.wxss */
.username {
color: red;
}
/**index.wxss**/
/* 导入公共样式文件 */
@import '/common/common.wxss';
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
color: #aaa;
}
input {
border: 1px solid rgb(42, 110, 211);
padding: 3px;
border-radius: 4px;
}
.userinfo-avatar {
overflow: hidden;
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.usermotto {
margin-top: 200px;
}
<!--index.wxml-->
<view class="username">marry</view>
5.3 设置全局样式 与 局部样式
- 定义在 app.wxss 中的样式为全局样式,作用于每一个页面。
- 在页面的 .wxss 文件中定义的样式为局部样式,只作用于当前页面。
注意:
- 当局部样式和全局样式冲突时,根据就近原则,局部样式会覆盖全局样式。
- 当局部样式的权重大于或等于全局样式的权重时,才会覆盖全局的样式。
6、全局配置
6.1 常用的全局配置及小程序的窗口组成
6.2 window 对象的配置
6.3 设置导航栏标题
- 设置步骤:app.json -> window -> navigationBarTitleText
6.4 设置导航栏背景色
- 设置步骤:app.json -> window -> navigationBarBackgroundColor
6.5 设置导航栏标题文本颜色
- 设置步骤:app.json -> window -> navigationBarTextStyle
- 目前小程序的导航栏标题只支持 black、white 两种颜色。
6.6 全局开启下拉刷新页面
- 概念:下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
- 设置步骤:app.json -> window -> 把 enablePullDownRefresh 的值设置为 true。
- 注意:在 app.json 中启用下拉刷新功能,会作用于每个小程序页面!
- app.json -> window -> backgroundColor 设置下拉刷新时窗口的背景色。
- app.json -> window -> backgroundTextStyle 设置下拉刷新时的窗口的小圆点的颜色,只有 dark 、light 两种颜色。
6.7 设置上拉触底功能
- 概念:上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。
- 设置步骤:app.json -> window -> 为 onReachBottomDistance 设置新的数值(不需要加单位)。
- 注意:默认距离为50px,如果没有特殊需求,建议使用默认值即可。
6.8 设置 tabBar
- 设置步骤:在 app.json 中,添加一个 tabBar 对象,该对象与 pages、window 同级。
示例:
// app.json
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#00ff00",
"navigationBarTitleText": "我的第一个微信小程序",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#4b3c5d"
},
// 需要添加的 tabBar 对象
"tabBar": {
// list 数组中的元素即为每一个 tab 项
"list": [
// tab 项中 pagePath 设置该 tab 被点击后跳转到的页面
// text 设置该 tab 的文本内容
{
"pagePath": "pages/index/index",
"text": "index"
},
{
"pagePath": "pages/list/list",
"text": "list"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
7、页面配置(局部配置)
- 小程序中,每个页面都有自己的 .json 配置文件,用来对当前页面的窗口外观、页面效果等进行配置。
- 页面配置和全局配置的关系:
小程序中,app.json中的 window节点,可以全局配置小程序中每个页面的窗口表现。
如果某些小程序页面想要拥有特殊的窗口表现,此时,“页面级别的 .json 配置文件”就可以实现这种需求。
注意:当页面配置与全局配置冲突时,根据就近原则,最终的效果以页面配置为准。
8、发起网络数据请求
8.1 GET 和 POST 请求
-
小程序的网络请求的限制
-
配置 request 合法域名
- 需求描述:假设在自己的微信小程序中,希望请求https://www.escook.cn/域名下的接口。
- 配置步骤:登录微信小程序管理后台->开发-→开发设置→>服务器域名->修改request合法域名 。
- 发起 get 请求
示例:
1)配置 request 合法域名
2)在项目中编写代码
<!--index.wxml-->
<!-- 测试发起网络数据请求 -->
<button bindtap="sendGetReq">发起GET请求</button>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
/**
* 设置按钮的事件处理函数,用于发起 GET 请求
*/
sendGetReq() {
console.log('按钮被点击 ~~~');
// 调用 wx.request() 发起网络请求
wx.request({ // 接收一个配置对象
url: 'https://www.escook.cn/api/get',
method: 'GET', // 设置请求方式
data: { // 设置发送到服务器的数据
name: '公孙离',
age: 23
},
// 设置请求成功的回调函数
success(res) {
console.log('请求成功。');
console.log(res);
}
})
}
})
- 发起 POST 请求
示例:
<!--index.wxml-->
<!-- 测试发起网络数据请求 -->
<button bindtap="sendPostReq">发起POST请求</button>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
// 发起 POST 请求
sendPostReq() {
console.log('按钮被点击 ~~~');
// 调用 wx.request() 发起网络请求
wx.request({ // 接收一个配置对象
url: 'https://www.escook.cn/api/post',
method: 'POST', // 设置请求方式
data: { // 设置发送到服务器的数据
name: '李白',
age: 23
},
// 设置请求成功的回调函数
success(res) {
console.log('POST请求成功。');
console.log(res);
console.log(res.data);
}
})
}
})
8.2 在页面加载时请求数据
8.3 跳过 request 合法域名校验
8.4 跨域和 Ajax 问题
- 跨域问题只存在于基于浏览器的Web开发中。由于小程序的宿主环境不是浏览器,而是微信客户端。所以小程序中不存在跨域的问题。
- Ajax技术的核心是依赖于浏览器中的 XMLHttpRequest 这个对象,由于小程序的宿主环境是微信客户端,所以小程序中不能叫做“发起 Ajax 请求”,而是叫做“发起网络数据请求”。
总结 1
9、页面导航
- 什么是页面导航
页面导航指的是页面之间的相互跳转。
例如,浏览器中实现页面导航的方式有如下两种:
1)<a>
链接
2) location. href
- 小程序实现页面导航的两种方式
9.1 导航到 tabBar 页面
示例:
<!--index.wxml-->
<!-- navigator 组件进行声明式导航 -->
<!--
1、url 属性指定跳转到某个路径下的哪个页面,且属性值必须以 / 开头;
2、open-type 属性指定导航的方式,若为 switchTab 则表示跳转到一个 tab 页面,且如果需要跳转到 tabBar 页面,则必须声明:open-type="switchTab" -->
<navigator url="/pages/list/list" open-type="switchTab">跳转到 list 页面</navigator>
9.2 跳转到非 tabBar 页面
- 注意:当需要导航到非 tabBar 页面时,甚至可以省略 open-type=“navigate” 。
示例:
<!--index.wxml-->
<!-- 测试发起网络数据请求 -->
<button bindtap="sendPostReq">发起POST请求</button>
<!-- navigator 组件进行声明式导航 -->
<!--
1、url 属性指定跳转到某个路径下的哪个页面,且属性值必须以 / 开头;
2、open-type 属性指定导航的方式,如果需要跳转到非 tabBar 页面,则必须声明:open-type="navigate"
3、当需要导航到非 tabBar 页面时,甚至可以省略 open-type="navigate" -->
<navigator url="/pages/logs/logs" open-type="navigate">跳转到 logs 页面</navigator>
9.3 后退导航
示例:
<!-- 后退导航 -->
<!-- 必须指定 open-type="navigateBack" ,且 delta 属性指定具体返回几个页面 -->
<navigator open-type="navigateBack" delta="1">返回上一页面</navigator>
- 注意:为了简便,如果只是后退到上一页面,则可以省略 delta 属性,因为其默认值就是 1。
9.5 编程式导航
- 导航到 tabBar 页面
示例:
<!--index.wxml-->
<view>首页</view>
<!-- 编程式导航 -->
<!-- 定义一个按钮,监听其点击事件,绑定事件处理函数,事件处理函数中进行编程式导航 -->
<button bindtap="toListPage">导航到 list 页面</button>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
// 事件处理函数,用于导航到 tabBar 页面 list
toListPage(e) {
// 编程式导航
// wx.switchTab() 方法用于导航到 tabBar 页面
wx.switchTab({
// url 直接指定页面路径
url: '/pages/list/list',
})
}
})
-
跳转到非 tabBar 页面
-
后退导航
9.6 导航传参
- 声明式导航传参
示例:
<!--index.wxml-->
<view>首页</view>
<!-- 导航传参 -->
<!--
1、直接在 url 的路径中跟上 ?参数键值对即可,多个键值对用 & 隔开
2、此方式只能用于跳转到非 tabBar 页面
-->
<navigator url="/pages/logs/logs?name=李白&age=23" >跳转到 logs</navigator>
效果:
-
编程式导航传参
-
在 onLoad 函数中接收导航参数
示例:
// logs.js
Page({
data: {
// 一般使用 query 属性接收页面导航过来传递的参数
query: {}
},
onLoad(options) {
console.log(options);
this.setData({
query: options
})
}
})
10、下拉刷新事件
- 下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
- 开启下拉刷新方法
- 在全局或页面的 .json 配置文件中,通过 backgroundColor 和 backgroundTextStyle 来配置下拉刷新窗口的样式,其中:
1)backgroundColor 用来配置下拉刷新窗口的背景颜色,仅支持16进制的颜色值。
2)backgroundTextStyle 用来配置下拉刷新loading 的样式,仅支持dark和 light。
- 监听页面的下拉刷新动作
在页面的 .js 文件中,通过 onPullDownRefresh() 函数即可监听当前页面的下拉刷新事件。
参考代码:
// logs.js
const util = require('../../utils/util.js')
Page({
data: {
},
/**
* 当用户进行下拉刷新时被调用
*/
onPullDownRefresh() {
console.log('用户进行下拉刷新动作 ~~');
// 真机运行时,下拉刷新动作执行后不会自动关闭该 loading 效果,此时我们可以手动执行 wx.stopPullDownRefresh() 即可关闭下拉刷新效果
wx.stopPullDownRefresh({
success: (res) => {console.log('下拉动作完成。');},
})
}
})
11、上拉触底事件
-
上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。
-
在页面的 .js 文件中,通过 onReachBottom() 函数即可监听当前页面的上拉触底事件。示例代码如下:
-
配置上拉触底距离:
上拉触底距离指的是触发上拉触底事件时,滚动条距离页面底部的距离。
可以在全局或页面的 .json 配置文件中,通过 onReachBottomDistance 属性来配置上拉触底的距离。小程序默认的触底距离是 50px,在实际开发中,可以根据自己的需求修改这个默认值。
示例:
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
// 监听上拉触底事件的处理函数,当发生上拉触底事件时被调用
onReachBottom() {
console.log('触发了上拉触底事件 ~~');
}
})
12、自定义编译模式
- 适用情景:比如我们需要编译后直接显示指定的页面,或者设置进入页面的请求参数等等。
- 设置方法:
13、小程序的生命周期
13.1 概念
13.2 应用生命周期函数
示例:
// app.js
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
},
/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {
},
/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {
},
/**
* 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
*/
onError: function (msg) {
// 自定义异常输出消息
console.log('程序运行时发生错误: ', msg);
}
})
13.3 页面的生命周期函数
示例:
// pages/message/message.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 常用于页面加载时,请求初始化数据
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
// 常用于页面加载完成后,修改初始化的数据
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
}
})
14、WXS 脚本
14.1 概念
- WXS ( WeiXin Script)是小程序独有的一套脚本语言,结合 WXML,可以构建出页面的结构。
- wxml 中无法调用在页面的 .js 中定义的函数,但是,wml 中可以调用 wxs 中定义的函数。因此,小程序中 WXS 的典型应用场景就是“过滤器”。
- wxs 与 js 的关系:
14.2 内嵌 wxs 脚本
示例:
<!--index.wxml-->
<view>首页</view>
<!-- wxml 文件中使用 wxs 脚本 -->
<!--
1、调用 wxs 脚本导出的函数,需要使用 Mustache 语法,如:模块名.函数名
2、传递的参数可以是页面 .js 文件的 data 对象属性值,也可以是自定义的基本类型数据,如字符串
-->
<view>{{m1.toUpper(username)}}</view>
<view>{{m1.toUpper('gongsunli')}}</view>
<!-- 定义 wxs 模块 -->
<wxs module="m1">
// 导出一个 toUpper 函数,该函数功能为:将字符串转为大写字母
module.exports.toUpper = function (str) {
return str.toUpperCase()
}
</wxs>
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
username: 'libai'
},
})
效果:
14.3 使用外联的 wxs 脚本
示例:
/** /utils/wxs/tools.wxs */
// 定义需要导出的函数,用于将文本转为小写
function toLower(str) {
return str.toLowerCase()
}
// 导出函数
module.exports = {
toLower: toLower
}
<!--index.wxml-->
<view>首页</view>
<!-- wxml 文件中使用 wxs 脚本 -->
<!--
1、调用 wxs 脚本导出的函数,需要使用 Mustache 语法,如:模块名.函数名
2、传递的参数可以是页面 .js 文件的 data 对象属性值,也可以是自定义的基本类型数据,如字符串
-->
<view>{{m1.toUpper(username)}}</view>
<view>{{m1.toUpper('gongsunli')}}</view>
<view>{{m2.toLower('HELLO,WORLD.')}}</view>
<!-- 定义 wxs 模块 -->
<wxs module="m1">
// 导出一个 toUpper 函数,该函数功能为:将字符串转为大写字母
module.exports.toUpper = function (str) {
return str.toUpperCase()
}
</wxs>
<!-- 导入一个外联的 wxs 脚本 -->
<!-- 需要声明 src 属性,属性值为外联 wxs 脚本文件的相对路径,同时指定 module 属性 -->
<wxs src='../../utils/wxs/tools.wxs' module="m2"></wxs>
14.4 wxs 的特点
-
为了降低 wxs (Weixin Script)的学习成本, wxs 语言在设计时借大量鉴了JavaScript 的语法。但是本质上,wxs 和 JavaScript 是完全不同的两种语言!
-
不能作为组件的事件回调
-
隔离性好
-
性能较好
总结 2
15、基础加强
15,1 自定义组件
1)创建组件
2)引用组件
- 局部引用组件
示例:
index.json
{
"usingComponents": {
"my-test01": "/components/test/test"
}
}
<!--index.wxml-->
<view>首页</view>
<!-- 使用页面 .json 导入的自定义组件 -->
<my-test01></my-test01>
<!-- components/test1/test.wxml -->
<text>components/test1/test.wxml</text>
效果:
- 全局引用组件
示例:
app.json
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#00ff00",
"navigationBarTitleText": "我的第一个微信小程序",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#4b3c5d"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "index"
},
{
"pagePath": "pages/list/list",
"text": "list"
}
]
},
"usingComponents": {
"my-test01": "/components/test/test"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
- 全局引用 VS 局部引用
15.2 自定义组件的样式
- 隔离性
- 修改组件的隔离性
15.3 自定义组件的 data、methods、properties
-
定义自定义组件的 data
-
定义自定义组件的 methods 方法
-
定义自定义组件的属性
示例:
<!-- components/test1/test.wxml -->
<text class="red-color-text">components/test1/test.wxml</text>
<view>
<text>{{count}}</text>
<button bindtap="addCount">+1</button>
</view>
// components/test1/test.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 第一种方式:简化写法,不指定默认值
max: Number,
// 第二种方式:属性值是一个配置对象,可以指定默认值
min: {
type: Number,
value: 10
}
},
/**
* 组件的初始数据
*/
data: {
// 一个计数器变量
count: 0
},
/**
* 组件的方法列表
*/
methods: {
// 让 this.data.count 加 1 的事件处理函数
addCount() {
if (this.properties.max <= this.data.count) return
this.setData({
count: this.data.count + 1
})
},
// 自定义方法
_getMax() {
console.log('当前组件设置的最大值=', this.properties.max);
}
}
})
- data 和 properties 的区别
实际上 data 和 properties 指向的是同一个对象,只不过为了区别它们的作用,使用了两个不同的变量名来表示而已。
15.4 数据监听器
- 监听 data 中的数据变化
即在自定义组件的 .js 文件中,创建于 data、methods 同一级别的 observers 对象,其中定义监听变量名的方法即可。
示例:
<!--components/test2/test2.wxml-->
<text>components/test2/test2.wxml</text>
<!-- 定义 view ,显示 data 中三个变量的值 -->
<view>{{n1}} + {{n2}} = {{sum}}</view>
<!-- 另一种写法 -->
<!-- <view>{{n1}} + {{n2}} = {{n1 + n2}}</view> -->
<!-- 定义按钮,让变量 n1 +1 -->
<button bindtap="addN1">n1+1</button>
<view></view>
<button bindtap="addN2">n2+1</button>
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
// 定义三个变量,初始值为 0
n1: 0,
n2: 0,
sum: 0
},
/**
* 组件的方法列表
*/
methods: {
// 事件处理函数,分别处理 n1+1、n2+1
addN1() {
this.setData({
n1: this.data.n1 + 1
})
},
addN2() {
this.setData({
n2: this.data.n2 + 1
})
}
},
/**
* 定义数据监听器
*/
observers: {
// 监听 data.n1、data.n2 的数据变化
// 只要监听的变量中有一个发生变化,该函数就会被自动调用
// 属性名就是要监听的变量名,多个变量名之间以逗号分隔
// 方法参数对应着监听的变量名,是对应变量名的新值
'n1,n2': function (newN1, newN2) {
// 修改 data.sum 的值
this.setData({
sum: newN1 + newN2
})
}
}
})
- 监听对象属性值的变化
15.5 纯数据字段
-
概念
-
使用规则
15.6 组件的全部生命周期函数
-
概念
-
主要的组件生命周期函数及特点
-
方式一:与 data 同级,创建组件的生命周期函数即可,此方式是旧版本的定义方式,此时已经不再推荐使用
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
/**
* 定义生命周期函数方式一:与 data 同级,创建生命周期函数即可,此方式是旧版本的定义方式,此时已经不再推荐使用
*/
created(){
console.log('created');
},
attached(){
console.log('attached');
},
ready(){
console.log('ready');
},
moved(){
console.log('moved');
},
detached(){
console.log('detached');
},
error(err){
console.log('err');
}
})
- 方式二:与 data 同级,创建 lifetimes 对象,在对象中定义组件的生命周期函数即可,此方式是新版本的定义方式,此时推荐使用
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
/**
* 定义生命周期函数方式二:与 data 同级,创建 lifetimes 对象,在对象中定义生命周期函数即可,此方式是新版本的定义方式,此时推荐使用
*/
lifetimes: {
created() {
console.log('created ~~');
},
attached() {
console.log('attached ~~');
},
ready() {
console.log('ready ~~');
},
moved() {
console.log('moved ~~');
},
detached() {
console.log('detached ~~');
},
error(err) {
console.log('err ~~');
}
}
})
15.7 组件所在页面的生命周期函数
示例:
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
/**
* 定义组件生命周期函数方式二:与 data 同级,创建 lifetimes 对象,在对象中定义生命周期函数即可,此方式是新版本的定义方式,此时推荐使用
*/
lifetimes: {
created() {
console.log('created ~~');
},
attached() {
console.log('attached ~~');
},
ready() {
console.log('ready ~~');
},
moved() {
console.log('moved ~~');
},
detached() {
console.log('detached ~~');
},
error(err) {
console.log('err ~~');
}
},
/**
* 定义组件在页面中时触发的生命周期函数
* 定义方法:将 show、hide、resize 方法定义在 pageLifetimes 对象中即可
*/
pageLifetimes: {
show() {
console.log('show');
},
hide() {
console.log('hide');
},
resize() {
console.log('resize');
}
}
})
15.8 自定义组件的插槽
-
概念
-
单个插槽用法
示例:
自定义组件中定义插槽
<!--components/test2/test2.wxml-->
<text>components/test2/test2.wxml</text>
<view></view>
<!-- 定义一个插槽 slot 组件,用于将此插槽的空间交给使用此组件的使用者进行填充 -->
<slot></slot>
页面中使用组件时,填充插槽内容
<!--index.wxml-->
<view class="red-color-text">首页</view>
<!-- my-test02 组件中定义了一个 slot 插槽,我们可以把填充内容写在 my-test02 标签中 -->
<my-test02>hello world</my-test02>
- 启用多个插槽
默认情况下,微信小程序不支持多个插槽的用法。如果非要使用,需要进行配置。
示例:
定义多个插槽的只定义组件的 .js 文件中定义 options.multipleSlots
=true,从而开启多个插槽的使用
// components/test2/test2.js
Component({
/**
* 对组件进行配置的对象
*/
options: {
// 配置组件可以使用多个插槽
multipleSlots: true
},
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
/**
* 定义组件生命周期函数方式二:与 data 同级,创建 lifetimes 对象,在对象中定义生命周期函数即可,此方式是新版本的定义方式,此时推荐使用
*/
lifetimes: {
created() {
console.log('created ~~');
},
attached() {
console.log('attached ~~');
},
ready() {
console.log('ready ~~');
},
moved() {
console.log('moved ~~');
},
detached() {
console.log('detached ~~');
},
error(err) {
console.log('err ~~');
}
},
/**
* 定义组件在页面中时触发的生命周期函数
* 定义方法:将 show、hide、resize 方法定义在 pageLifetimes 对象中即可
*/
pageLifetimes: {
show() {
console.log('show');
},
hide() {
console.log('hide');
},
resize() {
console.log('resize');
}
}
})
然后在自定义组件中定义多个插槽
<!--components/test2/test2.wxml-->
<text>components/test2/test2.wxml</text>
<view></view>
<!-- 定义多个插槽,每个插槽需要使用 name 属性进行命名 -->
<slot name='before'></slot>
<view>我是插槽内部的组件 D</view>
<slot name='center'></slot>
<slot name='after'></slot>
最后在使用自定义组件的页面中声明指定插槽填充的组件即可
<!--index.wxml-->
<view class="red-color-text">首页</view>
<!-- my-test02 组件中定义了多个 slot 插槽,我们可以把填充内容写在 my-test02 标签中 -->
<my-test02>
<!-- 为了使用定义好 name 名称的插槽,我们需要传递一个组件,组件使用 slot 属性指定需要填充到哪个插槽的插槽名称 -->
<view slot='center'>center</view>
<view slot='after'>after</view>
<view slot='before'>before</view>
</my-test02>
15.9 父子组件的通信方式
-
父子组件之间的三种通信方式
-
父传子:属性绑定
示例:
子组件的 .js 定义相关的 properties 属性
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 为了接收父组件传递的数据,我们需要在 properties 中定义相关的属性名,并声明其数据类型
// 父组件将传输一个字符串给自己,自己用 properties.username 进行接收
username: String
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
})
父组件在使用子组件时进行属性绑定(其实就是定义相关的属性名和属性值)
<!--index.wxml-->
<view class="red-color-text">首页</view>
<view>父组件中 username={{username}}</view>
<view>---------------------</view>
<!-- my-test02 组件即为自定义组件,同时也是当前页面的子组件,我们可以使用属性绑定的方法向子组件传值(只能传输基本数据类型的值) -->
<!-- 进行属性绑定时,我们使用 Mustache 语法赋值给子组件的对应属性名 -->
<my-test02 username="{{username}}"></my-test02>
子组件获取到父组件传递的数据后,进行 UI 渲染
<!--components/test2/test2.wxml-->
<text>components/test2/test2.wxml</text>
<view></view>
<!-- 渲染父组件传递过来的值,此时该值已经被赋予给自己的 properties 对象的属性了,因此可以直接进行访问 -->
<view>父组件传递而来的 username={{username}}</view>
效果:
- 子传父:事件绑定
示例:
父组件 .js
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
count: 1
},
// 父组件定义自定义事件处理函数
syncCount(event) {
console.log('syncCount');
// 事件对象会保存子组件调用 this.triggerEvent() 时传递的第二个参数对象在 e.detail 中
console.log(event);
// 获取到了子组件传递数据,我们就可以动态更新父组件的数据了
this.setData({
count: event.detail.value
})
}
})
父组件 .wxml
<!--index.wxml-->
<view class="red-color-text">首页</view>
<view>父组件中 count={{count}}</view>
<view>---------------------</view>
<!-- 父组件使用子组件时,使用 bind:sync (冒号可省略,冒号后面的是事件名称,可以任意定义)绑定父组件定义好的事件处理函数 -->
<my-test02 count="{{count}}" bind:sync="syncCount"></my-test02>
子组件 .js
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 为了接收父组件传递的数据,我们需要在 properties 中定义相关的属性名,并声明其数据类型
// 父组件将传输一个数值给自己,自己用 properties.count 进行接收
count: Number
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
// 让 properties.count+1 的方法
addCount() {
this.setData({
count: this.properties.count + 1
})
// 子组件中主动触发父组件定义的自定义事件(然后父组件自动调用相关的处理函数),从而将数据同步给父组件
// 调用 this.triggerEvent() 第一个参数传递父组件声明的自定义事件名称,即 bind: 后面的内容。第二个参数可以传递一个配置对象,在父组件被调用的处理函数中,会被保存在事件对象中,即 e.detail,所以这个配置对象我们可以直接将子组件的 properties 的属性值传递过去
this.triggerEvent('sync', { value: this.properties.count })
}
},
})
子组件 .wxml
<!--components/test2/test2.wxml-->
<text>components/test2/test2.wxml</text>
<view></view>
<!-- 渲染父组件传递过来的值,此时该值已经被赋予给自己的 properties 对象的属性了,因此可以直接进行访问 -->
<view>子组件中:父组件传递而来的 count={{count}}</view>
<!-- 使用按钮控制自己的 count +1 -->
<button bindtap="addCount">count+1</button>
效果:当点击一次子组件的 count+1 按钮时,控制台 syncCount(event) 输出的 event 对象
- 父访问子:获取子组件实例,从而直接获得或修改子组件的任意数据和方法,即 父组件调用 this.selectComponent()
示例:
子组件 .js
// components/test2/test2.js
Component({
/**
* 组件的属性列表
*/
properties: {
count: 0
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
},
})
父组件 .wxml
<!--index.wxml-->
<view class="red-color-text">首页</view>
<view>---------------------</view>
<!-- 父组件获取子组件实例 -->
<!-- 使用子组件时,需要指定 id 或 class 属性,便于选中此子组件 -->
<my-test02 class="child"></my-test02>
<!-- 点击按钮,触发获取子组件实例的处理函数 -->
<button bindtap="getChild">获取子组件</button>
父组件 .js
// index.js
// 获取应用实例
const app = getApp()
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
// 获取子组件实例的事件处理函数
getChild() {
// 调用 this.selectComponent() 获取子组件实例
const child = this.selectComponent('.child')
console.log(child);
// 根据获取到的子组件实例对象 child ,我们可以调用该对象的方法或修改该对象的数据
child.setData({
count: 100
})
}
})
15.10 自定义组件的 behaviors
-
概念
-
behaviors 的工作方式
-
创建 behavior
-
导入并使用 behavior
实际上不止是自定义组件 Component,就来页面 Page 也可以导入 behavior ,从而获得共享的数据和方法。
示例:
在 .js 文件中创建并导出一个 Behavior 对象
/** behaviors/my-behavior.js */
// 导出一个 Behavior 实例
// Behavior 实例定义需要共享的数据和方法
module.exports = Behavior({
data: {
username: '公孙离'
},
properties: {
},
methods: {
}
})
自定义组件或页面的 .js 文件中导入对应的 Behavior 对象,并设置 options.behaviors 数组
// index.js
// 获取应用实例
const app = getApp()
// 使用 require 函数获取 Behavior 实例
const behavior = require('../../behaviors/my-behavior')
Page({
// 声明 behaviors 数组,其中存储导入的 Behavior 实例
// 然后该数组中所有的 behavior 实例所具有的数据和方法将被共享给自身
behaviors: [behavior],
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
})
在页面或自定义组件中使用 behavior 共享的数据或方法
<!--index.wxml-->
<view class="red-color-text">首页</view>
<!-- 使用 behavior 中定义的数据 -->
<view>在 behavior 中,用户名={{username}}</view>
效果:
-
Behavior 所有可用的节点
-
同名字段和方法重名的覆盖和组合规则
官方文档解释:
总结 3
16、小程序使用 npm
- 使用 npm 的限制
16.1 安装 Vant Weapp
-
什么是 Vant Weapp
Vant weapp 是有赞前端团队开源的一套小程序 UI 组件库,助力开发者快速搭建小程序应用。它所使用的是 MIT 开源许可协议,对商业使用比较友好。
官方文档地址 -
npm 安装 Vant Weapp
详细步骤:参考官方文档
1)进入微信小程序项目中,观察是否含有 package.json 包管理文件,如果没有该文件,可以执行
npm init -y
2)执行:通过 npm 工具安装该依赖,并指定安装版本为 1.3.3
npm i @vant/weapp@1.3.3 -S --production
3)使用开发者工具构建 npm 包
4)修改 app.json
将 app.json 中的 "style": "v2"
去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。
5)配置 project.config.json
{
...
"setting": {
...
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./miniprogram/"
}
]
}
}
6)使用 Vant Weapp 组件
示例:
app.json 的 usingComponents 对象中导入 Vant Weapp 组件
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#00ff00",
"navigationBarTitleText": "我的第一个微信小程序",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#4b3c5d"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "index"
},
{
"pagePath": "pages/list/list",
"text": "list"
}
]
},
"usingComponents": {
"van-button": "@vant/weapp/button/index"
},
"sitemapLocation": "sitemap.json"
}
在 .wxml 中使用组件
<!--index.wxml-->
<!-- 使用 Vant Weapp 框架提供的 button 按钮,样式由 type 属性决定 -->
<van-button type='info'>按钮</van-button>
- 使用 CSS 变量控制 Vant Weapp 组件样式
定制全局主题样式
Vant Weapp使用CSS变量来实现定制主题。关于CSS变量的基本用法,请参考MDN文档:
公
MDN 文档
所有可用的 CSS 变量参考:官方描述、官方 CSS 变量配置文件
示例:
/**app.wxss**/
/* 配置 Vant Weapp 组件主体样式的颜色变量 */
page {
--button-info-background-color: yellow;
}
17、小程序 API 的 Promise 化
- 概念
什么是 APl Promise 化?
API Promise 化,指的是通过额外的配置,将官方提供的、基于回调函数的异步 API,升级改造为基于 Promise 的异步 APl,从而提高代码的可读性、维护性,避免回调地狱的问题。
-
实现小程序 API 的 Promise 化
-
安装 npm 包
miniprogram-api-promise
执行
npm i --save miniprogram-api-promise@1.0.4
然后重新使用微信开发者工具构建 npm 包(如果发现构建后出现异常情况,可以先将项目中的 miniprogram_npm
目录先删除干净,再重新构建 npm 包)
然后
- 在 app.js 定义 Promise 化代码
// app.js
// 使用 es6 的 import 语法导入 miniprogram-api-promise 的 promisifyAll 函数
import {promisifyAll} from 'miniprogram-api-promise'
// console.log(promisifyAll);
// console.log(wx.p);
// 首先在全局的 wx 对象上绑定一个 p 对象,该对象初始化为 {},然后使用 wxp 变量指向 wx.p 对象
const wxp = wx.p = {}
/**
* 在小程序入口文件中(app.js),只需调用一次 promisifyAl1 ()方法,即可实现异步 API 的 Promise 化
*/
promisifyAll(wx,wxp)
App({
// some codes...
})
- 调用 Promise 化之后的异步 API
示例:
wxml 中定义按钮,该按钮绑定处理函数
<!--index.wxml-->
<view class="red-color-text">首页</view>
<!-- 使用 Vant Weapp 框架提供的 button 按钮,样式由 type 属性决定 -->
<!-- 绑定点击事件的处理函数为 getInfo -->
<van-button type='info' bindtap="getInfo">getInfo 按钮</van-button>
声明一个异步函数,其中调用已经 Promise 化的对象方法
// index.js
// 获取应用实例
const app = getApp()
Page({
data: {
},
// 获取网络数据的方法
// 我们可以在方法名前使用 async 关键字,声明该函数为一个异步函数
async getInfo() {
// 发起网络数据请求
// 本来需要调用 wx.request() 方法来进行请求,但该方法是一个异步 API ,将来代码可维护性、可读性差
// 我们调用已经 Promise 化的 API 函数来完成
// 在 app.js 中,我们已经将所有 Promise 化后的函数绑定到了 wx.p 对象上,因此,我们直接调用它
// 最终,它的返回值就是一个 Promise 对象
// 我们可以在执行该方法代码前使用 await 关键字,然后该方法就可以直接返回 promise[[PromiseResult]] 结果对象了
const res = await wx.p.request({
url: 'https://www.escook.cn/api/get',
method: 'get',
data: {
name: '王昭君',
age: 26
}
})
console.log(res);
// 最终的 res.data 就是我们所需要的结果对象
console.log('res.data=', res.data);
}
})
调试结果:
18、全局数据共享方案和 MobX
-
概念
-
安装 MobX 的相关包
终端执行
npm i --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1
然后构建 npm 即可。
- 创建 Store 实例
示例:
/** store/store.js */
// 使用这个 JS 文件,专门用来创建 Store 实例对象
// 导入 mobx-miniprogram 模块的 observerable 方法,用于获取 Store 实例
// 导入 action 函数,用于生成修改 store 实例数据的修改方法
import { observable, action } from 'mobx-miniprogram'
// 根据传入的配置对象创建 Store 实例,并导出
export const store = observable({
// 定义数据字段
numA: 100,
numB: 33,
// 定义计算属性
// 使用 get 关键字进行表示该属性值可读不可修改
// 方法名 sum 就是计算属性的访问名称
get sum() {
// 返回值就是计算属性的值
return this.numA + this.numB
},
// 定义 actions 方法,专门用于修改 store 实例的数据
updateNumA: action(function (step) {
// console.log(this.numA);
// 让 numA 直接加上传入的参数值
this.numA += step
}),
updateNumB: action(function (step) {
this.numB += step
})
})
- 绑定 Store 实例对象数据到页面 .Page 对象中
示例:
// index.js
// 获取应用实例
const app = getApp()
// 导入 mobx-miniprogram-bindings 模块中的 createStoreBindings 函数,用于绑定 Store 对象数据到 Page 对象中
import { createStoreBindings } from 'mobx-miniprogram-bindings'
// 导入自定义的 Store 对象实例,我们将从该对象中获取共享的数据
import { store } from '../../store/store'
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
/**
* 将获取 Store 实例数据的代码写到 onLoad 生命周期函数中
*/
onLoad(options) {
// createStoreBindings 函数返回一个绑定对象,接收这个对象有助于我们使用它在 onUnLoad 函数中进行清理和关闭工作
// 调用此方法时,我们第一个参数传入当前的 Page 对象作为上下文对象,然后第二个参数是一个包含 Store 对象数据的配置对象,将 store 的数据进行绑定到 Page.data 上
this.storeBindings = createStoreBindings(this, {
// store 属性定义数据源,即需要获取的 Store 实例
store,
// fields 数组指定需要从数据源中获取哪些数据对象
// 数组中直接指定 store 中的数据字段属性名的字符串或计算属性的方法名字符串
fields: ['numA', 'numB', 'sum'],
// actions 数组指定需要从数据源获取哪些可以修改 Store 实例数据的修改方法
// 数组中直接指定该修改方法的方法名字符串
actions: ['updataNumA']
})
console.log(this.data);
},
onUnload() {
// 进行绑定对象的关闭和清理工作
this.storeBindings.destroyStoreBindings()
}
})
- 在页面 Page 中使用 Store 实例的数据
示例:
<!--index.wxml-->
<view class="red-color-text">首页</view>
<view>{{numA}} + {{numB}} = {{sum}}</view>
<!-- 使用一个导入的 button 组件,调用处理函数时,传递 step=1 参数给事件对象 -->
<van-button type='primary' bindtap="btnHandler1" data-step="{{1}}">numA +1</van-button>
<van-button type='warning' bindtap="btnHandler1" data-step="{{-1}}">numA -1</van-button>
// index.js
// 获取应用实例
const app = getApp()
// 导入 mobx-miniprogram-bindings 模块中的 createStoreBindings 函数,用于绑定 Store 对象数据到 Page 对象中
import { createStoreBindings } from 'mobx-miniprogram-bindings'
// 导入自定义的 Store 对象实例,我们将从该对象中获取共享的数据
import { store } from '../../store/store'
Page({
// data 对象设置对应的变量值,然后供 wxml 使用 Mustache 语法去引用
data: {
},
/**
* 将获取 Store 实例数据的代码写到 onLoad 生命周期函数中
*/
onLoad(options) {
// createStoreBindings 函数返回一个绑定对象,接收这个对象有助于我们使用它在 onUnLoad 函数中进行清理和关闭工作
// 调用此方法时,我们第一个参数传入当前的 Page 对象作为上下文对象,然后第二个参数是一个包含 Store 对象数据的配置对象,将 store 的数据进行绑定到 Page.data 上
this.storeBindings = createStoreBindings(this, {
// store 属性定义数据源,即需要获取的 Store 实例
store,
// fields 数组指定需要从数据源中获取哪些数据对象
// 数组中直接指定 store 中的数据字段属性名的字符串或计算属性的方法名字符串
fields: ['numA', 'numB', 'sum'],
// actions 数组指定需要从数据源获取哪些可以修改 Store 实例数据的修改方法
// 数组中直接指定该修改方法的方法名字符串
actions: ['updateNumA']
})
console.log(this.data);
},
onUnload() {
// 进行绑定对象的关闭和清理工作
this.storeBindings.destroyStoreBindings()
},
btnHandler1(e) {
console.log('click.');
console.log(e);
// 使用 e.target.dataset.step 获取点击组件传递过来的参数值
// 使用从 store 对象获取的修改 numA 数据字段的修改方法来修改 store 对象的 numA 数据
// 实际上,当前 Page.data 获取到的是 store 数据的引用,而不是数据对象的复制,因此当 store 中的数据发生变化,当前 Page.data 中的数据也随着变化
this.updateNumA(e.target.dataset.step)
}
})
- 在组件中使用 Store 数据
示例:
// components/test1/test.js
// 按需导入 mobx-miniprogram-bindings.storeBindingsBehavior 对象和自定义的 Store 对象
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Component({
/**
* 将 storeBindingsBehavior 存储到 behaviors 数组节点中
*/
behaviors: [storeBindingsBehavior],
/**
* 声明 storeBindings 对象,将组件对象与 Store 对象进行绑定
*/
storeBindings: {
// 数据源 store
store,
// fields 对象,用于映射组件与 store 的属性
fields: {
// key 是组件中使用的 data 的属性名,value 是 store 中属性名字符串
numA: 'numA',
numb: 'numB',
// 映射 store 的计算属性
sum: 'sum'
},
// actions 对象,用于映射组件与 store 的修改自身数据方法
actions: {
// 映射规则如 fields 中的规则
updateNumA: 'updateNumA'
}
},
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
- 在组件中使用 Store 共享的数据
示例:
<!-- components/test1/test.wxml -->
<text class="red-color-text">components/test1/test.wxml</text>
<view>{{numA}} + {{numb}} = {{sum}}</view>
<!-- 使用一个导入的 button 组件,调用处理函数时,传递 step=1 参数给事件对象 -->
<van-button type='primary' bindtap="btnHandler2" data-step="{{1}}">numB +1</van-button>
<van-button type='warning' bindtap="btnHandler2" data-step="{{-1}}">numB -1</van-button>
// components/test1/test.js
// 按需导入 mobx-miniprogram-bindings.storeBindingsBehavior 对象和自定义的 Store 对象
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Component({
/**
* 将 storeBindingsBehavior 存储到 behaviors 数组节点中
*/
behaviors: [storeBindingsBehavior],
/**
* 声明 storeBindings 对象,将组件对象与 Store 对象进行绑定
*/
storeBindings: {
// 数据源 store
store,
// fields 对象,用于映射组件与 store 的属性
fields: {
// key 是组件中使用的 data 的属性名,value 是 store 中属性名字符串
numA: 'numA',
numb: 'numB',
// 映射 store 的计算属性
sum: 'sum'
},
// actions 对象,用于映射组件与 store 的修改自身数据方法
actions: {
// 映射规则如 fields 中的规则
updateNumB: 'updateNumB'
}
},
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
btnHandler2(e) {
// 获取组件点击时传递的事件对象中的数据
this.updateNumB(e.target.dataset.step)
}
}
})
19、分包
19.1 概念
什么是分包?
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
分包的好处
对小程序进行分包的好处主要有以下两点:
1)可以优化小程序首次启动的下载时间
2)在多团队共同开发时可以更好的解耦协作。
分包前的项目构成
分包后的项目构成
分包的加载规则
分包的体积限制
19.2 配置分包
示例:
app.json 中 与 pages 节点同级创建 subpackages 节点对象,用于定义分包信息。
{
// 主包页面列表
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
// 分包列表,每个对象元素就是一个分包配置对象
"subpackages": [
// 分包配置对象1
{
"root": "pkgA", // 分包名称(即目录名)
"name": "p1", // 可以为当前分包设置别名
// 当前分包所包含的页面列表
"pages": [
// 一个分包页面的路径
"pages/cat/cat",
"pages/dog/dog"
]
},
{
"root": "pkgB",
"pages": [
"pages/bird/bird"
]
}
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#00ff00",
"navigationBarTitleText": "我的第一个微信小程序",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#4b3c5d"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "index"
},
{
"pagePath": "pages/list/list",
"text": "list"
}
]
},
"usingComponents": {
"my-test01": "/components/test/test",
"my-test02": "/components/test2/test2",
"van-button": "@vant/weapp/button/index"
},
"sitemapLocation": "sitemap.json"
}
-
使用微信开发者工具查看当前分包体积信息
点击【详情】–》【基本信息】–》【本地代码】进行查看分包信息
-
分包原则
-
分包之间的引用原则
19.3 独立分包
实际上,独立分包的配置方法就是在普通分包配置对象的基础上新增一个 =true
的属性即可。
示例:
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"subpackages": [
{
"root": "pkgA",
"name": "p1",
"pages": [
"pages/cat/cat",
"pages/dog/dog"
]
},
{
"root": "pkgB",
"pages": [
"pages/bird/bird"
],
// 设置 independent 属性为 true,表示当前分包是一个独立分包
"independent": true
}
],
// some codes...
}
- 独立分包的引用原则
19.4 分包预下载
什么是分包预下载
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。
- 配置分包预下载
示例:
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"subpackages": [
{
"root": "pkgA",
"name": "p1",
"pages": [
"pages/cat/cat",
"pages/dog/dog"
]
},
{
"root": "pkgB",
"pages": [
"pages/bird/bird"
],
"independent": true
}
],
// preloadRule 对象节点指定分包预下载功能
"preloadRule": {
// 对象属性名就是跳转到哪个页面路径触发预下载功能
// 属性值表示跳转到这个页面后,会对对象中的 packages 数组声明的所有的分包(可以指定分包的 root 名或 name 别名)进行预下载; 而 network 属性指定了允许预下载功能在什么网络状态下可以执行,有 all(所有网络状态均可)、wifi(只允许 wifi 网络)两个有效值。
"pages/list/list": {
"packages": [
"pkgA"
],
"network": "all"
}
},
}
效果:控制台的 console 界面
- 分包预下载的限制
20、案例-自定义 tabBar
-
项目概览
-
实现步骤 参考官方文档
示例:
1)app.json 中的 tabBar 节点对象声明 custom=true
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/logs/logs"
],
"subpackages": [
{
"root": "pkgA",
"name": "p1",
"pages": [
"pages/cat/cat",
"pages/dog/dog"
]
},
{
"root": "pkgB",
"pages": [
"pages/bird/bird"
],
"independent": true
}
],
"preloadRule": {
"pages/list/list": {
"packages": [
"pkgA"
],
"network": "all"
}
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#00ff00",
"navigationBarTitleText": "我的第一个微信小程序",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"backgroundColor": "#4b3c5d"
},
"tabBar": {
"custom": true,
// 这个 list 数组不能少,原因:为了保证低版本兼容以及区分哪些页面是 tab 页
"list": [
{
"pagePath": "pages/index/index",
"text": "index"
},
{
"pagePath": "pages/list/list",
"text": "list"
}
]
},
"usingComponents": {
"my-test01": "/components/test/test",
"my-test02": "/components/test2/test2",
"van-button": "@vant/weapp/button/index"
},
"sitemapLocation": "sitemap.json"
}
2)项目中新建 custom-tab-bar
目录,然后在其中创建 index Component。
模拟器界面中底部位置出现红框的信息即为成功。
3)我们使用 Vant Weapp 框架提供的 tabBar 组件
app.json 导入相关组件
{
// some codes...
"usingComponents": {
"van-tabbar": "@vant/weapp/tabbar/index",
"van-tabbar-item": "@vant/weapp/tabbar-item/index"
},
// some codes...
}
4)在 tabBar 指定的 custom-tab-bar/index.wxml
中使用组件
<!--custom-tab-bar/index.wxml-->
<!-- 当 tabbar 发生改变之后,会触发 change 事件,从而调用 onChange 函数;active 属性表示当前选中的 tab 索引,从 0 开始编号 -->
<van-tabbar active="{{ activeTabarIndex }}" bind:change="onChange">
<!-- info 属性表示当前 tab 右上角的徽标值,若为 '' 或 0,则不进行渲染,若为其他数字,则会进行渲染 -->
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info && item.info > 0 ? item.info : ''}}">
<!-- slot='icon' 表示当前图标或图片是当前 tab 不被选中时启用,而 slot='icon-active' 则表示当前图标或图片是当前 tab 被选中时启用 -->
<image slot="icon" src="{{ item.icon.normalPath }}" mode="aspectFit" style="width: 30px; height: 18px;" />
<image slot="icon-active" src="{{ item.icon.activePath }}" mode="aspectFit" style="width: 30px; height: 18px;" />
<!-- tab 的文本内容直接在 van-tabbar-item 中声明即可 -->
{{item.text}}
</van-tabbar-item>
</van-tabbar>
5)修改 store 对象的数据,将徽标数与被选中的 tab 索引交给它来控制
/** store/store.js */
// 使用这个 JS 文件,专门用来创建 Store 实例对象
// 导入 mobx-miniprogram 模块的 observerable 方法,用于获取 Store 实例
// 导入 action 函数,用于生成修改 store 实例数据的修改方法
import { observable, action } from 'mobx-miniprogram'
// 根据传入的配置对象创建 Store 实例,并导出
export const store = observable({
// 定义数据字段
numA: 10,
numB: 0,
// 表示当前选中的 tab 索引值
activeTabbarIndex: 0,
// 定义计算属性
// 使用 get 关键字进行表示该属性值可读不可修改
// 方法名 sum 就是计算属性的访问名称
// sum 就表示了徽标数
get sum() {
// 返回值就是计算属性的值
return this.numA + this.numB
},
// 定义 actions 方法,专门用于修改 store 实例的数据
updateNumA: action(function (step) {
console.log('store.numA=', this.numA);
// 让 numA 直接加上传入的参数值
this.numA += step
}),
updateNumB: action(function (step) {
console.log('store.numB=', this.numB);
this.numB += step
}),
updateActiveTabbarIndex: action(function (index) {
this.activeTabbarIndex = index
})
})
6)在 custom-tab-bar/index.js
中编写代码逻辑
// custom-tab-bar/index.js
// 按需导入 mobx-miniprogram-bindings.storeBindingsBehavior 对象和自定义的 Store 对象
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../store/store'
Component({
/**
* 将 storeBindingsBehavior 存储到 behaviors 数组节点中
*/
behaviors: [storeBindingsBehavior],
/**
* 声明 storeBindings 对象,将组件对象与 Store 对象进行绑定
*/
storeBindings: {
// 数据源 store
store,
// fields 对象,用于映射组件与 store 的属性
fields: {
// key 是组件中使用的 data 的属性名,value 是 store 中属性名字符串
// activeTabarIndex 表示当前被选中的 tab 索引
activeTabarIndex: 'activeTabarIndex',
// 映射 store 的计算属性
// sum 就是当前的徽标数
sum: 'sum'
},
// actions 对象,用于映射组件与 store 的修改自身数据方法
actions: {
// 映射规则如 fields 中的规则
updateActiveTabbarIndex: 'updateActiveTabbarIndex'
}
},
/**
* 数据监听器
*/
observers: {
'sum': function (val) {
console.log(val);
// 为第二个 tab 的 info 属性赋值
this.setData({
'list[1].info': val
})
}
},
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
icon: {
normalPath: "../images/home.png",
activePath: "../images/home-active.png"
}
},
{
"pagePath": "pages/list/list",
"text": "消息",
icon: {
normalPath: "../images/cart.png",
activePath: "../images/cart-active.png"
},
// 徽标数据,新消息数量
info: 0
},
{
"pagePath": "pages/logs/logs",
"text": "联系我们",
icon: {
normalPath: "../images/my.png",
activePath: "../images/my-active.png"
}
}
]
},
/**
* 组件的方法列表
*/
methods: {
onChange(event) {
// event.detail 的值为当前选中项的索引
// this.setData({ active: event.detail });
// 修改 store 中的公共数据:当前被选中的 tab 索引
this.updateActiveTabbarIndex(event.detail)
console.log(event.detail);
// 当前选中项的索引发生变化后,立即跳转到对应页面去
wx.switchTab({
// 被选中的 tab 索引与 list 中 tab 的索引一致
url: '/' + this.data.list[event.detail].pagePath,
})
},
}
})
效果:
总结 4
个人观点
至此,本文档编写告一段落,感谢各位网友的点评与观看。如有错误,请指正。