【小程序基础】小程序基础进阶部分学习笔记

141 阅读4分钟

小程序进阶

小程序路由跳转

路由跳转的三种方式:

  • navigate:跳转到普通页面,保留历史记录(默认)
  • redirect:跳转到普通页面,不保留历史记录
  • switchTab:跳转到 tabBar 页面,不保留历史记录

页面跳转时,可以通过 ? 来拼接参数,在新页面 onLoad(options) 生命周期函数中获取

当跳转方式为 switchTab 时,不能使用 queryString 传参。解决:本地存储

eg.html跳转示例

<navigator open-type="navigate" url="/pages/xxx/xxx">xxx</navigator>
<navigator open-type="redirect" url="/pages/xxx/xxx">xxx</navigator>
<navigator open-type="switchTab" url="/pages/xxx/xxx">tabBar页</navigator>

eg.js跳转示例

// 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
// 使用 wx.navigateBack 可以返回到原页面
wx.navigateTo({
    url: '/pages/xxx/xxx'
});
// 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
wx.redirectTo({
    url: '/pages/xxx/xxx'
});
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
wx.switchTab({
    url: '/pages/xxx/xxx'
});

小程序npm包管理

在小程序中使用npm包的步骤:

  1. 初始化 package.json 文件 npm init -y
  2. 安装三方包 npm i xxx
  3. 重新构建npm【重要】
  4. 使用第三方包

小程序不支持的第三方包:

  • 依赖于浏览器内置对象的包(如 axios、jquery)

  • 依赖于 Node.js 内置库的包

  • 依赖于 C++ 插件的包

@npm包导入错误

image-20231117203404529.png

原因:npm 安装的第三方包,必须由小程序开发者工具构建后才可以使用

解决:每次安装完新的三方包都重新构建一次

小程序数据共享

getAPP()

获取应用实例,实现全局数据和方法的共享

// app.js
App({
  state: {
    // 读取本地存储的token数据,保存到全局
  	token: wx.getStorageSync('xxx') || ''
  }
})
// pages/index/index.js
// 获取应用实例
const app = getApp()
Page({
  onLoad() {
    // 根据应用实例获取全局数据/方法
    console.log(app.state.token)
  },
})

getCurrentPages()

获取当前页面栈,页面栈 中包含的是 页面的实例,数组中第一个元素为首页,最后一个元素为当前页面

获取到页面栈后根据数组的索引值可以获取到 页面实例,通过页面实例可以获取页面中的 路由信息

// 未登录重定向到登录页,并且登录成功后回到之前的页面
if (!isLogin) {
    // 读取当前历史记录栈
	const pageStack = getCurrentPages()
    // 取出当前页面路径,以便登录成功后返回该页面
    const currentPage = pageStack[pageStack.length - 1]
    const redirectURL = currentPage.route
    // 重定向到登录页面
    wx.redirectTo({
    	url: `/pages/login/index?redirectURL=/${redirectURL}`,
    })
}

内置对象wx

wx 是小程序中的一个内置对象,提供了一些全局API,方便开发

// src/utils/utils.js
// 封装 wx.showToast
const utils = {
  toast(title = '数据加载失败...') {
    wx.showToast({
      title,
      mask: true,
      icon: 'none',
    })
  },
}
// 
// 扩展 wx 全局对象,以在任何页面访问(不要与原有api重名,如 wx.request)
wx.utils = utils
// app.js
// 执行utils.js,使之生效
import './utils/utils'
App({
  globalData: {},
})

组件behaviors

behaviors 字段可以为页面/组件混入可复用的数据和方法

每个 behavior 可以包含一组属性、数据、生命周期函数和方法。引用时,behavior 中的属性、数据和方法会被合并到页面/组件实例中

// 1.通过Behavior函数定义混入的数据或方法
export const mixin = Behavior({
  data: {
    share: '我是共享数据'
  },
  methods: {
    getShare() {
      console.log('获取共享数据:', this.data.share)
    }
  }
})
// 2.页面中通过behaviors选项引入使用
import { mixin } from '../../utils/mixin'
Page({
  behaviors: [mixin],
  onLoad() {
      console.log(this)
  }
})

微信小程序学习笔记-behaviors

网络请求

小程序内置API?

通过全局对象 wx 调用,实现网络请求、消息提示、本地存储、微信登录、微信支付等

小程序中大部分的 API 都是异步方式执行,异步 API 传入的都是对象类型的参数,且都可以传入 successfailcomplete 回调函数。也有少部分 API 是同步方式执行,同步方式的 API 有个特点就是均以 Sync 结尾。

image-20231117170807369.png

eg.页面初始化渲染

Page({
  data: {
    list: []
  },
  onLoad(options) {
    this.getList()
  },
  // 获取列表数据
  getList() {
    // 显示loading
    wx.showLoading({
      title: '加载中...',
      mask: true,
    })
    // 发起网络请求
    wx.request({
      url: 'https://mock.boxuegu.com/mock/3293/students',
      method: 'GET',
      data: {},
      success: (res) => {
        // 修改数据
        this.setData({
          list: res.data.result
        })
        // 提示
        wx.showToast({
          title: '获取数据成功!',
          icon: "success"
        })
      },
      complete: () => {
        // 隐藏loading
        wx.hideLoading()
      }
    })
  }
})

本地存储

同步本地存储(使用方便)

  • wx.setStorageSync(key,data) 在本地存入一个数据

  • wx.getStorageSync(key) 读取本地的一个数据

  • wx.removeStorageSync(key) 删除本地存储的一个数据

  • wx.clearStorageSync() 清空本地存储的数据

异步本地存储(不阻塞代码,执行效率较高)

  • wx.setStorage({key,data}) 在本地存入一个数据

  • wx.getStorage({key}) 读取本地的一个数据

  • wx.removeStorage({key}) 删除本地存储的一个数据

  • wx.clearStorage() 清空本地存储的数据

eg.本地存储示例

Page({
  // 存入本地数据
  setStorage() {
    wx.setStorageSync('user', { name: '小明', age: 18 })
  },
  // 读取本地数据
  getStorage() {
    const user = wx.getStorageSync('user')
  },
  // 删除数据
  removeStorage() {
    wx.removeStorageSync('user')
  },
  // 清空数据
  clearStorage() {
    wx.clearStorageSync()
  },
})

在小程序中,本地存储复杂类型数据不需要先进行JSON序列化

获取用户头像&昵称

获取用户头像需要借助 button 组件:

<view class="profile">
  <!-- 显示头像 --> 
  <image src="{{ profile.avatarUrl }}"></image>
  <!-- 选择头像 -->
  <button open-type="chooseAvatar" bindchooseavatar="getUserAvatar">
    点击选择头像
  </button>
</view>
getUserAvatar(e) {
    // 上传临时头像到自己的服务器
    wx.uploadFile({
      url: 'http://ajax-api.itheima.net/api/file',
      filePath: e.detail.avatarUrl,
      name: 'avatar',
      success: (res) => {
        console.log(res);
      },
    });
     // 修改页面上的头像
    this.setData({
      'profile.avatarUrl': e.detail.avatarUrl,
    });
},

获取用户昵称需要借助 input 组件:

<view>
    <!-- 显示昵称 -->
    <text>{{ profile.nickName }}</text>
    <!-- 点击获取微信昵称 -->
	<input type="nickname" bind:blur="getUserNickName"
	/>
</view>
getUserNickName(e) {
    // (可选) 调用接口将昵称保存在服务器
    // 修改页面上的用户昵称
    this.setData({
      'profile.nickName': e.detail.value,
	})
},

自定义源代码根目录

小程序中源代码默认放在项目根目录下,和配置文件混在一起,不方便维护

我们可以:

  1. 将源代码统一放入 src 目录下,如下图

image-20230109162334252.png

  1. 配置 源代码根目录位置 以及 构建npm生成目录位置
// project.config.json
{
  "miniprogramRoot": "src/",
  "setting": {
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./src"
      }
    ]
  }
}

配置后,源代码中的 / 代表的不再是项目根目录,而是 src 目录

分包&预加载

小程序对包大小的限制:单个包大小不能超过 2M;项目所有包大小总和不能超过 20M

分包:在 app.json 文件通过 subPackages 选项配置要加载的分包,将某些功能相关的页面及其依赖的资源放在独立的文件夹中

分包的好处:

  • 实现页面的按需加载,提升性能
  • 解决小程序代码包大小不能超过 2M 的限制

小程序启动的时候只下载主包中的代码,只有在访问到分包中的页面时才会下载分包中的代码,通过 preloadRule 配置需要预加载的分包,可以提升分包的访问速度

tabBar 页面只能放在主包当中,这是一个规定

eg.分包和使用

image-20231119182030542.png

// app.json
// subPackages中的页面如果不存在,会自动创建
{
    "pages": [],
    "subPackages": [{
        "root": "pack_one",
        "name": "pack_one",
        "pages": [
          "pages/list/list",
          "pages/detail/detail"
        ]
    }],
    "preloadRule": {
        // 在访问首页时,预加载pack_one分包中的页面
        "pages/index/index": {
            "network": "wifi",
            "packages": ["pack_one"]
        }
    },
}
<!-- 访问分包下的页面和资源 -->
<navigator url="/pack_one/pages/list/list">详情页</navigator>
<image src="/pack_one/assets/d.jpg"/>

自定义组件

组件的创建&注册&使用

组件和页面的区别

相同点:都由 .js/.json/.wxml/.wxss 四个文件组成

不同点:

  • 组件的 .json 文件中需要声明 "component": true 属性

  • 组件调用 Component() 函数进行初始化

  • 组件的方法需要定义到 methods

  • 组件有自己的生命周期函数

创建自定义组件

  1. 在源代码根目录下新建 components 文件夹

  2. 在 components 文件夹下新建 xxx 文件夹

  3. 在 xxx 文件夹上右击,选择新建组件

注册&使用组件

// app.json 全局注册,可以在任何页面中使用
// xxx.json 局部注册,只能在当前页面使用
{
  "usingComponents": {
    "my-test": "/components/test/test"
  }
}
<my-test></my-test>

小程序出现:Component is not found in path “components/xxx/xxx”问题

组件的数据&方法

Component 函数中的基础选项有:

  • data:组件内部的数据,可读写
  • properties:组件接收的数据,可读写
  • methods:组件内部的方法

data 和 properties 在使用上没有任何区别,它们只是一个对象的两种叫法

eg.组件数据和方法示例

<!-- 使用my-test组件 -->
<my-test max="10"></my-test>
<!-- my-test组件 -->
<view>
	<view style="padding: 10px;">
      count的值是:{{count}}
    </view>
    <view style="padding: 10px;">
      最大值不超过:{{max}}
    </view>
    <button bind:tap="addCount" type="primary">count++</button>
</view>
// components/test/test.js
Component({  
  lifetimes: {
    attached: function () {
      // data 和 properties 在使用上没有任何区别,它们只是一个对象的两种叫法
      console.log(this.data) // {count: 1, max: 10}
      console.log(this.properties) // {count: 1, max: 10}
      console.log(this.data === this.properties) // true
    }
  },
  properties: {
    // 1.简写形式
    // max: Number
    // 2.完整写法(带默认值)
    max: {
      type: Number,
      value: 100
    }
  },
  data: {
    count: 1
  },
  methods: {
    // count++
    addCount() {
      if (this.data.count >= this.properties.max) return
      this.setData({
        // 修改properties
        // max: this.properties.max + 1
        // 修改data
        count: this.data.count + 1,
      })
      this._showCount()
    },
    // 显示count最新值
    _showCount() {
      wx.showToast({
        title: 'count:' + this.data.count,
        icon: 'none'
      })
    }
  }
})

wx-comp-count.gif

组件中的纯数据字段

纯数据字段:不用于界面渲染、也不会传递给其他组件的 data 字段。使用纯数据字段可以提升页面更新的性能

在 Component 构造器的 options 节点中,指定 pureDataPattern 为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段

Component({
  // 以_开头的字段将成为纯数据字段
  options: {  pureDataPattern: /^_/  },
  data: { 
      // _aaa仅在当前js文件中使用,是一个纯数据字段
      _aaa: true  
  }
})

组件的监听器

observers 可以监听响应式数据的变化,然后执行一些操作

eg.求两数之和

<view>
  <view style="text-align: center;padding: 10px;">{{n1}}+{{n2}}={{sum}}</view>
  <button bind:tap="addN1" type="primary">n1 + 1</button>
  <button bind:tap="addN2">n2 + 1</button>
</view>
Component({
  properties: {},
  data: {
    n1: 0,
    n2: 0,
    sum: 0
  },
  methods: {
    addN1() {
      this.setData({
        n1: this.data.n1 + 1,
      })
    },
    addN2() {
      this.setData({
        n2: this.data.n2 + 1,
      })
    },
  },
  // 监听器
  observers: {
    'n1,n2': function(newN1,newN2) {
      this.setData({
        sum: newN1 + newN2
      })
    }
  }
})

wx-observers-demo.gif

eg.动态改变背景色

<view class="color-box" style="background-color: rgb({{fullColor}});">{{ fullColor }}</view>
Component({
  data: {
    rgb: {
      r: 0,
      g: 0,
      b: 0
    },
    fullColor: '0, 0, 0'
  },
  methods: {
    // 每次点击 +5
    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
      })
    },
  },
  // 监听器
  // 根据 rgb 对象的三个属性值,动态计算 fullColor 的值,用作背景色
  observers: {
    'rgb.r, rgb.g, rgb.b': function (r,g,b) {
      this.setData({
        fullColor: `${r}, ${g}, ${b}`
      })
    }
  }
})

wx-observers-fullcolor.gif

组件的生命周期

组件中有两种生命周期:

  • lifetimes:组件自身的生命周期
  • pageLifetime:组件所在页面的生命周期函数

eg.页面显示时随机生成颜色值

Component: ({
  pageLifetimes: {
    // 组件所在页面显示了
    show() {
      this._randomColor()
    }
  },
  methods: {
    // 生成随机颜色
    _randomColor() {
      this.setData({
        _rgb: {
          r: Math.floor(Math.random()*256),
          g: Math.floor(Math.random()*256),
          b: Math.floor(Math.random()*256)
        }
      })
    }
  }
})

组件通信

eg.父子组件通信示例

父组件:

<view style="padding: 10px;">
  <view>父组件中的count:{{ count }}</view>
  <!-- 通过绑定属性值给子组件传递数据,通过自定义事件等待子组件通知修改 -->
  <my-test5 count="{{ count }}" bind:sync="syncCount"></my-test5>
</view>
Page({
  data: {
    count: 0
  },
  syncCount(e) {
    // e.detail 获取子组件传递的数据
    this.setData({
      count: e.detail.value
    })
  },
})

子组件:

<view>
  <view>子组件接收的count:{{ count }}</view>
  <button bind:tap="addCount" type="primary" style="margin: 10px 0;">+1</button>
</view>
Component({
  // 子组件接收的数据
  properties: {
    count: Number
  },
  data: {},
  methods: {
    addCount() {
      // 修改子组件中的数据
      this.setData({
        count: this.properties.count + 1
      })
      // 通知父组件修改数据
      this.triggerEvent('sync', { value: this.properties.count })
    }
  }
})

wx-child-tongxin.gif

组件插槽

使用插槽自定义组件内部结构

  1. 在组件内部,通过 slot 标签占位
  2. 在使用组件时,传递插槽内容

image-20231118154439110.png

eg.多个具名插槽示例

// test4.js
Component({
  options: {
    // 允许定义多个插槽
    multipleSlots: true
  }
})
<!-- test4.wxml -->
<view>
  <view>通过插槽填充的内容:</view>
  <!-- 定义插槽 -->
  <slot name="header"></slot>
  <slot name="main"></slot>
</view>
<!-- home.wxml -->
<my-test4>
  <!-- 利用插槽传递结构/内容 -->
  <view slot="header">我是头部</view>
  <view slot="main">我是主体</view>
</my-test4>

image-20231118160004938.png

利用组件插槽,配合高阶组件,可以完成页面或按钮的访问控制

组件样式隔离

image-20231118095055687.png

image-20231118101237492.png

image-20231118101527326.png

image-20231118115347231.png

app.wxss 中的全局样式只能影响页面,对组件无效(标签选择器除外)

在组件中,最好使用类选择器

Vant组件库

快速上手

Vant Weapp 是一个轻量、可靠的小程序 UI 组件库

  1. 参照 官方文档 进行安装和配置
  2. 按需引入和使用组件
// app.json
"usingComponents": {
  "van-button": "path/to/@vant/weapp/dist/button/index"
}
<van-button type="primary">按钮</van-button>
  1. 覆盖 Vant 组件默认样式(可选)
  • 通过类名重写规则
.van-button--default {
  color: red !important;
}
  • 覆盖样式变量(注意CSS变量的作用域)
.my-button {
  --button-default-color: #1c52be;
}

常见组件

Button按钮组件

按钮用于触发一个操作,如提交表单

<van-button type="default">默认按钮</van-button>
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>

Cell 单元格

单元格用来展示列表中的一行内容

<van-cell-group>
    <van-cell title="单元格1" value="内容"> </van-cell>
    <van-cell title="单元格2" value="内容"> </van-cell>
</van-cell-group>

SwipeCell 滑动单元格

滑动单元格可以左右滑动展示操作按钮

<van-swipe-cell right-width="{{ 176 }}" left-width="{{ 88 }}">
    <!-- 左滑显示的按钮 -->
    <view slot="left" class="van-swipe-cell__left">
      <van-button type="primary">主要按钮</van-button>
    </view>
    <!-- 单元格内容区域 -->
    <van-cell-group>
      <van-cell title="单元格" value="内容" />
    </van-cell-group>
    <!-- 右滑显示的按钮 -->
    <view slot="right" class="van-swipe-cell__right">
      <van-button type="warning">警告按钮</van-button>
      <van-button type="danger">危险按钮</van-button>
    </view>
</van-swipe-cell>