乐趣区

关于javascript:小程序实现自定义组件以及自定义组件间的通信

前言

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

页面的显示 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.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 数据, 就能够更新父组件中的初始化数据

这个关系尽管有点绕, 至于重要性显而易见.

相干链接

  • 组件间通信与事件
  • 小程序中的事件
  • 原文出处 -https://coder.itclan.cn/
退出移动版