自定义组件
创建组件
在项目的根目录中,创建 components 文件夹,在里面编写我们的自定义主键,如下所示:
引用组件
局部引用:
- index.json
// 在页面的 .json 文件中,引入组件
{
"usingComponents": {
"my-text2": "/components/test2/test2"
}
}
- index.wxml
// 使用组件
<my-test2></my-test2>
全局引用:
- app.json
{
"usingComponents": {
"my-test2": "/components/test2/test2"
}
}
- index.wxml
// 使用组件
<my-test2></my-test2>
组件和页面的区别
相同点:
- 组件和页面都是由 .js、.json、.wxml 和 .wxss 这四个文件组成的
不同点:
-
组件的 .json 文件中需要声明
"component": true
-
组件的 .js 文件中调用的是
Component()
函数 -
组件的事件处理函数需要定义到 methods 节点中
组件的样式
样式隔离:默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的内容
注意:
- app.wxss 中的样式对自定义组件无效
- 只有 class 选择器会有样式隔离效果,id 选择器,属性选择器,标签选择器不受样式隔离的影响
建议:在组件和引用组件的页面中,使用 class 选择器,不要使用 id、属性、标签选择器!
修改组件的样式隔离选项:
有时候,我们希望外界能够控制组件内部的样式,此时,可以通过 styleIsolation 修改组件样式隔离的选项
- 自定义组件.js
Component({
options: {
styleIsolation: 'isolated'
}
})
数据|方法|属性
- 自定义组件.wxml
<view>{{ count }}</view>
<button bindtap="addCount">+1</button>
- 自定义组件.js
Component({
// 组件的属性列表
properties: {
max:{ // 完整定义属性的方式
type: Number, // 属性值的数据类型
value: 10 // 属性默认值
},
// max: Number // 简化定义属性的方式【不需要指定属性默认值的时候,可以使用简化的方式】
},
// 组件的初始数据
data: {
count: 0
},
// 组件的方法列表
methods: {
addCount() {
if(this.data.count >= this.properties.max) return // 限制加数的最大值
this.setData({
count: this.data.count + 1
})
}
}
})
- index.wxml
<!-- 使用组件 -->
<view>-----------------------</view>
<test max="15"></test>
<view>-----------------------</view>
在小程序的组件中,properties 属性和 data 数据的用法相同,它们都是可读可写的,只不过:
- data 更倾向于存储组件的私有数据
- properties 更倾向于存储外界传递给组件的数据
由于 data 数据和 properties 属性在本质上没有任何区别,因此 properties 属性的值也可以用于页面渲染,或者使用 setData 为 properties 中的属性重新赋值,代码如下:
- 自定义组件.wxml
// 在自定义组件 .wxml 文件中使用 properties 属性的值
<view> max属性的值:{{ max }} </view>
- 自定义组件.js
Component({
properties: { max: Number }, // 定义属性
methods: {
addCount() {
this.setData({ max: this.properties.max + 1 }) // 使用 setData 修改属性的值
}
}
})
数据监听器
基本概念:
数据监听器用于监听和响应任何属性和数据字段的变化,从而执行特定的操作。
它的作用类似于 vue 中的 watch 侦听器。
在小程序组件中,数据监听器的基本语法格式如下:
具体演示:
- 自定义组件.wxml
<view>{{ n1 }} + {{ n2 }} = {{ sum }}</view>
<button bindtap="addN1">n1自增</button>
<button bindtap="addN2">n2自增</button>
- 自定义组件.js
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(n1, n2) { // 监听 n1 和 n2 数据的变化
this.setData({ sum: n1 + n2 }) // 通过监听器,自动计算 sum 的值
}
}
})
- index.wxml
<!-- 使用组件 -->
<view>-----------------------</view>
<test></test>
<view>-----------------------</view>
纯数据字段
基本概念:
"纯数据字段"指的是那些不用于界面渲染的 data 字段
应用场景:
有些情况下,某些 data 中的字段既不会展示到页面上,也不需要传递给其他组件,仅仅在当前组件内部使用,带有这种特性的 data 字段适合被设置为纯数字字段
优势好处:
纯数据字段有助于提升页面更新的性能
使用规则:
Component({
options: {
// 指定所有 _ 开头的数据字段为纯数据字段
pureDataPattern: /^_/
},
data: {
a: true, // 普通数据字段
_b: true, // 纯数据字段
}
})
组件的全部生命周期函数
上面这些生命周期函数中,有三个比较重要,分别是 created、attached、detached
- 组件实例刚被创建好的时候,created 函数会被触发
此时还不能调用 setData
通常在这个生命周期函数中,只应该用于给组件的 this 添加一些自定义的属性字段
- 在组件完全初始化完毕,进入页面节点树后,attached 函数会被触发
此时,this.data 已经被初始化完毕
这个生命周期很有用,绝大多数初始化的工作可以在这个时机进行(例如:发请求获取初始数据)
- 在组件离开页面节点树后,detached 函数会被触发
退出一个页面时,会触发页面内每个自定义组件的 detached 函数
此时,适合做一些清理性质的工作
lifetimes 节点
Component({
// 使用生命周期函数
lifetimes: {
attached() {...}, // 在组件实例进入页面节点树时执行
detached() {...}, // 在组件实例被从页面节点树移除时执行
}
})
组件所在页面的生命周期
概念:自定义组件的行为依赖于页面状态的变化,此时就需要用到"组件所在页面的生命周期"
使用:组件所在页面的生命周期函数,需要定义在 pageLifetimes 节点中
Component({
pageLifetimes: {
show: function() {...}, // 页面被展示
hide: function() {...}, // 页面被隐藏
resize: function(size) {...}, // 页面尺寸变化
}
})
插槽
概念:在自定义组件的 wxml 结构中,可以提供一个 节点(插槽),用于承载组件使用者提供的 wxml 结构
单个插槽:每个小程序中,默认每个自定义组件中只允许使用一个进行占位,这种个数上的限制叫做单个插槽
- 自定义.wxml
<view>
<view>组件内部节点(上)</view>
<slot></slot>
<view>组件内部节点(下)</view>
</view>
- index.wxml
<view>这里是index</view>
<test>
<view>这些文字是插入到组件slot中的</view>
</test>
- 运行结果
这里是index
组件内部节点(上)
这些文字是插入到组件slot中的
组件内部节点(下)
启用多个插槽:在小程序的自定义组件中,需要使用多个 插槽时,可以在组件的 .js 文件中,通过如下方式进行启用:
- 自定义组件.js
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多 slot 支持
}
})
- 自定义组件.wxml
<view>
<view>组件内部节点(上)</view>
<slot name="before"></slot>
<slot name="after"></slot>
<view>组件内部节点(下)</view>
</view>
- index.wxml
<view>这里是index</view>
<test>
<view slot="before">1111 - 这些文字是插入到组件slot中的</view>
<view slot="after">2222 - 这些文字是插入到组件slot中的</view>
</test>
- 运行结果
这里是index
组件内部节点(上)
1111 - 这些文字是插入到组件slot中的
2222 - 这些文字是插入到组件slot中的
组件内部节点(下)
父子组件通信
三种方式:
- 属性绑定
用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容的数据
- 事件绑定
用于子组件向父组件传递数据,可以传递任意数据
- 通过组件实例
父组件还可以通过 this.selectComponent() 获取子组件实例对象
这样就可以直接访问子组件的任意数据和方法
方式一:属性绑定:
父向子传递数据,只能传递普通类型的数据,无法传递方法
// 父组件的 data 节点
data: {
count: 0
}
// 父组件的 wxml 结构
<test count="{{ count }}"></test>
<view>----</view>
<view>父组件中,count的值为:{{ count }}</view>
// 子组件的 properties 节点
properties: {
count: Number
}
// 子组件的 wxml 结构
<text>子组件中,count的值为:{{ count }}</text>
方式二:事件绑定:
事件绑定用于实现子向父传值,可以传递任何类型的数据
// 在父组件中定义 syncCount 方法
// 将来,这个方法会被传递给子组件,供子组件进行调用
syncCount() {
console.log('syncCount')
}
<!-- 在父组件的 wxml 中 -->
<test count="{{ count }}" bindsync="syncCount"></test>
<!-- 子组件的 wxml 中 -->
<view>子组件中,count的值为:{{ count }}</view>
<button bindtap="addCount">+1</button>
// 子组件的 js 代码
Component({
// 方法列表
methods: {
addCount: {
this.setData({
count: this.properties.count + 1
})
this.triggerEvent('sync', { value: this.properties.count })
}
}
})
// 在父组件的 js 中
syncCount(e) {
this.setData({
count: e.detail.value
})
}
方式三:组件实例:
// wxml 结构
<test count="{{ count }}" bind:sync="syncCount" class="customA" id="cA"></test>
<button bindtap="getChild">获取子组件实例</button>
// js --- 按钮的 tap 事件处理函数
getChild() {
// 切记下面参数不能传递标签选择器,不然返回的是 null
const child = this.selectComponent('.customA') // 也可以传递 id 选择器
child.setData({ count: child.properties.count + 1 }) // 调用子组件的 setData 方法
child.addCount() // 调用子组件的 addCount 方法
}
behavior
基本概念:
- behaviors 是小程序中,用于实现组件间代码共享的特性
- 每个 behavior 可以包含一组属性、数据、生命周期函数和方法
- 组件引用它时,它的属性、数据和方法会被合并到组件中
- 每个组件可以引用多个 behavior,behavior
如何创建:
在项目根目录下,创建一个 behaviors 文件夹,里面创建 myBehavior.js 文件
// 调用 Behavior() 方法,创建实例对象
// 使用 module.exports 将 behavior 实例对象共享出去
module.exports = Behavior({
// 属性节点
properties: {},
// 私有数据节点
data: { username: 'zs' },
// 事件处理函数和自定义方法节点
methods: {},
// 其他节点...
}
导入使用:
// 1. 导入 behavior 模块
const myBehavior = require("../../behaviors/myBehavior.js")
Component({
// 2. 将导入的 behavior 实例对象,挂载到 behaviors 数组节点中,即可生效
behaviors: [myBehavior],
// 组件的其他节点...
})
behavior 所有可用的节点:
同名字段的覆盖和组合规则:
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html
使用 npm 包
支持与限制
目前小程序中已经支持使用 npm 安装第三方包,从而来提高小程序的开发效率,但是,在小程序中使用 npm 包有如下限制:
- 不支持依赖于 Node.js 内置库的包
- 不支持依赖于浏览器内置对象的包
- 不支持依赖于 C++ 插件的包
UI 组件库
基本介绍
Vant Weapp 是一套小程序 UI 组件库,助力开发者快速搭建小程序应用
官网地址:https://vant-contrib.gitee.io/vant-weapp/
安装与使用
教程:https://vant-contrib.gitee.io/vant-weapp/#/quickstart
定制主题
教程:https://vant-contrib.gitee.io/vant-weapp/#/theme
API Promise化
为什么要 Promise 化
默认情况下,小程序官方提供的异步 API 都是基于回调函数实现的,例如,网络请求的 API 需要按照如下的方式调用:
wx.request({
method: '',
url: '',
data: { },
success: {} => { }, // 请求成功的回调函数
fail: {} => { }, // 请求失败的回调函数
complete: {} => { }, // 请求完成的回调函数
})
上面这种方式的缺点:容易造成回调地狱的问题,代码的可读性、维护性较差,而使用 Promise 能有效解决这个问题
什么是 API Promise 化
API Promise 化,指的是通过额外的配置,将官方提供的、基于回调函数的异步 API,升级改造为基于 Promise 的异步 API,从而提高代码的可读性、维护性、避免回调地狱的问题
如何实现 API Promise 化
依赖于 miniprogram-api-promise
第三方包
npm install --save miniprogram-api-promise@1.0.4
// 在小程序入口文件中(app.js),只需要调用一次 promisifyAll() 方法即可实现
import { promisifyAll } from 'miniprogram-api-promise'
const wxp = wx.p = {}
promisifyAll(wx, wxp)
App({
...
})
// 在小程序页面中使用
// index.wxml
<button bindtap="getInfo">按钮</button>
// index.js
Page({
async getInfo() {
// 异步等待数据
const res = await wx.p.request({
method: 'GET',
url: 'https://www.escook.cn/api/get'
data: {
name: 'zs',
age: 20
}
})
// 输出数据
console.log(res)
}
})
全局数据共享
基本概念
-
"全局数据共享"又叫做状态管理,是为了解决组件之间数据共享的问题
-
开发中常用的"全局数据共享"方案有:Vuex、Redux、MobX等
-
小程序中,可使用 mobx-miniprogram 配合 mobx-miniprogram-bindings 实现全局数据共享
mobx-miniprogram 用来创建 Store 实例对象
mobx-miniprogram-bindings 用来把 Store 中的共享数据或方法,绑定到组件或页面中使用
安装步骤
- 运行下面命令,安装 MobX 相关的包:
npm install --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1
- 注意:安装完成后,记得删除 miniprogram_npm 目录后,重新构建 npm
创建 Store 实例
在项目根目录下,新建 store 文件夹,在文件夹中创建一个 store.js 文件:
// 在这个 JS 文件中,专门来创建 Store 的实例对象
// 1. 引入包
import observable from 'mobx-miniprogram'
import action from 'mobx-miniprogram'
// 2. 导出 store
export const store = observable({
// 数据字段
numA: 1,
numB: 2,
// 计算属性
get sum() { // get 是一个修饰符,代表只读的意思
return this.numA + this.numB
},
// actions 方法,用来修改 store 中的数据
updateNum1: action(function (step) {
this.numA += step
}),
updateNum2: action(function (step) {
this.numB += step
}),
})
页面绑定 Store 成员
// 页面的 .js 文件
import { createStoreBindings } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Page({
// 生命周期函数——监听页面加载
onLoad: function () {
this.storeBindings = createStoreBindings(this, {
store,
fields: ['numA', 'numB', 'sum'],
actions: ['updateNum1']
})
}
// 生命周期函数——监听页面卸载
onUnload: function () {
this.storeBindings.destoryStoreBindings()
}
})
页面使用 Store 成员
// 页面的 .wxml 结构
<view>{{ numA }} + {{ numB }} = {{ sum }}</view>
<button bindtap="btnHandler1" data-setp="{{ 1 }}"> numA + 1</button>
<button bindtap="btnHandler1" data-setp="{{ -1 }}"> numA - 1</button>
// 页面的 .js 结构
// 按钮 tap 事件的处理函数
btnHandler1(e) {
// 通过 e.target.dataset.setup 可获取 data-setp 中对应的数据
this.updateNum1(e.target.dataset.setup)
}
组件绑定 Store 成员
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Component({
store, // 指定要绑定的 Store
fields: { // 指定要绑定的字段数据
numA: () => store.numA, // 绑定字段的第 1 种方式
numB: (store) => store.numB, // 绑定字段的第 2 种方式
sum: 'sum' // 绑定字段的第 3 种方式
},
actions: { // 指定要绑定的方法
updateNum2: 'updateNum2'
}
})
组件使用 Store 成员
// 自定义组件的 .wxml 结构
<view>{{ numA }} + {{ numB }} = {{ sum }}</view>
<button bindtap="btnHandler2" data-step="{{ 1 }}">numB + 1</button>
<button bindtap="btnHandler2" data-step="{{ 1 }}">numB - 1</button>
// 组件的方法列表
method: {
btnHandler2(e) {
this.updateNum2(e.target.dataset.step)
}
}
分包
基础概念
什么是分包:
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需加载
分包的好处:
- 可以优化小程序首次启动的下载时间
- 在多团队共同开发时可以更好的解耦协作
分包之前的项目构成:
分包之后的项目构成:
分包的加载规则:
-
在小程序启动时,默认会下载主包并启动主包内页面(tabBar 页面需要放到主包中)
-
当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示
-
非 tabBar 页面可以按照功能的不同,划分为不同的分包之后,进行按需下载
分包的体积限制:
- 整个小程序所有分包大小不能超过 16M(主包 + 所有分包)
- 单个分包/主包大小不能超过 2 M
使用分包
配置方法:
打包原则:
- 小程序会按照 subpackages 的配置进行分包,subpackages 之外的目录将被打包到主包中
- 主包也可以有自己的 pages(即最外层的 pages 字段)
- tabBar 页面必须在主包内
- 分包之间不能互相嵌套
引用原则:
- 主包无法引用分包内的私有资源
- 分包之间不能相互引用私有资源
- 分包可以引用主包的公共资源
独立分包
概念:独立分包本质上也是分包,只不过它比较特殊,可以独立于主包和其他分包而单独运行
区别:
- 普通分包必须依赖于主包才能运行
- 独立分包可以在不下载主包的情况下,独立运行
应用场景:
开发者可以按需将某些具有一定功能独立性的页面配置到独立分包中,因为独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度
注意事项:一个小程序中可以有多个独立分包
配置方法:
引用原则:
"独立分包"和"普通分包以及主包"之间,是相互隔绝的,不能引用彼此的资源!
分包预下载
基本概念:
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度
预下载分包的行为
相关配置:
使用 preloadRule 节点定义分包的预下载规则
{
"preloadRule": { // 分包预下载的规则
"pages/contact/contact": { // 触发分包预下载的页面路径
// network 表示在指定的网络模式下进行预下载
// 可选值为:all(不限网络)和 wifi(仅 wifi 模式下进行预下载)
// 默认值为:wifi
"network": "all",
// packages 表示进入页面后,预下载哪些分包
// 可以通过 root 或 name 指定预下载哪些分包
"packages": ["pkgA"]
}
}
}
预下载的限制:
预下载的大小限额 2M
自定义 tabBar
官方教程:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html