微信小程序自定义组件详解

自定义组件能够帮我们更好的复用代码和重构简化代码复杂度。一起来学习一下小程序自定义组件的内容吧。

从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。

开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似

总览

一、Component概念

Component像页面一样由wxml、wxss、js和json4个文件组成,且需要把这4个文件放在同一个目录中。与页面不一样的是,Component中的构造函数(也可以称构造器)是Component({}),而页面中的构造函数是Page({})。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将component字段设为true可这一组文件设为自定义组件):

{
  "component": true
}

slot

Component的slot(slot意思是插槽),主要是让你在外部的wxml可以自由的在你的Component的wxml里插入模块。默认情况下,一个组件的wxml只可能有一个slot。需要使用多个时,可以在组件js中声明启用。

Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },
  properties: { /* ... */ },
  methods: { /* ... */ }
})

此时,可以在这个组件的wxml中使用多个slot,以不同的 name 来区分。

<!-- 组件模板 -->
<view class="wrapper">
  <slot name="before"></slot>
  <view>这里是组件的内部细节</view>
  <slot name="after"></slot>
</view>

使用时,用 slot 属性来将节点插入到不同的slot上。

<!-- 引用组件的页面模板 -->
<view>
  <component-tag-name>
    <!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 -->
    <view slot="before">这里是插入到组件slot name="before"中的内容</view>
    <!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 -->
    <view slot="after">这里是插入到组件slot name="after"中的内容</view>
  </component-tag-name>
</view>

组件样式编写注意事项

  • 组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。
  • 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。
  • 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
  • 继承样式,如 font 、 color ,会从组件外继承到组件内。
  • 除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效。
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */

外部样式类

使用外部样式类可以让组件使用指定的组件外样式类,如果希望组件外样式类能够完全影响组件内部,可以将组件构造器中的options.addGlobalClass字段置为true。

/* 组件 custom-component.js */
Component({
  externalClasses: ['my-class']
})
<!-- 组件 custom-component.wxml -->
<custom-component class="my-class">这段文本的颜色由组件外的 class 决定</custom-component>
/* 组件外的样式定义 */
.red-text {
  color: red;
}

创建一个组件

一个组件需要包括json、wxml、wxss、js四个文件组成,下面我们先看看一个简单的入门:

<!--components/component/component.wxml-->
<view class="inner">
    {{innerText}}
</view>
<slot></slot>

编写JS文件,组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的

// components/component/component.js
Component({
    /**
     * 组件的属性列表
     */
    properties: {
        innerText: {
            type: String,
            value: 'hello world'
        },
        myProperties:String
    },

    /**
     * 组件的初始数据
     */
    data: {

    },

    /**
     * 组件的方法列表
     */
    methods: {

    }
})

设置字体的颜色

/* components/component/component.wxss */
.inner{color: red;}

完成对组件的初始化,包括设置属性列表,初始化数据,以及设置相关的方法。

使用自定义组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:

{
  "usingComponents": {
    "component": "/components/component/component"
  }
}

在page页面下添加声明过的自定义组件:

<component></component>
<view>
  <component>
    <!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
    <view>这里是插入到组件slot中的内容</view>
  </component>
</view>

上方的是一个最简单的自定义组件。

注意事项:

1.对于基础库的1.5.x版本, 1.5.7 也有部分自定义组件支持。

2.因为WXML节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。

3.自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。

4.自定义组件和使用自定义组件的页面所在项目根目录名不能以“wx-”为前缀,否则会报错。

5.旧版本的基础库不支持自定义组件,此时,引用自定义组件的节点会变为默认的空节点。

Component构造器

使用component构造器,进行构造。 该构造函数用于定义组件。调用Component函数能指定组件的数据,属性和方法。来看看这个完整的列表代码含义:

Component({

  behaviors: [],

  properties: {
    myProperty: { // 属性名
      type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
      value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个
      observer: function (newVal, oldVal) {
          this._propertyChange(newVal, oldVal);
       } // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'
      
    },
    myProperty2: String // 简化的定义方式
  },
  data: {
    A: [{
      B: 'init data.A[0].B'
    }]
  }, // 私有数据,可用于模版渲染

  lifetimes: {
    // 生命周期函数,可以为函数,或一个在methods段中定义的方法名
    attached: function () { },
    moved: function () { },
    detached: function () { },
  },

  // 生命周期函数,可以为函数,或一个在methods段中定义的方法名
  attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖
  ready: function() { },

  pageLifetimes: {
    // 组件所在页面的生命周期函数
    show: function () { },
  },

  methods: {
    onMyButtonTap: function () {
      this.setData({
        // 更新属性和数据的方法与更新页面数据的方法类似
        myProperty: 'Test'
      })
    },
    _myPrivateMethod: function () {
      // 内部方法建议以下划线开头
      this.replaceDataOnPath(['A', 0, 'B'], 'myPrivateData') // 这里将 data.A[0].B 设为 'myPrivateData'
      this.applyDataUpdates()
    },
    _propertyChange: function (newVal, oldVal) {
        console.log(newVal);
        console.log(oldVal);
    }
  }

})

组件与数据通信

组件间的通信方法有以下几种:

  • WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 2.0.9 开始,还可以在数据中包含函数)。具体在 组件模板和样式 章节中介绍。
  • 事件:用于子组件向父组件传递数据,可以传递任意数据。
  • 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。

通过在页面中给组件加了一个id值,这样子我们就能查到组件的方法了。

<compontent id="modal"></compontent>

/*js*/
var modal = this.setlectComponet('#modal');

这样子就能在外面调用组件里面的任意数据和方法了。

  • properties:主页面传入数据到组件,相当于vue的props,是传入外部数据的入口。
  • data:则用于组件的内部数据变化,外部数据没法初始化

在 properties 定义段中,属性名采用驼峰写法(propertyName);在 wxml 中,指定属性值时则对应使用连字符写法(component-tag-name property-name=”attr value”),应用于数据绑定时采用驼峰写法(attr=”{{propertyName}}”)

传入的数据,不管是简单数据类型,还是引用类型,都如同值复制一样

方法函数调用

methods:需要在组件中调用的方法,都写在这个对象里面。跟Page中的对象里面的方法同级。

生命周期:可单独某个生命周期放在Components下(旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容),也可以放在lifetimes,如果两个地方有同名生命周期,则lifetimes里面的方法会覆盖前者。

Component({
  lifetimes: {
    attached: function() {
      // 在组件实例进入页面节点树时执行
    },
    detached: function() {
      // 在组件实例被从页面节点树移除时执行
    },
  },
  // 以下是旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容
  attached: function() {
    // 在组件实例进入页面节点树时执行
  },
  detached: function() {
    // 在组件实例被从页面节点树移除时执行
  },
  // ...
})

组件所在的生命周期

还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义。其中可用的生命周期包括:

生成的组件实例可以在组件的方法、生命周期函数和属性 observer 中通过 this 访问。组件包含一些通用属性和方法

image.png

组件传出数据到主页面

组件间交互的主要形式是自定义事件。

组件通过 this.triggerEvent() 触发自定义事件,主页面在组件上 bind:myevent=”onMyEvent” 来接收自定义事件。

其中,this.triggerEvent() 方法接收自定义事件名称外,还接收两个对象,eventDetail 和 eventOptions。

<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({
  properties: {}
  methods: {
    // 子组件触发自定义事件
    ontap () {
    // 所有要带到主页面的数据,都装在eventDetail里面
    var eventDetail = {
            name:'sssssssss',
            test:[1,2,3]
    }
    // 触发事件的选项 bubbles是否冒泡,composed是否可穿越组件边界,capturePhase 是否有捕获阶段
    var eventOption = {
            composed: true
    }
    this.triggerEvent('myevent', eventDetail, eventOption)
    }
  }
})

触发的事件包括:

 

监听事件

自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。监听自定义组件事件的方法与监听基础组件事件的方法完全一致:

在Page事件中监听组件中传递过来的值。

Page({
  onMyEvent: function(e){
    e.detail // 自定义组件触发事件时提供的detail对象
  }
})

behaviors

behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。

每个 behavior 可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior 。 behavior 也可以引用其他 behavior 。

// my-behavior.js
module.exports = Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String
    }
  },
  data: {
    myBehaviorData: {}
  },
  attached: function(){},
  methods: {
    myBehaviorMethod: function(){}
  }
})

组件引用时,在 behaviors 定义段中将它们逐个列出即可。

// my-component.js
var myBehavior = require('my-behavior')
Component({
  behaviors: [myBehavior],
  properties: {
    myProperty: {
      type: String
    }
  },
  data: {
    myData: {}
  },
  attached: function(){},
  methods: {
    myMethod: function(){}
  }
})

字段的覆盖和组合规则

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:

如果有同名的属性或方法,组件本身的属性或方法会覆盖 behavior 中的属性或方法,如果引用了多个 behavior ,在定义段中靠后 behavior 中的属性或方法会覆盖靠前的属性或方法;
如果有同名的数据字段,如果数据是对象类型,会进行对象合并,如果是非对象类型则会进行相互覆盖;
生命周期函数不会相互覆盖,而是在对应触发时机被逐个调用。如果同一个 behavior 被一个组件多次引用,它定义的生命周期函数只会被执行一次。

内置behavior

组件间关系

<custom-ul>
  <custom-li> item 1 </custom-li>
  <custom-li> item 2 </custom-li>
</custom-ul>

这个例子中, custom-ul 和 custom-li 都是自定义组件,它们有相互间的关系,相互间的通信往往比较复杂。此时在组件定义时加入 relations 定义段,可以解决这样的问题。示例:

// path/to/custom-ul.js
Component({
  relations: {
    './custom-li': {
      type: 'child', // 关联的目标节点应为子节点
      linked: function(target) {
        // 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后
      },
      linkChanged: function(target) {
        // 每次有custom-li被移动后执行,target是该节点实例对象,触发在该节点moved生命周期之后
      },
      unlinked: function(target) {
        // 每次有custom-li被移除时执行,target是该节点实例对象,触发在该节点detached生命周期之后
      }
    }
  },
  methods: {
    _getAllLi: function(){
      // 使用getRelationNodes可以获得nodes数组,包含所有已关联的custom-li,且是有序的
      var nodes = this.getRelationNodes('path/to/custom-li')
    }
  },
  ready: function(){
    this._getAllLi()
  }
})
// path/to/custom-li.js
Component({
  relations: {
    './custom-ul': {
      type: 'parent', // 关联的目标节点应为父节点
      linked: function(target) {
        // 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后
      },
      linkChanged: function(target) {
        // 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后
      },
      unlinked: function(target) {
        // 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后
      }
    }
  }
})

更多:

  • 关联一类组件
  • 抽象节点
  • 自定义组件扩展
  • 开发第三方自定义组件

制作一个弹窗组件

https://developers.weixin.qq.com/s/M55masmW764P

PPT:https://ppt.baomitu.com/d/baf2b116


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流