微信小程序使用技巧

821 阅读12分钟

使用微信开发者工具开发小程序,请参考官方文档:微信开放文档 (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组件

(文档:框架接口 / 自定义组件 / Component (qq.com))

自定义组件,与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生命周期函数的注意事项

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) 处输入 yesimage.png

步骤三:正式构建npm

在微信开发者 菜单栏--工具--构建npm

image.png

步骤四:下载需要的依赖包

image.png

网络加载提示

页面栈产生的意外问题

1.switchTab与页面栈冲突

wx.switchTab 跳转到tab页面时会先闪一下第一个tab页面(首页)在跳转到指定的页面?

在某个非tabBar页面中调用switchTab后,不会直接跳转到该tabBar页面;而是会先回到页面栈的上一页,然后再切换到该tabBar; 解决方案:使用wx.reLaunchwx.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。

页面栈

小程序页面栈详解 - 掘金 (juejin.cn)

页面栈展示了小程序当前的渲染图层,使用navigateTo跳转是开启一个新的页面线程,而不是关闭之前的页面,所以前一个页面放入到了页面栈中,通过 getCurrentPages() 可以获取当前页面栈的信息。页面栈是一个数组,每个页面都是一个数组元素

var pages = getCurrentPages(); //获取当前页面栈
console.log(pages); // 打印当前页面栈信息
console.log("页面堆栈长度:"+pages.length); // 打印页面栈的长度

打印结果👇 【如果你打开页面的顺序为A->B->C(入栈),那么页面栈的元素顺序则相反(出栈)。】 image.png

事件总结

Tap 事件表单组件

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属性值
    },
})
e28a65dee079407d61561110c85bfd9.png *(1)data- --> 事件对象(e).currenttarget.dataset. (模板中的中划线对应脚本驼峰命名法)*

(2)value --> 事件对象(e).detail

(3)id --> 事件对象(e).currentTarget.id

动画效果 - Animation

避免 安卓点击'复制验证码'弹窗 导致页面input数据丢失

(案例1)安卓手机,复制短信验证码,小程序闪退 | 微信开放社区 (qq.com)

(案例2)安卓手机点击复制短信验证码弹窗导致小程序闪退

事件:部分安卓手机点击复制短信验证码弹窗会导致小程序重新加载,之前输入的内容都会被清空

原因:在安卓手机推送验证码弹窗时,一旦点击"复制验证码",则小程序会被认定为切入后台操作,点击后弹窗离开后,小程序会认定为切回前台操作;切入后台会执行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 //状态标识符
  },

定时器与延时

定时器 setInterval

定时器函数可以按照规定的时间间隔内反复执行回调函数,并且可以停止执行定时器函数;倒计时、防抖中应用较多

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)
    }
})

获取DOM信息