很多项目,都会有项目独特的风格。很多雷同的业务代码可以提炼成一个组件,提高开发效率。这篇文章主要通过记录标签卡组件的封装来介绍如何开发微信小程序自定义组件、如何实现小程序自定义组件之间的通信。
标签卡需求
封装一个如下样式的组件。
要求组件有以下功能:
- 需要根据传进来的数组渲染这样一组标签,且组件默认兼容两种样式 “圆角按钮型、下划线型”
- 可随意切换各项标签,切换标签时需要满足以下两个条件:
- 要有选中样式,与其他标签进行区分
- 要将选中的项通知给父组件,实现组件内外的双向绑定;
- 页面加载时可自行设置默认选中项
渲染标签卡基本样式
微信小程序中的自定义组件,类似页面,由json
wxml
wxss
js
4个文件组成。
标签卡基础组件封装
- 在
tab.json
中配置"component": true
{
"component": true
}
- 在
tab.wxml
文件中构建相应的页面模板,模板通过wx:for
渲染传进来的数组参数,对于不同的样式可以在模板内使用简单的逻辑切换。wx:for
列表渲染,类似vue中的v-for
, 这里同样需要使用key
关键字。- 这里的
class
通过逻辑来判断使用哪种样式(这里主要是选中和未选中的样式),对于圆角还是下划线的样式是事先定义好,由传入的参数styleType
决定的
<!--components/tab/tab.wxml-->
<view class="mainBox">
<view wx:for="{{tabData}}" bindtap="changeTap" wx:key="index" data-index="{{index}}" class="{{ showIdx == index ? styleType+'Active '+styleType : styleType }}">{{item}}</view>
</view>
- 在
tab.js
逻辑层文件中主要就是接收相应的参数,以及切换时需要调用的逻辑函数
// components/tab/tab.js
Component({
/** 引用组件外部样式 */
options: {
styleIsolation: 'apply-shared'
},
/** 组件的属性列表:即传进来的参数 */
properties: {
styleType:{
type:String,
value:"radiusBtn" // radiusBtn: 圆角button样式 lineText: 下划线文本样式
},
showIndex:{
type: Number, // 默认选中的标签
value: 0
},
tabData:{
type:Array,
}
},
/** 组件的初始数据 */
data: {
showIdx:0 // 组件选中的索引
},
...
})
这里使用
styleIsolation: 'apply-shared'
可以引用组件外部的样式,即组件中如果有些样式需要更改的时候,可以单独在组件外部修改。这样组件外的样式会覆盖组件内的样式,主要用于一些特殊处理样式的地方,这样就不会污染组件内部样式了。
- 最后在组件的样式中设置相应的样式
/* 标签卡样式 */
.mainBox{
display: flex;
justify-content: center;
margin-bottom: 20rpx;
margin-top: 20rpx;
}
.radiusBtn{
height: 52rpx;
padding: 6rpx 28rpx;
border: 1rpx solid #33CE80;
line-height: 52rpx;
color: #33CE80;
}
.radiusBtn:first-child{
border-right: 0px;
border-top-left-radius: 26rpx;
border-bottom-left-radius: 26rpx;
}
.radiusBtn:last-child{
border-top-right-radius: 26rpx;
border-bottom-right-radius: 26rpx;
}
.radiusBtnActive{
background-color: #33CE80;
color: #ffffff;
}
/* 下划线文字样式 */
.lineText{
height: 40rpx;
padding: 6rpx 20rpx;
line-height: 40rpx;
color: black;
}
.lineTextActive{
color:#33CE80;
border-bottom: 1rpx solid #33CE80;
}
页面引用
组件的引用可以在app.json中全局引用也可以在页面单独引用,这里使用在页面单独引用的方式。
selfComponent.json
中配置:这个路径前对应的名称就是模板中使用的标签名
{
"usingComponents": {
"tab": "../../components/tab/tab"
}
}
selfComponent.wxml
中使用
<!--pages/selfComponent/selfComponent.wxml-->
<view>
<view>
<tab class="tab" tabData="{{tabText}}" showIndex="{{showIdx}}"></tab>
<tab class="tab" tabData="{{tabText}}" styleType="lineText" showIndex="{{showIdx}}"></tab>
</view>
</view>
selfComponent.js
中给组件设置相应的参数
// pages/selfComponent/selfComponent.js
Page({
data: {
tabText: ["柱状图","曲线图"],
showIdx: 0,
},
...
})
这样简单的样式就渲染到页面上了
切换标签卡时更改选中样式(组件内部方法)
到上一步只是能简单的渲染初始样式,并不能根据点击切换选中样式。切换的时候需要触发tab组件中的事件,在处理事件的方法中来处理相关的逻辑, 在tab.wxml
中 渲染列表的样式是通过showIdx
与数组参数index
是否相等来决定的,这里只需要更改组件的showIdx的值就行了:
// components/tab/tab.js
Component({
...
/** 组件的方法列表 */
methods: {
changeTap:function(e){
var index = e.target.dataset.index;
this.setData({
showIdx: index
})
}
}
})
这样切换标签卡的时候,选中样式就会跟着变化了:
子组件与外部通信(子组件与父页面双向绑定)
到目前为止,组件基本能使用了。但是很多时候,我们在点击标签卡的时候,在组件外部的页面也是需要有相应的处理业务的,怎么将组件内选中的数据传递到页面,使页面能够做自己的逻辑业务呢?
- 首先需要在组件中通过
this.triggerEvent(向外抛出的事件名, 给外部事件的参数)
将相应的信息传递出去,这里对切换标签卡的逻辑函数进一步加工
// components/tab/tab.js
Component({
...
/** 组件的方法列表 */
methods: {
changeTap:function(e){
var index = e.target.dataset.index;
this.setData({
showIdx: index
})
var option = {
index
}
this.triggerEvent('tabevent', option)
}
}
})
- 在使用组件的外部页面中通过
bind:事件名="页面中该事件调用的方法"
绑定组件的事件:
- 再通过
event.detail
获取组件传出来的参数,进行相应的逻辑处理:
// pages/selfComponent/selfComponent.js
Page({
...
changeChart:function(e){
console.log(e);
this.data.showIdx = e.detail.index;
this.setData({
showIdx:this.data.showIdx
})
console.log(this.data.showIdx, this.data.tabText[this.data.showIdx]);
},
...
})
这样当切换标签卡就能获得点击的是哪个值了。
设置默认选中
标签卡的基本功能都已经实现了。有时候在业务中页面可能通过点击别的操作而来,会对应不同的标签,这个时候需要选中与之对应的标签,该如何实现?
在组建的开始我们设计了一个属性showIndex
,这个参数就是留给外部页面设置默认选中来使用的,这里我们再仔细看一下tab组件:
在模板里我们使用的是组件自己的初始数据
showIdx
,而不是组件外部传进来的参数showIndex
,所以要想和组件外部有联系,需要根据showIndex
参数来改变showIdx
,这样就会使用到observers数据监听器了
数据监听器可以用于监听和响应任何属性和数据字段的变化
在tab.js
中增加对showIndex
的监听:
// components/tab/tab.js
Component({
/** 数据监听器 */
observers: {
'showIndex': function() {
// 在 numberA 或者 numberB 被设置时,执行这个函数
this.setData({
showIdx: this.properties.showIndex
})
}
},
})
页面传入的参数改成1的时候,就会默认选中第一项了:
思考?
既然最终都是使用组件的参数showIndex
,来改变组件选中状态的,可不可以直接在组件模板中使用showIndex
来判断选中状态呢?这样是不是就可以不用监听了?
这样是可以的,但是需要注意,这样需要结合外部页面改变外部传进来的参数值,否则会出现问题。
比如在这个例子中,外部页面给传入showIndex
参数的值是showIdx
,当我们切换标签卡的时候,需要外部页面的事件中重置showIdx
的数据才行,如果不重置,无论怎么切换标签卡,标签卡的选中样式就会始终停留在初始的状态。
所以建议使用第一种方法设置默认选中!
应用实例
结合上篇文章,使用标签卡切换echarts图。只需要稍微改动一下上篇文章的代码就行了:
这样就能将标签卡组件和动态切换echart结合在一起了:
编写自定义组件总结
- 自定义组件需要配置属性:
"component": true
; 外部组件使用需要配置"usingComponents": { "组件名": "组件路径", }
- 组件通信:
- 父传子: 子组件声明
properties
属性,父组件调用的时候给该属性传值。如果用过Vue框架的话,这里的properties
就类似props
// 子组件 Component({ styleType:{ type:String, value:"radiusBtn" // radiusBtn: 圆角button样式 lineText: 下划线文本样式 }, ... })
<!-- 父页面 --> <tab styleType="lineText"></tab>
-
子传父:子组件派发事件,再在子组件的事件中通过
this.triggerEvent(向外抛出的事件名, 给外部事件的参数)
的第二个参数向父页面传递数据,最后通过父页面通过bind:子组件派发的事件名="父页面中该事件调用的方法"
绑定组件的事件,在父页面中通过event.detail
获取到子组件传过来的参数。this.triggerEvent
类似Vue中的this.$emit
-
除了以上该组件中用到的两种通信,还可以通过
this.selectComponent
获取子组件实例对象。这里的选择器类似css的选择器,可以使用id选择器、class选择器……
- 父传子: 子组件声明