小程序进阶
小程序路由跳转
路由跳转的三种方式:
- 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包的步骤:
- 初始化 package.json 文件
npm init -y - 安装三方包
npm i xxx - 重新构建npm【重要】
- 使用第三方包
小程序不支持的第三方包:
-
依赖于浏览器内置对象的包(如 axios、jquery)
-
依赖于 Node.js 内置库的包
-
依赖于 C++ 插件的包
@npm包导入错误
原因: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)
}
})
网络请求
小程序内置API?
通过全局对象
wx调用,实现网络请求、消息提示、本地存储、微信登录、微信支付等小程序中大部分的 API 都是异步方式执行,异步 API 传入的都是对象类型的参数,且都可以传入
success、fail、complete回调函数。也有少部分 API 是同步方式执行,同步方式的 API 有个特点就是均以 Sync 结尾。
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,
})
},
自定义源代码根目录
小程序中源代码默认放在项目根目录下,和配置文件混在一起,不方便维护
我们可以:
- 将源代码统一放入 src 目录下,如下图
- 配置
源代码根目录位置以及构建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.分包和使用
// 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中 -
组件有自己的生命周期函数
创建自定义组件
-
在源代码根目录下新建 components 文件夹
-
在 components 文件夹下新建 xxx 文件夹
-
在 xxx 文件夹上右击,选择新建组件
注册&使用组件
// app.json 全局注册,可以在任何页面中使用
// xxx.json 局部注册,只能在当前页面使用
{
"usingComponents": {
"my-test": "/components/test/test"
}
}
<my-test></my-test>
组件的数据&方法
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'
})
}
}
})
组件中的纯数据字段
纯数据字段:不用于界面渲染、也不会传递给其他组件的 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
})
}
}
})
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}`
})
}
}
})
组件的生命周期
组件中有两种生命周期:
- 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 })
}
}
})
组件插槽
使用插槽自定义组件内部结构
- 在组件内部,通过 slot 标签占位
- 在使用组件时,传递插槽内容
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>
利用组件插槽,配合高阶组件,可以完成页面或按钮的访问控制
组件样式隔离
app.wxss 中的全局样式只能影响页面,对组件无效(标签选择器除外)
在组件中,最好使用类选择器
Vant组件库
快速上手
Vant Weapp 是一个轻量、可靠的小程序 UI 组件库
- 参照 官方文档 进行安装和配置
- 按需引入和使用组件
// app.json
"usingComponents": {
"van-button": "path/to/@vant/weapp/dist/button/index"
}
<van-button type="primary">按钮</van-button>
- 覆盖 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>