使用微信开发者工具开发小程序,请参考官方文档:微信开放文档 (qq.com)
窗口的全局配置与局部配置
全局配置文件app.json;页面配置为当前page包下的xxx.json,它包含了对窗口、顶部navigationBar、底部tab、引入组件的配置,app.json生效范围为全小程序,xxx.json仅对当前包下的page生效;两个配置文件中有相同项优先使用xxx.json。
1.页面配置--windows(默认窗口表现)
(1)页面配置在全局配置下要在"windows"对象中完成;
[app.json]
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "导航栏"
},
(2)页面配置在单一page的配置下,与全局配置不同的是,不需要额外指定 window 字段;
[xxx.json]
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "导航栏"
Component组件
自定义组件,与Page实例同级别,由模板、样式、脚本、配置四大模块组成;
(1)和Page的区别
- 脚本中除data外还有properties属性(组件的对外属性,是属性名到属性设置的映射表);
- 生命周期函数的命名规范不同(attached、moved、detached);
- 所有的自定义方法声明于'methods'中;
- 在json文件中需要声明'component': true,表示是一个组件
Component({
behaviors: [],
// 属性定义(详情参见下文)
properties: {
myProperty: { // 属性名
type: String,
value: ''
},
myProperty2: String // 简化的定义方式
},
data: {}, // 私有数据,可用于模板渲染
lifetimes: {
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { },
moved: function () { },
detached: function () { },
},
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖
ready: function() { },
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
hide: function () { },
resize: function () { },
},
methods: {
onMyButtonTap: function(){
this.setData({
// 更新属性和数据的方法与更新页面数据的方法类似
})
},
// 内部方法建议以下划线开头
_myPrivateMethod: function(){
// 这里将 data.A[0].B 设为 'myPrivateData'
this.setData({
'A[0].B': 'myPrivateData'
})
},
_propertyChange: function(newVal, oldVal) {
}
}
})
Page生命周期函数的注意事项
1.tabBar页的onLoad和onReady只在页面第一次加载时执行;
在微信小程序中,onLoad 生命周期函数只在页面加载时执行一次。这是因为 onLoad 的设计初衷是为了处理页面加载时需要执行的初始化操作,比如从服务器获取数据、设置页面的初始状态等。一旦页面加载完成并且 onLoad 函数执行完毕,该页面就进入了“已加载”状态,之后即使页面内容因为某些操作(如用户交互)而发生变化,onLoad 也不会再次被触发。
(待证实)这意味着如果你在onLoad函数中发请求获取数据,之后再切出去重新进入这个页面时,你的请求数据不会更新;所以tabBar页尽量不要携带任何会变化的数据。
构建npm及引入外部依赖(方式一)
微信小程序工程需要构建npm来引入外部依赖(组件、脚本)
第一步:修改 project.config.json 文件的setting属性
"packNpmManually": true
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
],
第二步:创建package.json文件,并对其进行配置
{
"name": "lc",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"tdesign-miniprogram": "^1.1.0"
}
}
第三步:正式构建npm
菜单栏 -- 工具 -- 构建npm
第四步:打开终端引入外部依赖
引入Vant组件:pm i @vant/weapp -S --production
构建npm及引入外部依赖(方式二)
参考文档:微信小程序构建npm包 -- CSDN
如果你的代码来自github或是开源社区,那么你的项目代码目录很有可能是这样的:
项目根目录 ├── app.js // 小程序入口逻辑文件 ├── app.json // 小程序全局配置文件 ├── app.wxss // 小程序全局样式文件 ├── project.config.json // 项目配置文件(工具配置) ├── sitemap.json // 小程序页面收录配置(SEO相关) ├── README.md // 小程序文档 ├── pages/ // 页面目录(存放所有页面) │ └── index/ // 首页目录 │ ├── index.js // 首页逻辑 │ ├── index.json // 首页配置(可选) │ ├── index.wxml // 首页结构(类似HTML) │ └── index.wxss // 首页样式(类似CSS) └── utils/ // 工具函数目录(默认空)这种情况下,目录中不存在package.json,也就是不存在包管理工具npm,如果我们想让项目可以使用终端下载依赖包,则我们需要进行以下步骤来构建npm
步骤一:检查是否本机是否安装了npm与node环境
步骤二:初始化包管理工具
输入指令 npm init,后按步骤依次输入version、description、entry point、test command、git repository、keywords、author、license;最后在 Is this OK? (yes) 处输入 yes 。
步骤三:正式构建npm
在微信开发者 菜单栏--工具--构建npm
步骤四:下载需要的依赖包
网络加载提示
页面栈产生的意外问题
1.switchTab与页面栈冲突
wx.switchTab 跳转到tab页面时会先闪一下第一个tab页面(首页)在跳转到指定的页面?
在某个非tabBar页面中调用switchTab后,不会直接跳转到该tabBar页面;而是会先回到页面栈的上一页,然后再切换到该tabBar;
解决方案:使用wx.reLaunch。
wx.reLaunch函数可以关闭所有非 tabBar 页面,并跳转到应用内的某个 tabBar 页面。虽然这并不会直接清空整个页面栈(因为它保留了 tabBar 页面),但在很多情况下,你可以利用这一特性来达到“重新开始”应用的效果。
处理异步操作的解决方案
1.Promise联合async/await
return new Promise((resolve,reject)=>{~ 发送网络请求返回res/err参数 ~}可以使网络请求返回数据为Promise对象;
await可以接收Promise对象并解析为后端数据;
async可以使函数按照同步方式顺序执行代码;
如果不在this.getIntegral()返回Promise而是直接返回请求的res或error,await无法解析。
async onLoad(options) {
console.log("调用初始化函数")
try{
const data = await this.getIntegral();
/*
* 2.await解析Promise对象,resolve使其成为后端数据,reject使其成为error
*/
this.setData({
integral:data
})
}catch(error){
console.log(error)
}
},
getIntegral(){
var that = this
return new Promise((resolve,reject)=>{
/*
* 1.返回Promise对象,发送请求后会执行success或fail,succes返回resolve,fail返回error
*/
wx.request({
url: 'http://localhost:8080/user/getIntegral',
header:{
token:TOKEN.token
},
method:'GET',
data:{
userId:USER_INFO.id
},
success(res){
const response = that.handleResponse(res)
if(response != null){
resolve(response.data);
}else{
console.log("用户积分更新失败!")
}
},
fail(err){
console.log(err)
reject(error)
}
})
})
},
Component导入外部样式
让自定义组件或vant组件在页面中应用可以在原本样式上重新覆盖同名的新样式。 当你的复用组件在components中就已经封装了对其的样式,但是你把他应用到页面中又对其定位、位置不太满意,如何修改? -- 导入外部样式,让外部样式覆盖原本样式。
1.组件样式 -- 正常填写需要的全局样式即可👇
.illustration{
position: fixed;
right: 0px;
bottom: -35vh;
width: 100%;
height: 118%;
z-index: -998;
}
2.组件脚本 -- 引入外部样式,起名为custom-class👇
Component({
externalClasses: ['custom-class']
})
3.组件模板 -- 引入custom-class作为外部样式👇
<image src="../../images/illustration.png" mode="aspectFit" class="illustration custom-class"></image>
4.页面模板 -- 为custom-class外部样式命名为"login-illustration 👇"
<illustration custom-class="login-illustration"/>
5.页面样式 -- 为login-illustration定义样式👇
*注意:如果此样式中有和组件原样式冲突的,需要!importint才能覆盖原样式,让我们的外部样式生效
.login-illustration{
height: 100% !important;
}
父传子
即页面传递数据给组件;将页面data域中的数据,传递给组件的js脚本,给组件使用;可以实现不同页面访问组件,组件可以获取不同的数据,再用这个数据对当前页面进行更加定制化的操作。
案例:演示页面route变量的值,传递到组件中currentPageRoute属性;页面端 route --> 组件端 currentPageRoute
1.页面脚本👇 -- 为变量route赋值
data: {
route:""
},
onLoad(options) {
var that = this
console.log(this.route)
that.setData({
route:this.route
})
},
2.页面模板👇 -- 在页面中的组件标签中,声明currentPageRoute为需要传递到子组件的值,值为来自data域的route
<子组件名 currentPageRoute="{{route}}"/>
3.组件脚本👇 -- 在properties属性中可以获取页面传递来的currentPageRoute值,完成了整个父传子的过程
properties: {
currentPageRoute:'没获取到路由信息'
},
methods:{
test1(){
console.log(this.properties.currentPageRoute) //方法中可以打印到页面传来的数据
}
}
注意,当Component的模板应用的文本插值,在data域和properties同名时,优先使用properties,当properties为空时会使用data。
页面栈
页面栈展示了小程序当前的渲染图层,使用navigateTo跳转是开启一个新的页面线程,而不是关闭之前的页面,所以前一个页面放入到了页面栈中,通过 getCurrentPages() 可以获取当前页面栈的信息。页面栈是一个数组,每个页面都是一个数组元素
var pages = getCurrentPages(); //获取当前页面栈
console.log(pages); // 打印当前页面栈信息
console.log("页面堆栈长度:"+pages.length); // 打印页面栈的长度
打印结果👇 【如果你打开页面的顺序为A->B->C(入栈),那么页面栈的元素顺序则相反(出栈)。】
事件总结
bindtap / bind:tap:
- 作用:
bindtap用于处理点击事件。当用户点击绑定了bindtap的组件时,会触发该事件处理函数,执行相应的逻辑。 - 特点:
- 通常用于处理组件自身的点击事件,如按钮、链接等。
- 事件会按照从子元素向父元素冒泡的顺序传递,即如果子元素和父元素都绑定了
bindtap,子元素的点击事件会先被触发,然后冒泡到父元素。 - 可以通过返回
false来阻止事件的进一步冒泡和默认行为。
catchtap:
bindsubmit:
bindchange:
事件对象e(event)
一个点击事件发生后,事件函数接收的参数,一般声明为'e'或者'event';包含了触发事件时的详细信息。当用户在界面上进行操作时(如点击、输入等),会触发相应的事件,并生成一个事件对象,该对象包含了事件的所有相关信息,如事件类型、触发事件的组件、触发时间等。
下图为一个事件对象在控制台中的解析案例👇
//定义一个input输入框,表单值与data域的username绑定,值改变会触发onChangeUsename监听
<input value="{{username}}" id="10001" data-my-data="Hello World!" bindchange:onChangeUsername/>
Page({
data:{
username:""
},
onChangephoneNumber(event){
console.log(event) //事件对象
console.log(event.detail) // input的value值
console.log(event.currentTarget.dataset.myData) //data-绑定的值
console.log(event.currentTarget.id) id属性值
},
})
(2)value --> 事件对象(e).detail
(3)id --> 事件对象(e).currentTarget.id
动画效果 - Animation
避免 安卓点击'复制验证码'弹窗 导致页面input数据丢失
(案例1)安卓手机,复制短信验证码,小程序闪退 | 微信开放社区 (qq.com)
事件:部分安卓手机点击复制短信验证码弹窗会导致小程序重新加载,之前输入的内容都会被清空
原因:在安卓手机推送验证码弹窗时,一旦点击"复制验证码",则小程序会被认定为切入后台操作,点击后弹窗离开后,小程序会认定为切回前台操作;切入后台会执行onHide函数,切回前台会重新渲染页面,并执行onShow/onload等生命周期关键函数
解决方法:将input数据保存在全局数据中,而不是data域;在onHide()函数中定义一个全局变量(true/false)作为标识符来判断当前onLoad/onShow是正常渲染,还是切回前台渲染;
1.在全局数据中,将不想被清空的数据作为变量声明到app.js的globalData中;(例如手机号、验证码等等)
2.在用户输入手机号验证码时,编写函数,做到用户输入的字符可以同步到全局数据中,方便清空后去调取回来;
3.在全局数据中,声明一个标识变量'isToBackend',判断当前页面是否为从后台切回渲染的,还是正常渲染的。在onLoad函数判断此变量,来分别编写后台切回、正常渲染的业务逻辑。
//初始化函数,切回前台会执行👇
onLoad(options) {
//判断这次onLoad执行是否为切回前台发生
if(
(getApp().globalData.isToBackend == true) //是不是 切入过后台现在返回前台
&&
(getApp().globalData.input != null) //而且input框不是空的,空的执行没意义
){
this.setData({
phoneNumber:getApp().globalData.inputNumber, //从全局数据中调取切后台之前输入的手机号
captchaCode:getApp().globalData.captchaCode //从全局数据中调取切后台之前输入的验证码
})
getApp().globalData.isToBackend = false; //切入切出的标识符复位
console.log(getApp().globalData)
}else{
//正常非切回情况下的初始化函数
}
console.log("login页")
},
//将用户输入的手机号保存到全局数据中👇
onChangephoneNumber(){
getApp().globalData.inputNumber = 用户输入的手机号
},
//将用户输入的验证码保存到全局数据中👇
onChangephoneNumber(){
getApp().globalData.captchaCode = 用户输入的验证码
},
//切入后台(点击验证码弹窗或离开小程序会触发)👇
onHide(){
getApp().globalData.isToBackend = true //标识符,初始值为false,一旦触发改为true,然后切回是onLoad首先检验它为false还是true,是true则说明是切回来的
}
app.js中的全局数据👇
globalData:{
inputNumber:"", //用户输入的手机号
CaptchaCode:"", //用户输入的验证码
isToBackend:false //状态标识符
},
定时器与延时
定时器函数可以按照规定的时间间隔内反复执行回调函数,并且可以停止执行定时器函数;倒计时、防抖中应用较多
1、定时器函数 setInterval/clearInterval
number setInterval(function callback,number delayTime)
clearInterval(number intervalID)
参数
function callback 回调函数
number delay 执行回调函数之间的时间间隔,单位 ms。
number intervalID 要取消的定时器的变量
返回值
number 定时器的编号。这个值可以传递给 clearInterval 来取消该定时。
项目中应用
1.在data域定义一个清除定时变量,赋值为空即可;在js中如果想停止setIterval函数的执行,只需要在clearInterval中输入此变量即可
data:{
setIntervalID:"", //设置定时器变量
}
------------
setInterval(...) //设置定时器函数
clearInterval(this.data.setIntervalID) //清除定时器
2.案例:倒计时60秒,60秒后清除定时
Page({
data:{
startTime:6,
endTime:0,
intervalId:""
},
onLoad(){
var that = this
that.count(that.data.startTime,that.data.endTime);
},
//倒计时从60到0,每秒打印到控制台
count(startTime,endTime){
var that = this
that.data.intervalId = setInterval(function(){
console.log(startTime);
if(startTime <= endTime){
console.log("倒计时结束")
clearInterval(that.data.intervalId)
}else{
that.setData({
startTime: startTime--
})
}
},1000)
}
})