前言
对于组件的封装,在小程序当中对于多个页面的复用有着重要的作用,小程序中注册的每个页面都是独立的
页面的显示view
层与逻辑层是通过data
进行绑定关联,若须要更改页面中的数据,则通过setData
的形式进行批改
那么在小程序中如何自定义组件,以及自定义组件之间是如何进行通信呢
实例成果
- 残缺成果可见原文
通过下面一个简略的数字加减输入框组件,浏览完本文后,您将播种到
- 在小程序中如何自定义组件
- 在小程序页面中如何应用自定义组件
- 父(内部)组件如何向子组件传值
- 子组件如何承受父组件传递过去的值,同时渲染组件
- 子组件内如何进行事件交互,如何向父组件传递数据,影响父组件定义的数据
- 另一种办法父组件获取子组件的数据(非
triggerEvent
形式,即selectComponent
) - 达到某些条件时,如何禁止
view
的bindtap
事件 - 数字加减输入框代码的优化
为什么要自定义组件?
每个小程序页面都能够看成一个自定义组件,当多个页面呈现反复的构造时,能够把雷同的局部给抽取进去封装成一个公共的组件,不同的局部,在页面中通过传参的形式传入组件,渲染进去即可,达到复用的目标
上面以一个简略的数字加减输入框组件为例,麻雀虽小,但五脏俱全。
怎么应用自定义组件?
在miniprogram
下的目录下创立一个components
文件夹,与pages
目录同级,这个文件夹专门是用来放自定义组件的
例如:在components
目录下创立了一个count
文件夹,而后在新建Component
,组件名称命名为count
,微信开发者工具会主动的创立count
组件
如下所示:
<view> <view class="count-container"> <view bindtap="reduce" class="{{count == 1? 'btn-disabled': ''}}}">-</view> <input bindinput="handleInput" type="number" value="{{count}}" /> <view bindtap="add">+</view> </view></view>
如下是 css 代码
/* components/count/count.wxss */.count-container { width: 200rpx; display: flex; justify-content: center; border: 1px solid #ccc; margin: 30px auto;}.count-container view { width: 30px; text-align: center;}.count-container view:nth-child(1) { border-right: 1px solid #ccc;}.count-container view:nth-child(3) { border-left: 1px solid #ccc;}.count-container input { flex: 1; text-align: center;}.btn-disabled { background: #eee; pointer-events: none; /*微信小程序view禁掉bindtap事件,阻止点击,它是css3的一个属性,指定在什么状况下元素能够成为鼠标事件的target(包含鼠标的款式)*/}
如下是 js 逻辑代码
// components/count/count.jsComponent({ /** * 组件的属性列表 */ properties: { count: Number, }, /** * 组件的初始数据 */ data: {}, /** * 组件的办法列表 */ methods: { reduce() { console.log('减'); var count = this.data.count - 1; if (count < 1) { count = 1; } this.setData({ count, }); this.triggerEvent('changeCount', count); console.log(this.data.count); }, add() { console.log('加'); var count = this.data.count + 1; this.setData({ count, }); this.triggerEvent('changeCount', count); console.log(this.data.count); }, handleInput(event) { console.log(event); this.setData({ count: event.detail.value, }); this.triggerEvent('changeCount', event.detail.value); }, },});
自定义组件定义好了,那么如何应用呢
在pages
目录下,这里我创立了一个customComponents
页面
在要应用页面对应的customComponents.json
中的usingComponents
自定义组件的名称,同时引入组件的门路
{ "usingComponents": { "count":"/components/count/count" }}
留神
引入组件:应用相对路径地止也是能够的,如下面引入根门路/
也能够,自定义组件名称辨别大小写,为了代码的可读性,倡议对立小写,多个字母之间用-
连字符,例如:count-number
后面是自定义组件的名称,前面是申明创立该组件的门路
"usingComponents": { "count":"../../components/count/count" }
那么在对应页面(这里是customComponents
),的父组件(内部)wxml
中间接调用组件,以标签
模式插入就能够了的
你能够将自定义组件看作为自定义的标签,对原生wxml
中的view
的一种拓展,在自定义组件上能够增加自定义属性,绑定自定义事件.
如下示例代码所示
<count count="{{countNum}}" bind:changeCount="handleCount"></count><view class="parentText">父组件count:{{countNum}}</view>
而在customComponents
自定义页面中的逻辑代码中,如下所示
// pages/customComponents/customComponents.jsPage({ /** * 页面的初始数据 */ data: { countNum: 1, }, /** * 生命周期函数--监听页面加载 */ onLoad: function(options) {}, // 父组件中自定义绑定的事件 handleCount(event) { this.setData({ countNum: event.detail, }); },});
在微信小程序中,应用组件就是这么简略,想要在哪个页面应用,就在哪个页面的xxx.json
中申明组件,就能够了的
下面的代码兴许看得有点懵逼,上面将逐渐拆解的.
小程序中组件的通信与事件
在小程序中,组件间的根本通信形式有以下几种
wxml
数据绑定:用于父组件向子组件指定属性设置数据(当前会独自做一大节的,本篇不波及)- 事件: 用于子组件向父组件传递数据,能够传递任意数据(监听事件是组件通信的次要形式之一,自定义组件能够触发任意的事件,援用组件的页面能够监听这些事件,监听自定义组件事件的办法与监听根底组件事件的办法完全一致)
- 如果下面两种形式都无奈满足,在父组件中还能够通过
this.selectComponent("类名或ID")
办法获取子组件的实例对象,这样在父组件中不用通过event.detail
的形式获取,能够间接拜访子组件任意的数据和办法(前面也会提到)
如何向自定义组件内传递数据?
在页面customComponents
的wxml
中,以标签的形式,援用count
组件
这个页面,能够视作为父组件,父组件中能够定义以后组件的数据,办法等,如下所示
<count count="{{countNum}}" bind:changeCount="handleCount"></count>
定义在父组件中的数据,也能够视作为内部数据,例如:下面的countNum
就是挂载在customComponents
中的data
下的,初始值countNum
等于 1
父(内部)组件向子(内)组件传递数据是通过在子组件上自定义属性的形式实现的,自定义属性能够是根本数据类型(数字Number
,字符串String
,布尔(Boolean
)等)与简单数据类型(对象Object
,数组Array
)
如本示例中的,count
组件上定义了count
属性,这个名字并不是固定的,和自定义了changeCount
办法
也就是,将countNum
变量对象赋值给count
属性,给count
组件自定义了changeCount
办法
留神
handleCount
办法是定义在父组件当中的
// 父组件中自定义绑定的事件 handleCount(event){ this.setData({ countNum: event.detail // 通过event.detail能够拿到子组件传过来的值,如果不从新设置countNum,父组件的countNum是不会更新同步的 }) }
子组件内如何接管父组件传递过去的值?
在子组件内,Component
结构器能够用于定义组件,调用Component
结构器时,能够指定组件的属性,数据,办法等
其中properties
对象接管内部(父)组件传过来的自定义属性数据,能够是对象,数组,根本数据类型等
而data
是定义以后组件内的公有变量数据,可用于组件模板的渲染
舒适提醒
至于变量数据对象是定义在 properties
下还是挂载在 data
下,具体要看组件的应用
但凡内部传递过去的数据,那么就搁置在properties
中,而若是以后(外部)的组件模板渲染,那么就挂载在data
下
而这个data
上面挂载的数据,又分为一般数据字段,和纯数据字段,其中后者纯数据字段变量用_
结尾
这些指定的纯数据字段须要在Component
结构器的options
对象中指定pureDataPattern
的一个正则表达式,字段名合乎这个正则表达式的字段将成为纯数据字段
在小程序组件中,某些状况下,一些data
中的字段,也包含setData
中设置的字段,有些只参加业务逻辑,不会展现在界面上,也不会传递给其余组件,仅仅在以后组件外部应用
这样的数据字段被称为纯数据字段
,它能够定义在全局作用域中,也能够定义在data
下,若定义在data
下,它会被记录在this.data
中,而不会参加任何界面的渲染过程
如下所示
Component({ options: { pureDataPattern: /^_/, // 指定所有 _ 结尾的数据字段为纯数据字段 }, data: { a: true, // 一般数据字段 _b: true, // 纯数据字段 }, methods: { myMethod() { this.data._b; // 纯数据字段能够在 this.data 中获取 this.setData({ c: true, // 一般数据字段 _d: true, // 纯数据字段 }); }, },});
下面的组件中的纯数据字段不会被利用到wxml
中
<view wx:if="{{a}}"> 这行会被展现 </view><view wx:if="{{_b}}"> 这行不会被展现 </view>
:::
在properties
对象中接管内部组件传递过去的数据
// components/count/count.jsComponent({ /** * 组件的属性列表 */ properties: { count: Number, // 在这里接管内部组件传递过去的属性,同时确定传递过去数据的类型,类型有String,Boolean,Object,Array等 }, /** * 组件的初始数据 */ data: {}, /** * 组件的办法列表 */ methods: {},});
那么在外部组件中如何渲染呢,间接将properties
下的变量对象与wxml
中通过{{}}
插值表达式进行绑定关联就能够了的
如下所示input
中的count
<view> <view class="count-container"> <view>-</view> <input type="number" value="{{count}}" /> <view>+</view> </view></view>
以上就实现了子组件接管父组件内部传过来的值,而后在组件中渲染的过程
那么想要操作以后组件的数据,对加减输入框进行动静操作,在组件元素上绑定相应的事件操作就能够了的
<view> <view class="count-container"> <view bindtap="reduce" class="{{count == 1? 'btn-disabled': ''}}}">-</view> <input bindinput="handleInput" type="number" value="{{count}}" /> <view bindtap="add">+</view> </view></view>
在+
,-
上增加了bindtap
办法,进行业务逻辑的解决,如下所示
// components/count/count.jsComponent({ /** * 组件的属性列表 */ properties: { count: Number, }, /** * 组件的初始数据 */ data: {}, /** * 组件的办法列表 */ methods: { // 减操作 reduce() { console.log('减'); var count = this.data.count - 1; if (count < 1) { count = 1; } this.setData({ count, }); }, // 加操作 add() { console.log('加'); var count = this.data.count + 1; this.setData({ count, }); }, // 监听表单输出 handleInput(event) { console.log(event); this.setData({ count: event.detail.value, }); }, },});
子组件如何向父组件传递数据,影响父组件定义的数据
小程序,组件与组件之间是互相隔离,独立的,通过下面的一顿操作,数字框架的加减的确曾经实现了的,然而若在内部组件中,想要获取拿到子组件中的数据,如果不通过某些伎俩,子组件中的数据是影响不到父组件的
因为小程序当中数据的传递是单向的,也就是父组件传递数据给子组件,是通过在组件上增加自定义属性实现的,而在子组件外部的properties
中接管自定义组件的属性
如果你接触过vue
,与react
等框架,你会发现有惊人的相似之处,vue
中是props
接管,而react
是this.props
接管
小程序正是借鉴了它们的思维.
那父组件想要拿到子组件中的数据,换而言之,子组件又如何向父组件传递数据呢?影响到父组件中定义的初始化数据呢,该怎么办呢
父组件想要拿到子组件的数据,通过在组件上绑定自定义监听事件
监听事件
- 事件是视图层到逻辑层的通信形式
- 能够将用户的行为反馈到逻辑层进行解决
- 能够绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数
- 事件对象能够携带额定信息,如
id
,dataset
,touches
事件零碎是组件间通信的次要形式之一。自定义组件能够触发任意的事件,援用组件的页面能够监听这些事件,监听自定义组件事件的办法与监听根底组件事件的办法完全一致
如下所示
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”办法 --><component-tag-name bindmyevent="onMyEvent" /><!-- 或者能够写成 --><component-tag-name bind:myevent="onMyEvent" />
在本文示例中如下所示,bind:changeCount="handleCount"
,就是绑定了自定义changeCount
事件,这句话的含意,相当于是
在count
组件上监听绑定了一个changeCount
事件,当触发changeCount
事件时,就会调用前面父组件中定义的handleCount
办法
<count class="count" count="{{countNum}}" bind:changeCount="handleCount"></count>
而在父组件中,申明handleCount
办法,能够通过event
事件对象拿到子组件中的数据
Page({ handleCount: function(event) { event.detail; // 自定义组件触发事件时提供的detail对象 },});
既然在父组件中通过监听自定义事件,那么在子组件外部如何触发该事件呢
触发事件
自定义组件触发事件时,须要应用 triggerEvent
办法,指定事件名
、detail对象
和事件选项
如下所示
Component({ properties: {}, methods: { onTap: function() { var myEventDetail = {}; // detail对象,提供给事件监听函数 var myEventOption = {}; // 触发事件的选项 this.triggerEvent('自定义事件名称myEvent', myEventDetail, myEventOption); }, },});
在本示例中:
// components/count/count.jsComponent({ /** * 组件的属性列表 */ properties: { count: Number, }, /** * 组件的初始数据 */ data: {}, /** * 组件的办法列表 */ methods: { // 减 reduce() { console.log('减'); var count = this.data.count - 1; if (count < 1) { count = 1; } this.setData({ count, }); this.triggerEvent('changeCount', count); }, // 加 add() { console.log('加'); var count = this.data.count + 1; this.setData({ count, }); this.triggerEvent('changeCount', count); }, // 监听输入框 handleInput(event) { console.log(event); this.setData({ count: event.detail.value, }); this.triggerEvent('changeCount', event.detail.value); }, },});
至于为什么有三次triggerEvent
,每次加,减都是子组件外部的操作,内部组件想要实时获取到,那么就须要触发父组件监听的自定义办法的,同时triggerEvent
办法的第二个参数代表的就是以后子组件的外部所要传递给父组件的数据
当子组件触发了changeCount
办法,会调用父组件的handleCount
办法,在父组件中进行从新setData
父组件中的初始化数据,就能够更新同步到页面上了的
这个过程尽管有些绕,波折,对于初学者,须要自行感悟,理一下的
这个triggerEvent
,就相当于vue
中的this.$emit('绑定在父组件自定义事件名称',携带的数据)
办法的,而在React
中是通过this.props.办法
接管,调用父组件的办法
留神
在父组件中监听的自定义办法(如上示例的changeCount
),是通过triggerEvent
进行触发的,是搁置在子组件外部要监听的办法内的,而不是定义在methods
办法中
changeCount() { // 这是谬误的写法,有些小伙伴误以为自定义办法,就必须要写成办法这种模式的,它只是一个名称而已}
通过以上的代码示例,文字介绍,就晓得子组件如何向父组件传递数据,影响父组件定义的数据
子组件想要传递数据给父组件,影响父组件初始化定义的数据
- 首先须要在父组件上的自定义组件上设置监听自定义办法
- 在子组件外部的事件办法中,通过
triggerEvent
触发父组件中的自定义事件名称,同时,triggerEvent
第二个参数为携带所需的数据 - 在父组件中定义的办法,即可通过事件对象
event.detail
的形式获取到子组件中传递过去的值 - 在父组件中,从新
setData
数据即可更新父组件中初始化的数据,从而渲染到页面上
以上是通过triggerEvent
的形式,并携带参数传递给自定义事件,从而在父组件中能够通过event.detail
的形式拿到子组件中的数据
其实,还有另外一种简便的办法,同样能够拿到
父组件通过
this.selectComponent
拿到子组件中的数据
前提条件
须要在父组件的援用自定义组件上,增加class
或id
例如:在count
组件上增加了一个class
类count
<count class="count" count="{{countNum}}" bind:changeCount="handleCount"></count>
那么,在父组件中的handleCount
中里调用 this.selectComponent
,获取子组件的实例数据
调用时须要传入一个匹配选择器 selector
,class
与Id
都能够,如,this.selectComponent('类或ID')
本示例中是this.selectComponent('.count')
,如下示例代码所示
handleCount(){ console.log(this.selectComponent('.count')); var count = this.selectComponent('.count'); this.setData({ countNum: count.data.count // 从新赋值setData countNum数据 }) }
这种办法也是能够的,在小程序当中也很罕用
如何禁止掉
view
的bindtap
事件?
在做数字加减输入框时,对于减到某个数值时,想要禁用状态,遇到相似的状况时,要么把view
换成button
而后当达到某个条件时,将button
的状态设置为disabled
属性也是能够的
然而若不必button
呢,该怎么实现呢
如果用view
代替button
,尽管在某个条件下,能够达到款式上是禁用状态,然而如果你在测试时,这个减
操作依然是在一直触发的
这样显然有些鸡肋
解决这个问题: 借助了 css3 中的一个十分好用的个性
在指定的类上增加一个pointer-events: none
就能够了的
.btn-disabled { pointer-events: none; /*微信小程序view禁掉bindtap事件,阻止点击,它是css3的一个属性,指定在什么状况下元素能够成为鼠标事件的target(包含鼠标的款式)*/}
这个属性,作用在view
上,能够组织bindtap
的点击
数字加减输入框代码的优化
在下面实现数字加减框组件,别离给减
,加
绑定了两个办法,屡次呈现了triggerEvent
<view> <view class="count-container"> <view bindtap="handleCount" data-count="-1" class="{{count == 1? 'btn-disabled': ''}}}" >-</view > <input bindinput="handleInput" type="number" value="{{count}}" /> <view bindtap="handleCount" data-count="1">+</view> </view></view>
在下面的加减中绑定一个雷同的事件办法handleCount
,而通过设置data-xx
属性,判断是加还是减
那么在逻辑代码中
methods: { handleCount(event){ var count = event.currentTarget.dataset.count; count = this.data.count+Number(count); // 这里之所以要把count,转换为Number,因为自定义属性的count是字符串,+加号字符串拼接,会变成一个字符串 if(count < 1) { count = 1; } this.setData({ count: count }) this.triggerEvent('changeCount', count); },}
下面的代码相比于后面写的代码,就要简便得不少,看着难受得多
在做这种相似的业务逻辑时,无妨能够通过这种形式对代码进行优化的
结语
本文次要是讲到了在小程序中父子组件之间如何进行通信,父组件向子组件传递数据是通过在援用组件上绑定自定义属性实现的
而子组件是通过在properities
对象中进行接管的,子组件如何向父组件传递数据,它是通过在援用组件上绑定监听自定义事件,而后在子组件的事件办法内,通过this.triggerEvent
办法进行触发自定义事件名,并能够携带子组件内的数据,在父组件中的函数中
能够通过event.detail
能够拿到子组件中传递给父组件的值,从而从新在setData
数据,就能够更新父组件中的初始化数据
这个关系尽管有点绕,至于重要性显而易见.
相干链接
- 组件间通信与事件
- 小程序中的事件
- 原文出处-https://coder.itclan.cn/