从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/
2.1 创建
步骤:
建议:为了保证目录结构清晰,建议把不同的组件存放到单独的目录中,如下图所示
2.2 引用
1)局部引用
.json
//在页面的.json文件中,引入自定义的组件并定义引用时的标签名
{
"usingComponents": {
//自定义组件的标签名字为my-test1
"my-test1":"/components/test1/test1"
},
.wxml
//在页面的.wxml文件中使用刚刚自定义组件的标签名来引用自定义组件
<my-test1>使用自定义组件1</my-test1>
2)全局引用
3)应用场景
主要根据组件的使用频率和范围来选择合适的引用方式
2.3 组件与页面的区别
大家会发现自定义组件和页面是不是非常相似,同样都是拥有四个文件 .js .json .wxml .wxss
但是组件与页面的.js与.json文件有着明显的区别:
3.1样式隔离特性
在默认的情况下,自定义组件的样式只对当前的组件生效,而不会去影响到组件之外的UI结构
如下图:
注意点:
所以在自定义组件和引用自定义组件的页面中最好使用class样式选择器,不要使用id,属性,标签选择器从而避免造成不必要的麻烦!
3.2 修改隔离选项
默认情况下,自定义组件的样式隔离特性可以有效防止内外样式互相干扰的问题。但是在某些情况下我们又需要在外界能够控制自定义组件的内部样式,此时就可以通过修改styleIsolation属性来修改自定义组件样式隔离选项,从而达到控制内部组件样式的目的。
有两种方法:
//.js文件中
Component({
options:{
//表示启用样式隔离
styleIsolation:'isolated'
}
})
//.json文件中
{
"styleIsolation":"isolated"
}
styleIsolation的可选值:
使用后两者时,请务必注意组件间样式的相互影响。
如果这个 Component 构造器用于构造页面 ,则默认值为 shared ,且还有以下几个额外的样式隔离选项可用:
4.1 data数据节点
在小程序组件中,用于组件模板渲染的私有数据需要定义到.js文件的data节点中
// components/test1/test1.js
Component({
/**
* 组件的初始数据
*/
data: {
count:0
},
})
4.2 methods方法节点
在小程序组件中,事件处理函数和自定义的方法需要定义到.js文件的methods节点中
// components/test1/test1.js
//点击按钮使count加1,并且控制其最大值为属性中的max值
Component({
/**
* 组件的初始数据
*/
data: {
count:0
},
/**
* 组件的属性列表
*/
properties: {
//方法一:完整方式
max:{
type:Number,//参数类型
value:0 //参数的默认值
}
},
/**
* 组件的方法列表
*/
methods: {
// 点击事件的处理函数
addCount(){
if(this.data.count>=this.properties.max)return
this.setData({
count:this.data.count+1
})
this._showCount()
},
// 自定义的方法建议以下划线开头以示区分
_showCount(){
// 打印消息框
wx.showToast({
title: 'count值为:'+this.data.count,
icon:"none"
})
},
}
})
4.3 properties属性节点
在小程序组件中,properties是组件的对外属性,用来接收外界传递到组件中的数据
// components/test/test1.js
Component({
/**
* 组件的属性列表
*/
properties: {
//方法一:完整方式
max:{
type:Number,//参数类型
value:0 //参数的默认值
}
//方法二:简化方式
max:Number
},
})
然后在页面结构中可以在标签内直接传值
<my-test1 max="10"></my-test1>
4.4 data和properties的区别
在小程序组件中,properties属性和data数据的用法相同,在本质上没有任何区别,properties里面的值与data一样可以用于页面渲染,也可以使用setData()方法为properties属性中的值进行重新赋值,但有以下两点区别:
5.1 监听数据的变化
数据监听器,用于监听和响应任何属性与数据字段的变化,从而执行特定的操作。
基本语法格式:
Component({
observer:{
'字段A,字段B':function(字段A的新值,字段B的新值){
// do something
}
}
})
示例代码:
test1.wxml
<!--components/test1/test1.wxml-->
<view>{{n1}}+{{n2}}={{sum}}</view>
<button bindtap="addN1" type="primary">n1+1</button>
<button bindtap="addN2" type="primary">n2+1</button>
test1.js
// components/test1/test1.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
n1:0,
n2:0,
sum:0
},
/**
* 组件的方法列表
*/
methods: {
//n1+1
addN1(){
this.setData({
n1:this.data.n1+1
})
},
//n2+1
addN2(){
this.setData({
n2:this.data.n2+1
})
}
},
//数据监听器。监听n1,n2的变化,自动计算sum
observers:{
'n1,n2':function(newN1,newN2){
this.setData({
sum:newN1+newN2
})
}
}
})
app.json声明自定义组件
"usingComponents": {
"my-test1":"/components/test1/test1"
}
在home.wxml上渲染
<!--pages/home/home.wxml-->
<my-test1></my-test1>
实现效果
当任意按下n1+1或者n2+1按钮时,导致n1,n2数值的变化从而被数据监听器捕捉到,然后在事件监听器处理函数内部对sum用新值进行自动计算赋值
5.2 监听对象属性的变化
数据监听器支持监听对象中单个或者多个属性的变化,基本语法如下:
Component({
observer:{
'对象.属性A,对象.属性B':function(属性A的新值,属性B的新值){
// do something
}
}
})
如果某个对象中需要被监听的属性太多,为了方便,则可以使用通配符 ** 来监听对象中所有属性的变化,基本语法如下:
Component({
observer:{
'对象.**':function(obj){
this.setData({
某数据字段:`${obj.对象属性1},${obj.对象属性2}......`
})
}
}
})
纯数据字段指的是那些完全不用于页面渲染的data字段
纯数据字段可以提升页面更新的性能
应用场景:
使用规则:
在Component构造器的options节点中,指定pureDataPattern为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段,示例代码如下:
Component({
options:{
//这里示例指定所有以 _ 开头的数据字段为纯数据字段
pureDataPattern:/^_/
},
data:{
a:true,//普通字段
_b:true,//将被设置为纯数据字段
}
})
7.1 组件主要生命周期
7.2 定义生命周期函数
在小程序组件中,生命周期函数的定义有新旧两种方式
自定义组件.js文件:
//定义组件生命周期函数的两种方式
Component({
//推荐用法
lifetimes:{
attached(){ },
detached(){ },
}
//旧的定义方式,与data节点平级
attached(){ },
detached(){ },
})
注:如果同时存在两种新旧定义方式,那么旧的定义方式将会被覆盖掉
在自定义组件的wxml结构中,可以提供一个节点(插槽),用于填充组件使用者提供的wxml结构,起到占位的作用
8.1 单个插槽的使用
在小程序中,默认每个自定义的组件只能允许使用一个进行占位,这种个数上的限制就叫做单个插槽
<!-- 组件的封装者 -->
<view>
<view>这是组件的内部节点</view>
<!-- 下面对于不确定的内容可以使用<slot>插槽进行占位,后续由组件使用者进行填充 -->
<slot></slot>
</view>
<!-- 组件的使用者 -->
<component-tag-name>
<!-- 下面的内容将被放置到组件封装者定义插槽<slot>的位置 -->
<view>这是插入到组件slot中内容</view>
</component-tag-name>
8.2 多个插槽的使用
1)启动多个插槽
小程序的自定义组件默认支持一个插槽的使用,如果需要使用多个插槽,就可以在组件.js文件中通过如下方式进行配置从而启用多个插槽的功能
Component({
options:{
multipleSlots:true//启用多个插槽
},
properities:{/* ... */},
methods:{/* ... */}
})
定义与使用
<!-- 页面模板,即组件的封装者 -->
<view>
<!-- 这是name为slot1的第一个插槽 -->
<slot name="slot1"></slot>
<view>~~~~~~~分隔线~~~~~~~~~</view>
<!-- 这是name为slot2的第二个插槽 -->
<slot name="slot2"></slot>
</view>
<!-- 引用组件的页面模板,即组件的使用者 -->
<component-tag-name>
<view slot="slot1">这是插入到名字为slot1插槽里面的内容</view>
<view slot="slot2">这是插入到名字为slot2插槽里面的内容</view>
</component-tag-name>
9.1 组件间通信
组件间的基本通信方式有以下几种。
9.2 监听事件
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见 事件 。
监听自定义组件事件的方法与监听基础组件事件的方法完全一致:
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
Page({
onMyEvent: function(e){
e.detail // 自定义组件触发事件时提供的detail对象
}
})
9.3 触发事件
自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项:
<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,提供给事件监听函数
var myEventOption = {} // 触发事件的选项
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
})
可在父组件里调用 this.selectComponent ,获取子组件的实例对象。
调用时需要传入一个匹配选择器 selector,如:this.selectComponent(“.my-component”)。
// 父组件
Page({
data: {},
getChildComponent: function () {
const child = this.selectComponent('.my-component');
console.log(child)
}
})
在上例中,父组件将会获取 class 为 my-component 的子组件实例对象,即子组件的 this 。
注 :默认情况下,小程序与插件之间、不同插件之间的组件将无法通过 selectComponent 得到组件实例(将返回 null)。如果想让一个组件在上述条件下依然能被 selectComponent 返回,可以自定义其返回结果(见下)
// 自定义组件 my-component 内部
Component({
behaviors: ['wx://component-export'],
export() {
return { myField: 'myValue' }
}
})
<!-- 使用自定义组件时 -->
<my-component id="the-id" />
// 父组件调用
const child = this.selectComponent('#the-id') // { myField: 'myValue' }