30

小程序-实现自定义组件以及自定义组件间的通信

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI4OTc3NDgzNQ%3D%3D&%3Bmid=2247487629&%3Bidx=1&%3Bsn=fed2e9873bd780bae94f2e2bc7b43e6e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

虽互不曾谋面,但希望能和您成为笔尖下的朋友

以读书,技术,生活为主,偶尔撒点鸡汤

不作,不敷衍,意在真诚吐露,用心分享

点击 左上方 ,可关注本刊

标星公众号( ID:itclanCoder

如果不知道如何操作

点击这里, 标星不迷路

Y3iUjyn.jpg!mobile

前言

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

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

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

实例效果

YNVnUjI.gif!mobile

通过上面一个简单的数字加减输入框组件,阅读完本文后,您将收获到

  • 在小程序中如何自定义组件

  • 在小程序页面中如何使用自定义组件

  • 父(外部)组件如何向子组件传值

  • 子组件如何接受父组件传递过来的值,同时渲染组件

  • 子组件内如何进行事件交互,如何向父组件传递数据,影响父组件定义的数据

  • 另一种方法父组件获取子组件的数据(非 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.js
Component({
  /**
   * 组件的属性列表
   */
  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.js
Page({
  /**
   * 页面的初始数据
   */
  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.js
Component({
  /**
   * 组件的属性列表
   */
  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.js
Component({
  /**
   * 组件的属性列表
   */
  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.js
Component({
  /**
   * 组件的属性列表
   */
  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 数据,就可以更新父组件中的初始化数据

这个关系虽然有点绕,至于重要性不言而喻.如果您有任何疑问

关注微信 itlanCoder 公众号,在文章下方留言,一起学习谈论,欢迎添加微信 suibichuanji 进行咨询

相关链接

  • 组件间通信与事件

  • https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html

  • 小程序中的事件

  • https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html

quAjQzu.jpg!mobile

记账就用轻记账

ymAfqy.jpg!mobile

公众号(ID:itclanCoder)

码能让您早脱菜籍,文能让您洗净铅华

   可能您还想看更多:

yYvA7zi.jpg!mobile

iAzyUvf.jpg!mobile

都看完了 ZbmyimZ.gif!mobile不点个在看吗

IzAJvqE.png!mobile

点击阅读原文了解更多


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK