前言

对于组件的封装,在小程序当中对于多个页面的复用有着重要的作用,小程序中注册的每个页面都是独立的

页面的显示view层与逻辑层是通过data进行绑定关联,若须要更改页面中的数据,则通过setData的形式进行批改

那么在小程序中如何自定义组件,以及自定义组件之间是如何进行通信呢

实例成果

  • 残缺成果可见原文

通过下面一个简略的数字加减输入框组件,浏览完本文后,您将播种到

  • 在小程序中如何自定义组件
  • 在小程序页面中如何应用自定义组件
  • 父(内部)组件如何向子组件传值
  • 子组件如何承受父组件传递过去的值,同时渲染组件
  • 子组件内如何进行事件交互,如何向父组件传递数据,影响父组件定义的数据
  • 另一种办法父组件获取子组件的数据(非triggerEvent形式,即selectComponent)
  • 达到某些条件时,如何禁止viewbindtap事件
  • 数字加减输入框代码的优化

为什么要自定义组件?

每个小程序页面都能够看成一个自定义组件,当多个页面呈现反复的构造时,能够把雷同的局部给抽取进去封装成一个公共的组件,不同的局部,在页面中通过传参的形式传入组件,渲染进去即可,达到复用的目标

上面以一个简略的数字加减输入框组件为例,麻雀虽小,但五脏俱全。

怎么应用自定义组件?

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的形式获取,能够间接拜访子组件任意的数据和办法(前面也会提到)

如何向自定义组件内传递数据?

在页面customComponentswxml中,以标签的形式,援用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接管,而reactthis.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拿到子组件中的数据

前提条件

须要在父组件的援用自定义组件上,增加classid

例如:在count组件上增加了一个classcount

<count  class="count"  count="{{countNum}}"  bind:changeCount="handleCount"></count>

那么,在父组件中的handleCount中里调用 this.selectComponent,获取子组件的实例数据

调用时须要传入一个匹配选择器 selectorclassId都能够,如,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数据    })  }

这种办法也是能够的,在小程序当中也很罕用

如何禁止掉viewbindtap事件?

在做数字加减输入框时,对于减到某个数值时,想要禁用状态,遇到相似的状况时,要么把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/