八、使用背景图片(background-image)
注意:
- url不能指定为本地地址
- 可以将图片转化为base64方式使用。
- 使用网络地址:
background-image: url(https://img1.baidu.com);
考虑到版权问题,这里没有放具体路径。
- 使用base64格式指定(不推荐,因为base64过长)
推荐转换网站(图片在线转换Base64 | 图片编码base64)
九、事件系统
(一)事件绑定和事件对象
A、事件绑定
小程序中绑定事件跟网页开发中绑定事件几乎一致。小程序绑定事件不用on也没有click事件,分别被bind关键字和tap事件来代替。绑定事件的调用函数,要写在页面的js文件的page方法的对象中。绑定事件方式有两种:
- bind:tap
- bindtap
- 创建按钮和输入框,分别绑定点击事件和输入事件事件
<!--事件绑定-->
<button type="primary" size="default" bind:tap="consoleInfo">触发事件</button>
<input type="digit" bindinput="consoleInfo"/>
- 对应处理函数
//测试事件绑定
consoleInfo(event){
//自动传入事件对象参数
console.log("事件被触发了!");
let num = event.detail.value;
if(num != null){
console.log(num);
}else{
console.log("没有数据!");
}
}
知识点:小程序为了统一,button组件的type中仅有两个值:primary、warn,一个绿色一个红色。
B、事件对象
在事件被触发时,事件对象会默认传到函数形参中,事件对象中包括关于事件的各种数据,包括事件传递的参数。
下面是大概的解释:
detail | 存入触发事件的坐标值(x,y)、input框输入值(value) |
dataset(该属性在target,或者currentTarget属性下) | 参数值(属性名) |
crrentTarget | 事件的绑定者,意为绑定了当前事件处理函数的组件。 |
Target | 事件的触发者,意为触发了当前事件处理函数的组件。 |
一般情况下:currentTarget和Target一致,即事件的绑定者和触发者一致,但是也存在特殊情况,二者不一致。
- 绑定者和触发者分离 (通过事件冒泡方式触发)
C、事件冒泡
当有如下结构时:
<view bind:tap="consoleInfo">
<button type="primary" size="default" bind:tap="consoleInfo">触发事件</button>
</view>
当点击button时,触发自己事件后,会冒泡触发view的绑定事件。
- 在这里
- currentTarget 是button
- target是view
- 如何截断事件冒泡
- 改bind:事件名 ----> catch:事件名
D、参数传递
(1 )使用data-*传递参数,*代表属性名,value是属性值
- 参数传递方
<button catch:tap="consoleInfo" data-name="路飞" data-age="23">触发事件</button>
- 参数接收端
consoleInfo(event){
//自动传入事件对象参数
console.log("事件被触发了!");
//获取组件传递数据
console.log(event.currentTarget.dataset.name);
}
- 注意点:
- 当属性名涉及多个单词时,使用-连接,如:data-my-name,则在event对象中就为myName
- 如果为:data-myName,则event对象中自动转化为小写data-myname。
(2)使用自定义属性传递参数(mark)
- 参数传递方
<button catch:tap="consoleInfo" mark:name="路飞"mark:myAge="22">触发事件</button>
- 参数接收方
//测试事件绑定
consoleInfo(event){
//自动传入事件对象参数
console.log("事件被触发了!");
//获取组件传递数据
console.log(event.mark.name);
console.log(event.mark.myAge);
}
通过冒泡等触发的事件,定义的相关属性都会被保存在mark中。
重名属性,以最内层属性为主。
十、wxml语法
(一)声明和绑定数据
A、声明数据
在 页面.js 文件中的page中声明data并指定数据:
data:{
name:'杨旭',
age: 23,
lover: '天治国'
}
然后再wxml中使用双括号语法,绑定数据:
<text class="{{lover}}">{{name}}</text>
双括号中可以使用:表达式 逻辑运算 判断 三元运算
不可以使用:jsAPI 语句
B、修改数据
- 修改单属性
在方法中调用this.setData即可:
<button bind:tap="changeName">修改姓名</button>
changeName(){
this.setData({
name:"天将"
})
}
这样修改数据会动态将更新后的结果响应到页面上(响应式的),直接赋值则不是响应式的。
- 修改对象类型
data:{
name:'杨旭',
lover:"和之国",
go:{
name:"end",
age: 20,
body: "白皙"
}
}
方法一:
changeGo(){
this.setData({
'go.name':"天降无双",
'go.age':22
})
}
方法二:利用Object的assign方法,来合并对象,后面参数的对象在属性相同时,会覆盖前面参数的对象的属性,不一样则添加。
changeGo(){
let newGo=Object.assign(this.data.go,{name:"未来未来",age:34,head:'头颅生光'});
this.setData({
go:newGo
})
}
方法三:利用展开运算符
changeGo(){
let go = {
...this.data.go,
name:"花间可",
}
this.setData({
go //触发属性简写形式。
})
}
方法四:利用结构赋值删除对象属性
changeGo(){
let {name,...rest} = this.data.go;
//意为将go对象中name属性赋值到name,其他属性都赋值给rest对象,这样就巧妙删除了name属性。
this.setData({
go:rest
})
}
- 修改数组类型
方式一:
<text wx:for="{{myList}}" wx:key="index">{{item}}</text>
<button bind:tap="changeGo">修改list</button>
结构上要使用循环实现。
data:{
myList:[1,3,5,5,4,5],
},
changeGo(){
let myList = this.data.myList.push(4);
//压入数组后赋给原值
this.setData({
myList
})
}
方式二:展开运算符,附新值然后再用setData赋值
changeGo(){
let myList = [...this.data.myList,7]
this.setData({
myList
})
}
方式三:splice方法(删除数组元素)
changeGo(){
this.data.myList.splice(1,1);
this.setData({
myList:this.data.myList
})
}
方式四:使用过滤实现filter
changeGo(){
let oldList = this.data.myList.filter(item => item != 5);
this.setData({
myList:oldList
})
}
方式五:修改指定下标元素的值
changeGo(){
this.setData({
'myList[4]':78
})
}
C、双向绑定数据
双向绑定数据就是指,当页面数据发生改变时,也会影响到后台数据的改变。
方法:要双向绑定属性前加:model:
- input绑定数据
<input model:value="{{num}}"/>
data:{
num:34
}
- 复选框绑定数据
<checkbox model:checked="{{isSelected}}"></checkbox>
data:{
isSelected: false
}
注意:
- 只能是一个单一字段的绑定,双引号中不能使用其他内容,错误示范:
<input model:value="值为{{num}}"></input>
- 不能写data路径,即不支持数组和对象的双向绑定,错误示范:
<input model:value="{{end.num}}"></input>
(二)列表渲染
通过循环遍历一个数组或者对象,将其中每个元素渲染到页面上。
在组件中使用wx:for属性绑定一个数组或者对象,即可每一项数据重复渲染当前组件。
每一项默认为item,下表变量名为index。
注意:在添加wx:key时不需要使用双括号包裹。如果确定列表为静态的,可以不添加wx:key属性。
A、渲染列表和对象
data:{
furitList:[
{id:1,name:'苹果'},
{id:2,name:'香蕉'},
{id:3,name:'梨子'}
]
}
<list wx:for="{{furitList}}" wx:key="id">{{item.name}}</list>
data:{
furitList:[
2,234,'冲天香'
]
}
<list wx:for="{{furitList}}" wx:key="index">{{item}}</list>
index在遍历对象时,是对象的各个属性名,在遍历数组时是下标。
当遍历对象时,wx:key中可以直接指定对象的属性名。
当item本身就具有唯一性时,可以直接在wx:key中使用*this指定自身,如下:
data:{
furitList:[
2,3,5
]
}
<list wx:for="{{furitList}}" wx:key="*this">{{item}}</list>
B、修改默认、同时渲染一个item到多个组件
使用:wx:for-item指定每个元素名称;wx:for-index指定下标或者属性名代称。
data:{
furitList:[
{id:1,name:"苹果",price:10},
{id:2,name:"栗子",price:89},
{id:3,name:"香蕉",price:23}
]
}
<list wx:for="{{furitList}}" wx:key="id" wx:for-item="itemFor" wx:for-index="key">{{itemFor.name}}-{{key}}</list>
渲染到多个组件中 :注意,block不会被渲染到页面中。
<block wx:for="{{furitList}}" wx:key="id" wx:for-item="itemFor" wx:for-index="key">
<view>{{itemFor.name}}</view>
<view>{{itemFor.price}}</view>
</block>
(三)条件渲染
A、wx:if
data:{
isShow: true,
num:0
},
addNum(){
this.setData({
num:this.data.num+1,
})
}
<text wx:if="{{num == 0}}">{{num}}</text>
<text wx:elif="{{num == 1}}">{{num}}</text>
<text wx:else>{{num}}</text>
<button bindtap="addNum">数字加1</button>
注意:
- 只用当条件为true时,组件才会显示
- if、elif、else必须连续使用,否则报错,elif、else不能离开if单独使用(wx省写了)
- 这种方式,若不显示,就是真的将该组件从文件中移除了,在页面中不存在。
B、hidden
<text hidden="true">{{num}}</text>
若hidden为true,则元素不显示,原理是css中display:none。
为false,则显示。
十一、小程序机制
(一)小程序运行机制
小程序分为四种状态,前台、后台、挂起、销毁
前台:当小程序界面被展示给用户时,就处于前台状态。
后台:当用户通过主页面键、小程序关闭按钮、关机键让小程序离开展示界面,则会处于后台状态。
挂起:小程序进入“后台”状态一段时间后(5s),微信停止小程序JS线程执行,小程序进入“挂起”状态,当开发者使用了后台播放音乐、后台地理位置等功能时,小程序仍然可以保持后台运行,不会进入到挂起状态。
销毁:如果用户很久没有使用小程序,或者系统资源紧张,小程序会被销毁,即完全终止运行。当小程序进入后台并被“挂起”后,如果很长时间(目前是30分钟)都未再次进入前台,小程序会被销毁,当小程序占用系统资源过高时,可能会被系统或者微信客户端主动回收。
- 冷启动和热启动:
冷启动:在小程序没有在后台运行,从零开始打开的过程。
热启动:小程序仅仅在后台,没有被销毁,随时可以被切换到前台叫做热启动。
(二)小程序更新机制
在访问小程序时,微信会将小程序代码包缓存到本地。开发者在发布了新的小程序版本以后,微信客户端会检查本地缓存的小程序有没有新版本,并进行小程序代码包的更新。
小程序的更新机制有两种:启动时同步更新和启动时异步更新
启动时同步更新:微信运行时,会定期检查最近使用的小程序是否有更新,如果有更新,下次小程序启动时会同步进行更新,更新到最新版本后再打开小程序,如果用户长时间未使用小程序时,会强制同步检查版本更新。
启动时异步更新:在启动前没有发现更新,小程序每次冷启动时,都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本代码包,将新版本的小程序在下一次冷启动进行使用,当前访问使用的依然是本地的旧版本代码。
在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用wx.getUpdateManager API进行处理。在有新版本时提示用户重启小程序更新版本。
// 声明周期钩子,在冷启动时一定会执行该函数
onLaunch(){
//获取更新管理者对象,以方便下面操作
const updateManager = wx.getUpdateManager();
//执行在更新准备好时的方法
updateManager.onUpdateReady(function(){
wx.showModal({
title:"更新提示",
content:"新版本已准备好,是否重启应用使用最新版?",
// 成功时的回调函数,如果用户选择重启,执行更新。
success(res){
if(res.confirm){
updateManager.applyUpdate();
}
}
})
})
}
接下来在开发者工具中模拟版本更新,首先点击“普通编译”下拉框,点击添加编译模式
然后点击“在下一次更新时模拟更新”即可。
(三)小程序生命周期介绍
A、区别应用声明周期和小程序生命周期
应用生命周期是指应用程序进程从创建到消亡的整个过程。小程序的生命周期指的是小程序从启动到销毁的整个过程。
一个小程序完整的生命周期由应用生命周期、页面生命周期和组件生命周期三部分来组成。
小程序生命周期伴随着一些函数,这些函数由小程序框架本身提供,被称为生命周期函数,生命周期函数会按照顺序一词自动触发调用。帮助程序员在特定的时机执行特定的操作,辅助程序员完成一些比较复杂的逻辑。
这里大概介绍一下小程序的结构:
应用由页面构成,页面由组件构成。这一切所有就是小程序。
B、应用生命周期
应用生命周期声明在app.js中
应用周期分为三个:
-->onLaunch(小程序初始化)
-->onShow(小程序启动或者切到前台)
--> onHide(小程序切后台)
冷启动时:执行onLaunch、onShow、切到后台后执行onHide。
热启动时:从后天切前台执行onShow(大前提是应用进程没有被销毁)、切后台执行onHide
C、页面生命周期
页面生命周期声明在 页面.js文件中:
页面生命周期如下:
onLoad | 监听页面加载,在页面加载时执行。 |
onShow | 监听页面展示,会在页面加载前,或者切到其他页面(关闭应用程序在卸载之前)再次切回来时。 |
onReady | 监听初次渲染完成,该流程仅仅会在程序冷加载过程中仅仅执行一次。 |
onUnload | 页面在卸载的过程总会被执行。例如路由的时候,如果open-type为redirect,其他页面被销毁的时候,其他页面的redirect会被执行。 |
onHide | 页面被切走时,会被调用执行。 |
注意:
一、tabBar切换页面,原页面不会被销毁
二、导航open-type=navigator方式切到的页面,点击左上角小标返回后,原页面会被销毁。
十二、小程序API
(一)什么是小程序API
是小程序提供给开发者使用的一系列接口,通过这些接口,开发者可以方便地实现一些功能,如获取用户信息、微信登录、微信支付等,小程序提供的API几乎都挂载在wx对象下,例如:wx.request(),wx.setStorage()等,wx对象实际上就是小程序的宿主环境微信所提供的全局对象。
接口分为三大类:
- 异步API:通常都接受一个object类型的参数,例如:wx.request({});
- 同步API:约定以Sync结尾,例如:wx.setStorageSync();
- 事件监听API:约定以on开头,例如:wx.onAppHide();
注意,异步API支持callback&Promise两种调用方式:
- 当接口参数Object对象中不包含success/fail/complete时将默认返回Promise
- 部分接口如request,uploadFile本身就有返回值,因此不支持Promise风格的调用方式,它们的promisify需要开发者自行封装。
(二)wx.request(网络请求API)
- 首先需要将自己要请求的服务器通过公众号平台,添加后才可以使用,开发时可以使用ip地址,没有域名,但是上线时一定要变为正式域名。
- 取消合法域名校验:点开开发者工具中的“详情”-->“本地设置”
- 而后就可以使用了:
<text>获得的每日一句是:{{textMe}}</text>
<button type="primary" bind:tap="getTextFromInternet">点我获取每日一句</button>
data: {
textMe:'嘿嘿嘿'
},
getTextFromInternet(){
wx.request({
url: 'https://v1.hitokoto.cn/?c=f&encode=text',//一个自动获取随机一句话的网站
method:'get',
header:{},//请求头
data:{},//要携带的参数
//请求成功后执行的回调
//注意这里用使用函数简便写法,负责this指向会变,导致API无法使用
success:(resReturn)=>{
//根据后端返回内容决定,我这里返回内容保存在data中。
this.setData({
textMe:resReturn.data
})
console.log(resReturn.data);
},
//执行失败后调用的回调
fail(error){
console.log("失败了,原因是:" + error);
},
//无论成功或者失败都会执行的回调
complete(resInfo){
console.log(resInfo);
}
})
}
(三)wx.showLoading(展示加载提示框)
wx.hideLoading(隐藏加载提示框)
二者需要配合使用,我们在上面网络请求的例子中使用提示框:
getTextFromInternet(){
//在请求前就开启加载提示框
wx.showLoading({
title:'加载中...',
//设置加载框的消息提示内容,注意不会自动换行,若提示内容过多,会被多余部分会被隐藏。
mask:true,
//是否打开透明层,防止加载框存在,而用户继续触发页面内容。
})
wx.request({
//这里省略与本知识点无关内容
complete(resInfo){
// 请求完成后,关闭加载框
wx.hideLoading();
}
})
}
mask:开启模态透明层,防止加载框打开后,用户继续与页面交互产生。
(四)wx.showModel(模态对话框)、
wx.showToast(消息提示框)
A、wx.showModel
常常用于询问用户是否执行一些操作,例如:询问用户是否退出登录、是否删除该商品等等。
案例:我们在网路请求方法前加上模态对话框,询问是否需要网络请求:
async changeText(){
//添加模态对话框,询问是否要请求每日一句,该API是异步的,所以可以使用Promise对象格式。
const {confirm,cancel}= await wx.showModal({
title:'提示',//为模态对话框,说明模态对话框的主题
content:'是否要更换每日一句?'//消息的具体内容。
});
//根据返回值类型,判断用户选择结果
if(confirm == true){
//如果用户选择确定,则调用请求获取每日一句方法。
this.getTextFromInternet();
}else if(cancel == true){
console.log("用户取消更新每日一句!");
}
}
这里展示下,返回的Promise对象的结构:
B、wx.showToast(消息提示框)
消息提示框,根据用户的某些操作来告知操作的结果,例如:退出成功给用户提示,提示删除成功等等。
这里我们模拟每日一句更新成功提示:
//弹出消息提示框,提示每日一句更新成功
wx.showToast({
title:'更新成功!',
icon:"success",
//四个值,代表不同的图标
})
icon四个值分别是:
success | 成功图标,title最多七个汉字 |
loading | 加载中图标,title最多七个汉字 |
error | 失败图标,title最多七个汉字 |
none | 无图标,最多两行汉字。 |
(五)本地存储相关
分为同步和异步两种API,如下:
在微信环境中,存储或获取对象类型的数据,不需要JSON.parse()或者JSON.stringify()进行转换。
A、同步方式
setDataToStore(){
wx.setStorageSync('name', "少年李小龙");
wx.setStorageSync('people',{name: "少年李小龙",age: 23});
},
getDataToStore(){
console.log(wx.getStorageSync("people"));
},
removeDataToStore(){
wx.removeStorageSync('people');
},
clearDataToStore(){
wx.clearStorageSync();
}
B、异步方式
setDataToStore(){
//注意,因为异步方式,参数大多数以对象形式呈现。
wx.setStorage({key:'name', data:"少年李小龙"});
wx.setStorage({key:'people',data:{name: "少年李小龙",age: 23}});
},
async getDataToStore(){
const {data} = await wx.getStorage({key:"people"});
console.log(data);
},
removeDataToStore(){
const promise = wx.removeStorage({key:'people'});
console.log(promise);
},
clearDataToStore(){
wx.clearStorage();
}
(六)编程式路由导航
其实就是五个微信提供的API,代替了navigtor组件的open-type属性指定跳转方式的页面跳转模式。
API如下:
wx.navigateTo | 保留当前页面,并跳转到新的非tabBar页面。 |
wx.redirectTo | 销毁当前页面,并跳转到新的非tabBar页面。 |
wx.switchTab | 跳转到其他tabBar页面 |
wx.reLaunch | 关闭其他页面,重新加载指定页面。 |
wx.navigateBack | 关闭当前页面,返回一级或多级页面。 |
A、如何指定要跳转的页面?
wx.navigateTo({
url: '/pages/list/list',
})
B、指定多级页面
wx.navigateBack({
delta: 3//指定返回三级页面
})
C、携带参数
wx.navigateTo({
url: '/pages/list/list?name=苏爱&age=32',
})
D、接收参数
//被跳转页面的onLoad生命周期函数
onLoad(options) {
console.log(options);
}
(七)上拉加载页面
使用onReachBottom事件监听用户上拉加载行为。
默认触发onReachBottom的页面距离是50px,可以在app.json或者page.json中进行修改。
下面案例模拟下拉触发事件后加载新内容:
onReachBottom() {
//打开信息提示框,提示正在加载
wx.showToast({
title: '加载中...',
icon:"loading"
})
//通过定时器模拟请求过程
setTimeout(()=>{
let newIconList = [this.data.iconNumList[this.data.iconNumList.length -1]+1,this.data.iconNumList[this.data.iconNumList.length -1]+2,this.data.iconNumList[this.data.iconNumList.length -1]+3];
//将新产生的标签,加到标签list中,推动视图更新
this.setData({
iconNumList:[...this.data.iconNumList,...newIconList]
});
//完成后,关闭消息提示框
wx.hideToast();
},2000)
}
view{
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 230px;
}
view:nth-of-type(odd){
//给单数标签设置背景色
background-color: greenyellow;
}
view:nth-of-type(even){
//给双数标签设置背景色
background-color: indianred;
}
<view wx:for="{{iconNumList}}" wx:key="*this">{{item}}</view>
(八)下拉加载页面
需要再json配置页面开启允许下拉:
"enablePullDownRefresh": true,
"backgroundTextStyle":"light",
然后在页面,下拉时会触发onPullDownRefresh函数。我们通过该函数,将上拉增加的数组,置为初始状态:
onPullDownRefresh() {
this.setData({
iconNumList:[1,2,3]
});
//声明新的方法,在数组被置为0时,关闭下拉加载页面
if(this.data.iconNumList.length == 3){
//调用停止下拉刷新API
wx.stopPullDownRefresh();
}
}
(九)scroll-view实现上拉和下拉
首先数据和样式先展示:
data: {
iconNumList:[1,2,3],
isRefreshed: false //用来控制下拉刷新的开始和停止
},
getMore() {
中间过程跟上面上拉刷新一致
},
refreshHandler() {
this.setData({
iconNumList:[1,2,3]
});
//声明新的方法,在数组被置为0时,关闭下拉加载页面
if(this.data.iconNumList.length == 3){
//更改属性值为true
this.setData({
isRefreshed: false
})
}
},
.scroll-y{
height:100vh;//让scroll-view元素填满整个页面。
}
view{
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 230px;
}
view:nth-of-type(odd){
//给单数标签设置背景色
background-color: greenyellow;
}
view:nth-of-type(even){
//给双数标签设置背景色
background-color: indianred;
}
<scroll-view
scroll-y
class="scroll-y"
lower-threshold="100"
bindscrolltolower="getMore"
enable-back-to-top
refresher-enabled
refresher-default-style="black"
refresher-background="#ccc"
bindrefresherrefresh="refreshHandler"
refresher-triggered="{{isRefreshed}}"
>
<view wx:for="{{iconNumList}}" wx:key="*this">{{item}}</view>
</scroll-view>
关键在scroll-view标签的各个属性值,下面列出各属性值具体作用:
scroll-y | 设置上下滚动模式 |
lower-threshold | 设置触发上拉刷新的距离 |
bindscrolltolower | 指定触发上拉刷新要调用的函数 |
enable-back-to-top | 允许快捷返回顶部 |
refresher-enabled | 允许下拉刷新 |
refresher-default-style | 设置刷新界面风格 |
refresher-background | 刷新背景色 |
bindrefresherrefresh | 指定下拉刷新后要调用的函数 |
refresher-triggered | 指定控制是否刷新的变量,要求变量为boolean值。 |
十三、自定义组件
小程序支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。
(一)全局自定义组件和局部自定义组件
A、公共组件
将页面内的功能模块抽取成自定义组件,以便在不同的页面中重复使用。
- 声明
在项目路径下,创建components文件夹,在文件夹下继续创建已组建名命名的文件夹(比如my_view),最后在该文件下“创建component”(my_view)文件(无后缀)后,开发者工具会自动补全文件夹内的文件,其结构和一个界面的结构一样,但作为组件来用。
- 注册组件
在app.json中全局注册,配置参数,key是组件名,value是组件路径。
"usingComponents": {
"my_view":"./components/my_view/my_view"
}
这样子,这个组件在整个项目的任何地方都可以使用了!
- 使用
直接在随意的页面wxml中声明即可。
<my_view></my_view>
B、页面组件
将复杂的页面拆分成多个低耦合模块,有助于代码维护。注册后只能由本页面使用。
- 声明
和全局组件相同,唯一不一样之地是,页面组件只需要声明在页面文件夹内部。
- 注册
和全局注册一致,不同是页面组件在页面内部的json中注册。
- 使用
与上同,但注意只能使用在页面内部。