小程序项目结构
一.了解项目的基本组成结构
- pages用来存放所有小程序的页面
- utils用来存放工具性质的模块(例如:格式化时间的自定义模块)
- app.js小程序项目的入口文件
- app.json小程序项目的全局配置文件
- app.wxss小程序项目的全局样式文件
- project.config.js项目的配置文件
- sitemap.json用来配置小程序及其页面是否允许被微信索引
二.小程序页面的组成部分
小程序官方建议,把所有小程序的页面都存放在pages目录中,以单独的文件夹存在:
其中,每个页面由4个基本文件组成,它们分别是:
- .js文件(页面的脚本文件,存放页面的数据,事件处理函数等)
- .json文件(当前页面的配置文件,配置窗口的外观、表现等)
- .wxml文件(页面的模版结构)
- .wxss文件(当前页面的样式表文件)
三.json配置文件
json配置文件的作用:json是一种数据格式,在实际开发中,json总是以配置文件的形式出现。小程序中通过不同的.json配置文件,可以对小程序项目进行不同级别的配置。
小程序项目中有4种json配置文件,分别是:
- 项目根目录中的app.json配置文件
- 项目根目录中的project.config.json配置文件
- 项目根目录中的sitemap.json配置文件
- 每个页面文件夹中的.json配置文件
1.app.json
app.js是当前小程序的全局配置,包括了小程序的所有页面路径、窗口外观、界面表现、底部tab等。
navigationBarBackgroundColor:导航栏背景颜色,如
#000000,#666,十六进制颜色
navigationBarTextStyle:导航栏标题颜色,仅支持 black / white
修改项目首页:调整app.json中pages数组中页面路径的前后顺序,即可修改项目的首页。小程序会把排在第一位的页面当做项目首页进行渲染。
在app.json文件中的"pages"属性中,直接写"pages/my/my",然后保存,项目会自动创建my文件夹
2.project.config.json
project.config.json是项目配置文件,用来记录小程序开发工具所做的个性化配置,例如:
- setting中保存了编译相关的配置
- projetname中保存的是项目名称
- appid中保存的是小程序的账号ID
3.sitemap.json
4.每个页面文件夹中的.json配置文件
小程序中的每一个页面,可以使用.json文件来对本页面的窗口外观进行配置,页面中的配置项会覆盖app.json的window种相同的配置项。
四.WXML模版
1.wxml 和 html的区别
(1)标签名称不同
- html(div、span、img、a)
- wxml(view、text、image、navigator)
(2)属性节点不同
<a href="#">超链接</a>
<navigator url="/pages/index/index"></navigator>
(3)提供了类似于vue中的模板语法
- 数据绑定
- 列表渲染
- 条件渲染
2.wxss 和 css的区别
wxss还有: @import样式导入
五.小程序中js文件分类
小程序宿主环境
宿主环境包括 通信模型、运行机制、组件、API
1.通信模型
小程序通信的主体是渲染层和逻辑层。
2.运行机制
小程序启动的过程:
页面渲染的过程:
3.组件
小程序中组件的分类:网址如下 developers.weixin.qq.com/miniprogram…
常用的视图容器类组件
swiper和swiper-item组件的基本使用:
swpier组件的常用属性:
常用的基础内容组件
长按复制的话:要在text标签中添加selectable属性
<text user-select="true">文本组件text</text>
rich-text富文本可以添加nodes属性:
<rich-text nodes="<h3 style='color:pink;'>富文本框</h3>"></rich-text>
其他常用组件
4.API
小程序API的3大分类:
正式开始学习小程序开发内容
一、wxml模板语法
1.数据绑定
基本原则: 在data中定义数据,在wxml中使用数据。
动态绑定的时候和vue不同【vue中是在src前加:,且imgSrc不加{{ }}】。但在小程序中无论是内容还是属性都要加{{ }}
//页面结构
<image src="{{imageSrc}}" mode="widthFix"></image>
//页面数据
Page({
data: {
imageSrc:'xxx'
},
})
2.事件绑定
(1)小程序中常用的事件:
(2)事件对象的属性列表:
taeget 和 currentTarget的区别:
(3)bindtap的语法格式:
在小程序之中,不存在html中的onclick鼠标点击事件,而是通过tap事件来响应用户的触摸行为。
// 通过bindtap,为组件绑定 tap 触摸事件
<button type="primary" bindtap="changCount">按钮</button>
// 在js文件中定义函数,事件参数通过形参 event(一般写成e)来接收
Page({
changCount(e){
console.log(e); //事件参数对象 e
},
})
(4)在事件处理函数中为data中的数据赋值:
通过调用 this.setData(dataObject)方法。
Page({
data: {
count:1
},
// 修改count的值
changCount(){
this.setData({
count:this.data.count+1
})
},
})
(5)事件传参:
小程序中的事件传参比较特殊,不能在绑定事件的同时为事件处理函数传递参数。
// 错误写法
<button type="primary" bindtap="tapHandle(123)">事件传参</button>
//小程序会把bindtap的属性值,统一当成事件名称来处理,
//相当于事件名为 tapHandle(123)。
解决办法:可以为组件提供 dada-* 自定义属性传参,其中 * 代表的是参数的名字。
<button type="primary" bindtap="tapHandle" data-info="{{24}}">事件传参</button>
// info会被解析为参数的名字
// 数值 24 会被解析为参数的值
// 如果不加 {{}} 传过去的就是字符串了
在事件处理函数中,通过 event.target.dataset.参数名 即可获取到具体参数的值。
tapHandle(e){
//dataset是一个对象,包含了所有通过 data-* 传递过来的参数项
console.log(e.target.dataset);
//通过 dataset 可以访问到具体参数的值
console.log(e.target.dataset.info);
},
(6)bindinput的语法格式:
在小程序中,通过input事件来响应文本框的输入事件,语法格式如下:
<input bindinput="inputHandler"/>
inputHandler(e){
// e.detail.value 是变化过后,文本框最新的值
console.log(e.detail.value);
//实现文本框和data之间的数据同步
this.setData({
msg: e.detail.value
})
},
3.条件渲染
如果要一次性控制多个组件的展示与隐藏,可以使用一个<block></block>标签将多个组件包装起来,并在<block>标签上使用wx:if控制属性。
注意:<block>并不是一个组件,它只是一个包裹性质的容器,不会在页面中做任何渲染
在小程序中,直接使用 hidden="{{ true/false}}" 也能控制元素的显示与隐藏。
4.列表渲染
通过wx:for可以根据指定的数组,循环渲染重复的组件结构,语法如下:
//默认情况下,当前循环项的索引用index表示;当前循环项用item表示
<view wx:for="{{array1}}">索引是{{index}}--数组项是{{item}}</view>
手动指定索引和当前项的变量名:
- 使用wx:for-index指定索引名 (写key的时候不加大括号)
- 使用wx:for-item指定循环项的变量名
<view wx:for="{{array1}}" wx:for-index='id' wx:for-item='arr'>索引是{{id}}--数组项是{{arr}}</view>
wx:key的使用:类似于vue列表渲染中的:key,小程序在实现列表渲染时,也建议为渲染出来的列表项指定唯一的key值,从而提高渲染的效率。
案例一:轮播图案例
<view>
<swiper>
<swiper-item wx:for="{{imageslist}}" wx:key="id">
<image src="{{item.image}}" alt=""/>
<!-- <text>{{index}}</text> --> 输出为 0
<!-- <text>{{item.id}}</text> --> 输出为 1
</swiper-item>
</swiper>
</view>
// data数据
imageslist:[
{id:1,image:"http://xxx3.jpg"},
{id:2,image:"http://xxx4.jpg"},
{id:3,image:"http://xxx5.jpg"}
]
// wxss样式
swiper{
height: 350rpx;
}
swiper image{
width: 100%;
height: 100%;
}
案例二:经典九宫格
<view class="grid-list">
// 与案例四相关联,传递当前点击格子的id和name
<navigator url="/pages/shoplist/shoplist?id={{item.id}}&title={{item.name}}" open-type="navigate" class="grid-item" wx:for="{{catelist}}" wx:key="id">
<image src="{{item.icon}}"></image>
<text>{{item.name}}</text>
</navigator>
</view>
// 样式
.grid-list{
display: flex;
flex-wrap: wrap;
border-top: 1px solid #efefef;
border-left: 1px solid #efefef;
}
.grid-item{
width: 33.33%;
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-right: 1px solid #efefef;
border-bottom: 1px solid #efefef;
box-sizing: border-box;
}
.grid-item image{
width: 60rpx;
height: 60rpx;
}
.grid-item text{
font-size: 24rpx;
margin-top: 10rpx;
}
二、wxml模板样式
1.rpx
rpx是微信小程序独有的,用来解决屏适配的尺寸单位。
实现原理:rpx把所有设备的屏幕,在宽度上等分为750份(即:当前屏幕的总宽度为750rpx)。
小程序在不同设备上运行的时候,会自动把rpx的样式单位换算成对应的像素单位来渲染,从而实现屏幕适配。
三、全局配置
1.全局配置文件及常用的配置项
2.全局配置-window
小程序窗口的组成部分:
window节点常用的配置项:
上拉触底是指手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。【具体是指进度条距离底部的距离为50px的时候加载更多数据】
3.全局配置-tabBar
1.什么是tabBar
2.tabBar的6个组成部分
3.tabBar节点的配置项
4.每个tab项的配置选项
5.配置tabBar
"tabBar":{
"list":[
{
"pagePath":"pages/index/index",
"text":"首页"
},
{
"pagePath":"pages/my/my",
"text":"我的"
}
]
} ,
四、页面配置
app.json中的window节点,可以全局配置小程序中每个页面的窗口表现。
如果某些小程序页面想要有特殊的窗口表现,此时,“页面级别的 .json配置文件”就可以实现这种需求。
当页面配置与全局配置冲突时,根据就近原则,最终的效果以页面配置为准。
页面配置中常用的配置项:
不建议在全局配置中设置enablePullDownRefresh属性,建议在单独的页面json文件中设置。
注意在真机演示的时候需要手动关闭。
五、网络数据请求
1.小程序中网络数据请求的限制
出于安全性方面的考虑,小程序官方对数据接口的请求做出了如下两个限制:
(1)只能请求https类型的接口
(2)必须将接口的域名添加到信任列表中
2.配置request合法域名
需求:假设在自己的微信小程序中,希望请求 www.escook.cn/ 域名下的接口
配置步骤:登录微信小程序管理后台->开发->开发设置->服务器域名->修改request合法域名
3.如何跳过request合法域名校验
4.发起 GET 请求
调用微信小程序提供的wx.request()方法,可以发起get数据请求,示例代码如下:
getHandle(){
wx.request({
//请求的接口必须基于https协议
url: 'https://www.escook.cn/api/get',
method:'GET',
data:{
name:'tom',
age:24
},
//请求成功后的回调函数
success:(res)=>{
console.log(res.data.data);
}
})
},
5.发起 POST 请求
调用微信小程序提供的wx.request()方法,可以发起post数据请求,示例代码如下:
postHandle(){
wx.request({
//请求的接口必须基于https协议
url: 'https://www.escook.cn/api/post',
method:'POST',
data:{
name:'tom',
age:24
},
//请求成功后的回调函数
success:(res)=>{
console.log(res.data.data);
}
})
},
6.在页面刚加载时请求数据
在很多情况下,我们需要在页面刚加载的时候,自动请求一些初始化的数据,此时需要在页面的onLoad事件中调用获取数据的函数,示例如下:
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getHandle();
this.postHandle()
},
7.关于跨域和Ajax的说明
跨域问题只存在于基于浏览器的web开发中。由于小程序的宿主环境是微信客户端,所以小程序中不存在跨域的问题。
Ajax技术的核心是依赖于浏览器中的XMLHttpRequest这个对象,由于小程序的宿主环境是微信客户端,所以小程序中不能叫做“发起Ajax请求”,而是叫做“发起网络数据请求”。
六、页面导航
小程序中实现页面导航的两种方式
(1)声明式导航
- 在页面上声明一个
<navigator>导航组件 - 通过点击
<navigator>组件实现页面跳转
(2)编程式导航
- 调用小程序的导航API,实现页面的跳转
1.声明式导航-导航到tabBar页面
tabBar页面指的是被配置为tabBar的页面。
在使用<navigator>组件跳转到指定的tabBar页面时,需要指定url属性和open-type属性,其中
- url表示要跳转的页面的地址,必须以 / 开头
- open-type表示跳转的方式,必须为switchTab
<navigator url="/pages/message/message" open-type="switchTab">跳转到tabBar页面</navigator>
2.声明式导航-导航到非tabBar页面
非tabBar页面指的是未被配置为tabBar的页面。
在使用<navigator>组件跳转到指定的tabBar页面时,需要指定url属性和open-type属性,其中:
- url表示要跳转的页面的地址,必须以 / 开头
- open-type表示跳转的方式,必须为navigate
<navigator url="/pages/message/message" open-type="navigate">跳转到非tabBar页面</navigator>
为了简便,在导航到非tabBar页面时,open-type属性可省略。
3.声明式导航-后退导航
如果要后退到上一页面或多级页面,则需要指定open-type属性和delta属性,其中:
- open-type的值必须是navigateBack,表示要进行后退导航
- delta的值必须是数字,表示要后退的层级。
为了简便,如果只是后退到上一层,则可以省略delta,因为默认值就是1。
// 我测试不管用?? 后退不了是怎么回事??
//我现在在message页面,是tabBar配置页面
<navigator open-type="navigateBack">后退到home页面</navigator>
我测试不管用?? 后退不了是怎么回事??
原因:tabBar 页面是不能实现后退的效果的. 因为, 当我们跳转到 tabBar 页面,会关闭其他所有非tabBar 页面,所以当处于 tabBar 页面时, 无页面可退。
解决办法:用路径url跳转过去,参考1、2条笔记。
4.编程式导航-导航到tabBar页面
调用wx.switchTab(Object object)方法,可以跳转到tabBar页面。其中Object参数对象的属性列表如下:
//页面结构
<button bindtap='gotoMessage'>跳转到消息页面</button>
//编程式导航,跳转到tabBar页
gotoMessage(){
wx.switchTab({
url:'/pages/message/message'
})
}
5.编程式导航-导航到非tabBar页面
//页面结构
<button bindtap='gotoInfo'>跳转到消息页面</button>
//编程式导航,跳转到tabBar页
gotoInfo(){
wx.navigateTo({
url:'/pages/info/info'
})
}
6.编程式导航-后退导航
导航传参
1.声明式导航传参
2.编程式导航传参
3.接收导航参数---在onLoad中
通过声明式导航传参或编程式导航传参所携带的参数,可以直接在onLoad事件中直接获取到。
一般会把传递过来的参数对象赋值给data中的query,因为要用到这个数值。
data中定义一个 query:{}
//在onload函数中添加
this.setData({
query:options
})
七、页面事件
1.下拉刷新事件
下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。
启动下拉刷新有两种方式:
(a)全局开启下拉刷新
- 在app.json的window节点中,将enablePullDownRefresh设置为true
(b)局部开启下拉刷新【为需要的页面单独开启下拉刷新的效果】
- 在页面的.json配置文件中,将enablePullDownRefresh设置为true
配置下拉刷新窗口的样式:
在全局或页面的.json配置文件中,通过backgroundColor和backgroundTextStyle来配置下拉刷新窗口的样式,其中:
- backgroundColor用来配置下拉刷新窗口的背景颜色,仅支持16进制的颜色值
- backgroundTextStyle用来配置下拉刷新的loading的样式,仅支持dark和light
监听页面的下拉刷新事件:
在页面的.js文件中,通过onPullDownRefresh()函数即可监听当前页面的下拉刷新事件。
停止下拉刷新的效果:
当处理完下拉刷新后,下拉刷新的loading效果会一直显示,所以需要手动隐藏loading效果。此时,调用wx.stopPullDownRefresh()可以停止当前页面的下拉刷新。
2.上拉触底事件
上拉触底是移动端的专有名词,通过手指在屏幕上的上拉滑动操作,从而加载更多数据的行为。
监听页面的下拉触底事件:
在页面的.js文件中,通过onReachBottom()函数即可监听当前页面的上拉触底事件。
配置上拉触底距离:
上拉触底距离指的是触发上拉触底事件时,滚动条距离页面底部的距离。
可以在全局或页面的.json配置文件中,通过onReachBottomDistance属性来配置上拉触底的距离。小程序默认的是50px,可按需求修改。
案例三--下拉触底事件添加loading效果和节流处理:
//wxml文件代码:
// 第 3 步:渲染UI结构并美化页面效果
<view wx:for="{{colorList}}" wx:key="index" class="num-item" style="background-color: rgba({{item}});">{{item}}</view>
//js文件代码:
Page({
data: {
colorList:[],
//第 6 步:对上拉触底进行节流处理
/* 6.1 在data中定义isloading节流阀---false 表示当前没有进行任何数据请求---true 表示当前正在进行数据请求*/
isloading:false
},
//第 1 步:定义随机获取颜色的方法并存储数据
getColors(){
/* 6.2 在getColor()方法中修改isloading节流阀的值---在刚调用方法时将节流阀设置为true---在complete回调函数中,将节流阀重置为false*/
this.setData({
isloading:true
})
// 第 5 步:(5.1)添加loading提示效果
wx.showLoading({
title: '数据加载中...',
})
wx.request({
url: 'https://www.escook.cn/api/color',
method:'GET',
// 将返回数据中的data项重命名为res
success:({data:res})=>{ //解构data属性
this.setData({
colorList:[...this.data.colorList,...res.data] //将旧数据与新数据进行拼接
})
},
//(5.2).隐藏loading效果,无论请求成功或失败都要关闭加载框
complete:()=>{
wx.hideLoading()
// 6.3 请求结束后,把状态改回来
this.setData({
isloading:false
})
}
})
},
onLoad(options) {
//第 2 步:在页面一加载的时候就调用方法获取colorlist
this.getColors();
},
onReachBottom() {
/* 6.4 判断节流阀的值,从而对数据请求进行节流控制---如果节流阀的值为true,则阻止当前请求---如果节流阀的值为false,则发起数据请求*/
if(this.isloading) return
//第 4 步:在上拉触底时调用获取随机颜色的方法
this.getColors()
},
})
//wxss文件代码
效果图:
扩展:自定义编译模式,可以改变首次要运行的页面。
八、生命周期
1.什么是生命周期
生命周期是指一个对象从创建->运行->销毁的整个阶段,强调的是一个时间段。
也可以把小程序运行的过程概括为生命周期:
- 小程序的
启动,表示生命周期的开始。 - 小程序的
关闭,表示生命周期的结束。 - 中间小程序运行的过程,就是小程序的生命周期。
2.生命周期的分类
在小程序中,生命周期分为两类,分别是:
(1)应用生命周期
- 特指小程序从启动->运行->销毁的过程
(2)页面生命周期
- 特指小程序中,每个页面的加载->渲染->销毁的过程
其中,页面的生命周期范围较小,应用程序的生命周期范围较大,如图所示:
3.什么是生命周期函数
生命周期函数:是由小程序框架提供的内置函数,会伴随着生命周期,自动按次序执行。
生命周期函数的作用:允许程序员在特定的时间点``,执行某些特定的操作。例如,页面刚加载的时候,可以在onLoad生命周期函数中初始化页面的数据。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
4.生命周期函数的分类
小程序中的生命周期函数分为两类,分别是:
(1)应用的生命周期函数
- 特指小程序从启动->运行->销毁期间一次调用的那些函数
(2)页面的生命周期函数
- 特指小程序中,每个页面从加载->渲染->销毁期间依次调用的那些函数
5.应用的生命周期函数
小程序的应用周期函数需要在app.js中进行声明,示例如下:
// app.js文件
App({
//当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
onLaunch: function () {},
//当小程序启动,或从后台进入前台显示,会触发 onShow
onShow: function (options) {},
//当小程序从前台进入后台,会触发 onHide
onHide: function () {},
}
})
6.页面的生命周期函数
小程序的页面生命周期函数需要在页面的.js文件中进行声明,示例如下:
Page({
//生命周期函数--监听页面加载
onLoad(options) {},
//生命周期函数--监听页面初次渲染完成
onReady() {},
//生命周期函数--监听页面显示
onShow() {},
//生命周期函数--监听页面隐藏
onHide() {},
//生命周期函数--监听页面卸载
onUnload() {},
})
九、WXS脚本
1.什么是wxs
wxs是小程序独有的一套脚本语言,结合wxml,可以构建出页面的结构。
2.wxs的应用场景
wxml中无法调用在页面的.js中定义的函数,但是,wxml中可以调用wxs中定义的函数。因此,小程序中wxs的典型应用场景就是“过滤器”。
3.wxs和javascript的关系
4.wxs脚本-基础语法
(1)内嵌 wxs 脚本
wxs代码可以编写在wxml文件中的<wxs>标签内,就像javascript代码可以编写在html文件中的<script>标签内一样。
wxml文件中的每个<wxs></wxs>标签,必须提供module属性,用来指定当前wxs的模块名称,方便在wxml中访问模块中的成员。
// username:'yqy'
<view>{{ m1.toUpper(username) }}</view>
//页面 YQY
<wxs module="m1">
module.exports.toUpper = function(str){
return str.toUpperCase()
}
</wxs>
(2)定义外联的wxs脚本
wxs代码还可以编写在以 .wxs 为后缀名的文件内,就像javascript代码可以编写在以 .js为后缀名的文件中一样。
(3)使用外联的 wxs脚本
module.exports 一个文件只能用一次
在wxml中引入外联的wxs脚本时,必须为<wxs>标签添加module和src属性,其中:
module用来指定模块的名称src用来指定要引入的脚本的路径,且必须是相对路径
// username2:'YQY'
<view>{{m2.toLower(username2)}}</view>
// 页面展示 yqy
// 引入文件
<wxs src="../../utils/tools.wxs" module="m2"></wxs>
// 这个 .wxs文件写在 utils文件夹下
function toLower(str) {
return str.toLowerCase()
}
module.exports = {
toLower:toLower //这里要写全,不能简写
}
5.wxs的特点
案例四--点击九宫格中的任意一个格子跳转到新界面并展示相关数据
1.首先是shoplist.wxml文件的布局
<view wx:for="{{shoplist}}" wx:key="id" class="listview">
<view class="img">
<image src="{{ item.images[0] }}" alt=""/>
</view>
<view class="info">
<text>{{item.name}}</text>
<text>手机:{{m1.splitPhone(item.phone) }}</text>
<text>地址:{{item.address}}</text>
</view>
</view>
// 引入外联的wxs脚本
<wxs src="../../utils/tools.wxs" module="m1"></wxs>
2.shoplist.json文件--打开下拉刷新设置,并设置样式
{
"usingComponents": {},
// 设置下拉触底的距离
"onReachBottomDistance": 200,
// 单独打开当前页面的下拉刷新
"enablePullDownRefresh": true,
"backgroundColor": "#efefef",
"backgroundTextStyle": "dark"
}
3.shoplist.js文件
Page({
data: {
query:{},
shoplist:[],
//请求参数
page:1,
pageSize:10,
total:0,
//加载效果
isloading:false
},
//生命周期函数--监听页面加载
onLoad(options) {
// 存储传过来的id和name
this.setData({
query:options
})
this.getShopList()
},
getShopList(callback){
this.setData({
isloading:true
})
// 展示loading效果
wx.showLoading({
title: '加载下一页...',
})
wx.request({
url: `https://www.escook.cn/categories/${this.data.query.id}/shops`,
method:'GET',
//以分页的形式获取商铺列表数据,传递参数
data:{
_page:this.data.page,
_limit:this.data.pageSize
},
success:(res)=>{
this.setData({
shoplist:[...this.data.shoplist,...res.data],
// header是一个对象,里面的X-Total-Count有字符,所以用字符串形式,获得是字符串“80”,所以要转成数值类型
total:res.header['X-Total-Count'] - 0
})
},
// 请求完成后关闭loading效果
complete:()=>{
wx.hideLoading()
this.setData({
isloading:false
})
callback && callback()
}
})
},
//生命周期函数--监听页面初次渲染完成
onReady() {
wx.setNavigationBarTitle({
title: this.data.query.title
})
},
//页面相关事件处理函数--监听用户下拉动作
onPullDownRefresh() {
// 需要重置关键的数据
this.setData({
page:1,
pageSize:10,
total:0
})
// 重新发起数据请求
this.getShopList(()=>{
//并不是所有的请求都要关闭下拉刷新效果,所以把它作为回调函数传过去比较合适
wx.stopPullDownRefresh()
})
},
//页面上拉触底事件的处理函数
onReachBottom() {
if(this.data.page*this.data.pageSize>=this.data.total){
// 证明没有下一页的数据了
return wx.showToast({
title: '数据加载完毕',
icon:'none'
})
}
// 判断是否正在加载数据
if(this.isloading) return
this.setData({
page:this.data.page+1
})
this.getShopList()
},
})
4.外联的wxs文件
// 分隔手机号
function splitPhone(str) {
if(str.length!== 11) return str
// 把一个字符串分割成字符串数组
var arr = str.split('')
// 在第4个位置删除0个元素,添加-
arr.splice(3,0,'-')
arr.splice(8,0,'-')
// 数组元素转换为字符串,json()返回新的字符串
return arr.join('')
}
module.exports={
splitPhone:splitPhone //这里要写全,不能简写
}
效果图:
十、自定义组件
1.组件的创建与引用
(1)创建组件
①在项目的根目录中,鼠标右键,创建 components -> test文件夹(这是一个组件)
②在新建的components -> test文件夹上,右键“新建component”
③输入组件名称
注意:为了保证目录结构的清晰,建议把不同的组件,存放到单独目录中。
(2)引用组件
引用方式分为“局部引用”和“全局引用”,顾名思义:
- 局部引用:组件只能在当前被引用的页面内使用
- 全局引用:组件可以在每个小程序页面中使用
(3)局部引用组件
在页面的.json配置文件中引用组件的方式,叫做"局部引用"。
{
// 在.json文件中引入组件
"usingComponents": {
"my-test":"/components/test/test"
}
}
<!-- 在页面的 .wxml文件中使用组件 -->
<my-test></my-test>
(4)全局引用组件
在 app.json 全局配置文件中引用组件的方式,叫做"全局引用"。和局部引用组件相同。
(5)全局引用VS局部引用
根据组件的使用频率和范围,来选择合适的引用方式:
- 如果某组件在多个页面中经常被用到,建议进行“全局引用”
- 如果某组件只在特定的页面中被用到,建议进行“局部引用”
(6)组件和页面的区别
从表面来看,组件和页面都是由js、json、wxml和wxss这四个文件组成的。但是,组件和页面的.js与 json文件有明显的不同:
- 组件的json文件中需要声明
"component":true属性 - 组件的.js文件中调用的是
Component()函数 - 组件的事件处理函数需要定义到
methods节点中
// json文件
{
"component": true,
"usingComponents": {}
}
// js文件
Component({
//组件的属性列表
properties: {},
//组件的初始数据
data: {},
//组件的方法列表
methods: {}
})
2.样式
(1)组件样式隔离
默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的结构。好处:
- 防止外界的样式影响组件内部的样式
- 防止组件的样式破坏外界的样式
(2)组件样式隔离的注意点
- app.wxss中的全局样式对组件无效
- 只有class 选择器会有样式隔离效果, id 选择器、属性选择器、标签选择器不受样式隔离的影响
建议:在组件和引用组件的页面中建议使用class择器,不要使用id、属性、标签选择器!
(3)修改组件的样式隔离属性
默认情况下,自定义组件的样式隔离特性能够防止组件内外样式互相干扰的问题。但有时,我们希望在外界能够控制组件内部的样式,此时,可以通过stylelsolation修改组件的样式隔离选项,用法如下:
// 在组件的.js文件中新增如下配置
Component({
options:{
styleIsolation: "apply-shared"
}
})
// 或在组件的 .json 文件中新增如下配置
{
"styletsolation":"apply-shared"
}
3.数据、方法和属性
(1)data数据
在小程序组件中,用于组件模板渲染的私有数据,需要定义到data节点中。
(2)methods方法
在小程序组件中,事件处理函数和自定义方法需要定义到methods节点中。
(3)properties属性
在小程序组件中,properties是组件的对外属性,用来接收外界传递到组件中的数据。
// index.wxml文件:把max传给 test组件
<my-test max="9"></my-test>
// properties属性的值也可以用于页面渲染
<view>{{max}}</view>
// test.wxml文件
<text>当前count值是:{{count}}</text>
<button bindtap="addCount">+1</button>
// test.js文件
Component({
properties: {
// 第一种方式:简化的方式
// max:Number
// 第二种方式:完整的定义方式(没有传值的情况下)
max:{
type:Number,
value:10
}
},
data: {
count:0
},
//组件的方法列表【包含事件处理函数和自定义方法】
methods:{
//事件处理函数
addCount(){
if(this.data.count>=this.properties.max) return
this.setData({ count:this.data.count + 1})
//通过 this直接调用自定义方法
this._showCount()
},
//自定义方法建议以 _ 开头
_showCount(){
wx.showToast({
title:'count值为:' + this.data.count,
icon:'none'
})
}
}
})
(4)data和properties区别
在小程序的组件中,properties属性和data数据的用法相同,它们都是可读可写的,只不过:
- data更倾向于存储组件的私有数据
- properties更倾向于存储外界传递到组件中的数据
this.data === this.properties // true
(5)使用 setDate修改properties 的值
由于data数据和properties属性在本质上没有任何区别,因此properties属性的值也可以用于页面渲染或使用setData为properties中的属性重新赋值。
4.数据监听器
(1)什么是数据监听器
数据监听器是用于监听和响应任何属性和数据字段的变化,从而执行特定的操作。它的作用类似于vue中的watch侦听器。在小程序组件中,数据监听器的基本语法格式如下:
(2)基本用法
Component({
//数据监听节点
observers:{
// 前后顺序一致
'字段a,字段b':function (字段a的新值,字段b的新值) {
// do something
}
},
})
(3)监听对象属性的变化
数据监听器支持监听对象中单个或多个属性的变化
Component({
observers:{
'对象.属性A,对象.属性B':function(属使A的新值,属性B的新值){
//触发此监听器的3种情况:
//【为属性A值】使用setData 设置this.data.对象,属性A时触发
//【为属性B值】使用setData 设置this.data.对象,属性B时触发
//【直接为对象赋值】使用setData 设置 this.data.对象时触发
//do something.
})
(4)监听对象中指定属性的变化(在下面案例中)
案例五--自定义组件的数据监听器
// 引入test2组件
<my-test2></my-test2>
// test2.wxml 自定义组件
<view class="colorbox" style="background-color: rgba({{fullColor}});">颜色值:{{fullColor}}</view>
<view class="rgbbox">
<button bindtap="changeR" size="mini" type="default">R</button>
<button bindtap="changeG" size="mini" type="primary">G</button>
<button bindtap="changeB" size="mini" type="warn">B</button>
</view>
</view>
// test2.js文件
Component({
data: {
rgb:{
r:0,
g:0,
b:0
},
fullColor:'0,0,0'
},
methods: {
changeR(){
this.setData({
"rgb.r":this.data.rgb.r+5>255?255:this.data.rgb.r+5,
})
},
changeG(){
this.setData({
"rgb.g":this.data.rgb.g+5>255?255:this.data.rgb.g+5,
})
},
changeB(){
this.setData({
"rgb.b":this.data.rgb.b+5>255?255:this.data.rgb.b+5,
})
}
},
// 监听对象中指定属性的变化
observers:{
'rgb.r,rgb.g,rgb.b':function (r,g,b) {
this.setData({
fullColor:`${r},${g},${b}`
})
}
}
})
(5)监听对象中所有属性的变化
如果某个对象中需要被监听的属性太多,为了方便,可以使用通配符 ** 来监听对象中所有属性的变化。
observers:{
'rgb.**':function (obj) {
this.setData({
fullColor:`${obj.r},${obj.g},${obj.b}`
})
}
}
5.纯数据字段
(1)什么是纯数据字段
纯数据字段指的是那些不用于界面渲染的data字段。(案例五中的rgb{}就是纯数据字段)
应用场景:例如有些情况下,某些data中的字段既不会展示在界面上,也不会传递给其他组件,仅仅在当前组件内部使用。带有这种特性的data字段适合被设置为纯数据字段。
好处:纯数据字段有助于提升页面更新的性能。
(2)使用规则
在Component构造器的options节点中,指定pureDataPattern为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段。
Component({
options:{
//指定所有 _ 开头的数据字段为纯数据字段
pureDataPattern:/^_/
},
data: {
a:true, // 普通数据字段
_b:true // 纯数据字段
},
})
(3)使用纯数据字段改造数据监听器案例
Component({
options:{
//指定所有 _ 开头的数据字段为纯数据字段
pureDataPattern:/^_/
},
data: {
// 将所有的 rgb 改为 _rgb
_rgb:{
r:0,
g:0,
b:0
},
},
})
6.组件的生命周期
(1)组件全部的生命周期函数
(2)组件主要的生命周期函数
在小程序组件中,最重要的生命周期函数有3个,分别是created、attached、detached,它们的特点如下:
1.组件实例刚被创建好的时候,created 命周期函数会被触发
- 此时还不能调用 setData
- 通常在这个生命周期函数中,只应该用于给组件的this添加一些自定义的属性字段
2.在组件完全初始化完毕、进入页面节点树后,attached 命周期函数会被触发
- 此时,this.data 已被初始化完毕
- 这个生命周期很有用,绝大多数初始化的工作可以在这个时机进行(例如发请求获取初始数据)
3.在组件离开页面节点树后,detached 生命周期函数会被触发
- 退出一个页面时,会触发页面内每个自定义组件的detached 生命周期函数
- 此时适合做一些清理性质的工作
(3)lifetimes节点
在小程序组件中,生命周期函数可以直接定义在Component构造器的第一级参数中,可以在lifetimes字段内进行声明(这是推荐的方式,其优先级最高)。
Component({
// 推荐用法
lifetimes:{
attached(){},
detached(){}
},
// 旧式写法
attached(){},
detached(){}
})
7.组件所在页面的生命周期
(1)什么是组件所在页面的生命周期
有时,自定义组件的行为依赖于页面状态的变化,此时就需要用到组件所在页面的生命周期。
例如:每当触发页面的show生命周期函数的时候,希望重新生成一个随机的rgb颜色值。在自定义组件中,组件所在页面的生命周期函数有如下3个,分别是:
(2)pageLifetimes节点
组件所在页面的生命周期函数,需要定义在pageLifetimes节点中。
// 自定义组件的 .js文件
Component({
pageLifetimes:{
// 组件所在页面被展示时,生成随机颜色值
show:function () { //页面被展示
this._randomColor()
},
hide:function () {}, // 页面被隐藏
resize:function () {} // 页面尺寸变化
},
methods: {
// 非事件处理函数建议以 _ 开头
_randomColor(){
this.setData({
_rgb:{
r:Math.floor(Math.random()*256),
g:Math.floor(Math.random()*256),
b:Math.floor(Math.random()*256),
}
})
}
}
})
8.插槽
(1)什么是插槽
在自定义组件的wxml结构中,可以提供一个<slot>节点(插槽),用于承载组件使用者提供的wxml结构。
(2)单个插槽
在小程序中,默认每个自定义组件只允许使用一个<slot>进行占位,这种个数上的限制叫做单个插槽。
<!-- 组件的使用者 -->
<my-test3>
<!-- 这部分内容被放置在组件 <slot>的位置上 -->
<view>这是message插入到test组件slot中的内容</view>
</my-test3>
<!-- 组件的封装者 -->
<view>
<text>我是自定义组件的内部节点</text>
<!-- 对于不确定的内容,可以使用<slot>进行占位,具体内容由组件的使用者决定 -->
<slot></slot>
</view>
(3)启用多个插槽
在小程序的自定义组件中,需要使用多个<slot>插槽时,可以在组件的.js文件中,通过以下方式进行启用。
Component({
options:{
multipleSlots:true //在组件定义时的选项中启用多 slot 支持
}
})
(4)定义多个插槽
可以在组件的 .wxml中使用多个<slot>标签,以不同的name来区分不同的插槽。
<my-test3>
<view slot="before">插入到test组件slot name="before"中的内容</view>
<view slot="after">插入到test组件slot name="after"中的内容</view>
</my-test3>
<!-- 组件的封装者 -->
<view>
<slot name="before"></slot>
<slot name="after"></slot>
</view>
9.父子组件之间的通信
(1)父子组件之间通信的3种方式
① 属性绑定
- 用于父组件向子组件的指定属性设置数据,仅能设置JSON兼容的数据 ② 事件绑定
- 用于组件向父组件传递数据,可以传递任意数据 ③ 获取组件实例
- 父组件还可以通过this.selectComponent()获取子组件实例对象
- 这样就可以直接访问子组件的任意数据和方法
(2)属性绑定
属性绑定用于实现父向子传值,而且只能传递普通类型的数据,无法将方法传递给子组件。
子组件在properties节点中声明对应的属性并使用。(示例在下面(3)中)
(3)事件绑定
事件绑定用于实现子向父传值,可以传递任何类型的数据。使用步骤如下:
① 在父组件的js中,定义一个函数,这个函数即将通过自定义事件的形式,传递给子组件
② 在父组件的wxml中,通过自定义组件的形式,将步骤1中定义的函数引用,传递给子组件
③ 在子组件的js中,通过调用this.triggerEvent('',{/* 参数对象 */}),将数据发送到父组件
④ 在父组件的js中,通过e.detail获取到子组件传递过来的数据
父组件:
<!-- 使用 bind:自定义事件名称(推荐:结构清晰) -->
<my-test4 count='{{count}}' bind:addCount='addCount'></my-test4>
<!-- 使用 bind 后面直接写上自定义事件名称 -->
<!-- <my-test4 count='{{count}}' bindaddCount='addCount'></my-test4> -->
Page({
data: {
count:1,
},
addCount(e){;
this.setData({
count:e.detail.value
})
}
})
子组件:
<view>父组件传过来的count:{{count}}</view>
<button type="primary" bindtap="countadd">count+1</button>
Component({
properties: {
count:Number
},
methods: {
countadd(){
this.setData({
count:this.properties.count+1
})
this.triggerEvent('addCount',{
value:this.properties.count
})
}
}
})
(4)获取组件实例
可在父组件里调用this.selectComponent("id或class选择器"),获取子组件的实例对象,从而直接访问子组件的任意数据和方法。调用时需要传入一个选择器,例如 this.selectComponent(".my-component")。
getChild(){
// 切记下面参数不能传递标签选择器'my-test4',不然返回的是null
const child = this.selectComponent('.child')
child.setData({
count:child.properties.count+1
})
},
10.behaviors
(1)什么是behaviors
behaviors是小程序中,用于实现组件间代码共享的特性,类似于Vue.js中的“mixins”。
(2)behaviors的工作方式
每个behavior可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中。
每个组件可以引用多个 behavior,behavior 可以引用其它 behavior.
(3)创建behavior
调用Behavior(Object object)方法即可创建一个共享的behavior实例对象,供所有的组件使用:
(4)导入并使用behavior
(5)behavior中所有可用的节点
(6)同名字段的覆盖和组合规则
developers.weixin.qq.com/miniprogram…
十一、使用npm包
小程序对npm的支持与限制:
1.什么是Vant Weapp
2.安装Vant组件库
(youzan.github.io/vant-weapp/…])
3.使用Vant组件
4.定制全局主题样式
5.API Promise化
(1)基于回调函数的异步API的缺点
默认情况下,小程序官方提供的异步API都是基于回调函数实现的,例如,网络请求的API需要按照如下的方式调用:
(2)什么是API Promise化
API Promise化,指的是通过额外的配置,将官方提供的、基于回调函数的异步API,升级改造为基于Promise的异步API,从而提高代码的可读性、维护性,避免回调地狱的问题。
(3)实现API Promise化
在小程序中,实现API Promise 主要依赖于 miniprogram-api-promise 这个第三方的npm包。它的安装和使用步骤如下:
npm install --save miniprogram-api-promise@1.0.4
// 在小程序入口文件中(app.js),只需调用一次 promisifyAll()方法
// 即可实现异步 API 的 Promise化
import { promisifyAll } from 'miniprogram-api-promise'
const wxp = wx.p = { }
promisifyAll(wx, wxp)
(4)调用Promise化之后的异步API
<van-button type="primary" bindtap="getInfo">主要按钮</van-button>
async getInfo(){
const {data:res} = await wx.p.request({
method:'GET',
url:'https://www.escook.cn/api/get',
data:{name:'zx',age:20}
})
console.log(res);
},
十二、全局数据共享
1.什么是全局数据共享
全局数据共享(又叫做:状态管理)是为了解决组件之间数据共享的问题。
开发中常用的全局数据共享方案有:Vuex、Redux、MoboX等。
2.小程序中的全局数据共享方案
在小程序中,可使用mobx-miniprogram配合mobx-miniprogram-bindings实现全局数据共享。其中:
- mobx-miniprogram 用来创建Store实例对象
- mobx-miniprogram-bindings 用来把Store中的共享数据或方法,绑定到组件或页面中使用。
3.Mobx
(1)安装 MobX 相关的包
在小程序项目中,可以通过 npm 的方式引入 MobX 。如果还没有在小程序中使用过 npm ,先在小程序目录中执行命令:
npm init -y
引入 MobX :
npm install --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1
注意:MobX 相关的包安装完毕之后,记得 删除 miniprogram_npm 目录后,重新构建 npm。
(2)创建 MobX 的 Store实例
在项目文件夹下创建store文件夹,再在文件夹下创建store.js文件:
// 在这个js文件中,专门创建 store的实例对象
import {action, observable} from 'mobx-miniprogram'
export const store =observable({
numA:1,
numB:2,
// 计算属性,get的意思是只读
get Sum(){
return this.numA + this.numB
},
// action 方法,用来修改 store 中的数据
updateNum1:action(function (step) {
this.numA += step
})
})
(3)将Store中的成员绑定到页面中
(4)在页面上使用 Store中的成员
<van-button type='primary' bindtap="handleNum" data-step="{{1}}">numA + 1</van-button>
<van-button type='warning' bindtap="handleNum" data-step="{{-1}}">numA - 1</van-button>
import {createStoreBindings} from 'mobx-miniprogram-bindings'
import {store} from '../../store/store'
Page({
onLoad() {
this.storeBindings = createStoreBindings(this,{
store,
fields:['numA','numB','sum'],
actions:['updateNum1']
})
},
onUnload:function () {
this.storeBindings.destroyStoreBindings()
},
handleNum(e){
// 直接调用方法即可
this.updateNum1(e.target.dataset.step)
},
})
(5)将Store中的成员绑定到组件中
import { storeBindingsBchavior } from 'mobx-miniprogram-bindings'
import { store } from '././store/store'
Component({
behaviors:[ storeBindingsBchavior ],//通过storeBindingsBchavior 来实现自动绑定
storeBindings:{
//指定要绑定的Store
store,
fields:{ //指定要绑定的字段数据
numA:()=>store.numA, //绑定字段的第1种方式
numB:(store)=>store.numB, //绑定字段的第2种方式
sum:'sum' //绑定字段的第3种方式
},
actions:{ //指定要绑定的方法
updateNum2:'updateNum2'
}
}
})
十三、分包
1.什么是分包
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
2.分包的好处
对小程序进行分包的好处主要有以下两点:
- 可以优化小程序首次启动的下载时间
- 在多团队共同开发时可以更好的解耦操作
3.分包前项目的构成
分包前,小程序项目中所有的页面和资源都被打包到了一起,导致整个项目体积过大,影响小程序首次启动的下载时间。
4.分包后项目的构成
5.分包的加载规则
6.分包的体积限制
7.使用分包
(1)配置方法
(2)打包原则
(3)引用原则
①主包无法引用分包内的私有资源
②分包之间不能相互引用私有资源
③分包可以引用主包内的公共资源
8.独立分包
(1)什么是独立分包
(2)独立分包和普通分包的区别
最主要的区别:是否依赖于主包才能运行。
- 普通分包必须依赖于主包才能运行
- 独立分包可以在不下载主包的情况下,独立运行
(3)独立分包的应用场景
(4)独立分包的配置方法
(5)引用原则
9.分包预下载
(1)什么是分包预下载
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。
(2)配置分包的预下载
预下载分包的行为,会在进入指定的页面时触发。在app.json中,使用preloadRule节点定义分包的预下载规则。实例代码如下:
{
preloadRule": { // 分包预下载的规则
"pages/contact/contact": { // 触发分包预下的页面路径
// network 表示在指定的网络模式下进行预下载,
// 可选值为: all(不限网络) 和 wifi(仅 wifi 式下进行预下载)
// 默认值为: wifi
"network":"all",
// packages 表示进入页面后,预下载哪些分包
// 可以通过 root 或 name 指定预下哪些分包
"packages":["pkgA"]
}
}
}
(3)分包预下载的限制
案例六--自定义tabBar
网址:developers.weixin.qq.com/miniprogram…
注意:custom-tab-bar/index.js 创建的是 组件Component!
跟着视频没写出来,去b站找了详细的视频,实现了效果。
至此,小程序的理论课程结束了。
配套的uniapp项目:
开发文档:www.escook.cn/docs-uni-sh…
项目源码地址:gitee.com/qiuyan-yin/…