小程序学习:自定义组件

本文详细介绍了小程序自定义组件的创建与使用,包括数据传递、事件处理、生命周期、属性监听、组件通信等方面,旨在帮助开发者更好地理解和运用自定义组件进行代码复用和维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

本文要创建一个效果如下图的组件,由一个心形图片和右上角的数字构成。
在这里插入图片描述

1、创建自定义组件

(1)首先在根目录下新建文件夹components,components下新建like文件夹,like下新建index页面;
(2)在 index.json 文件中进行自定义组件声明:

{
  "component": true
}

(3)在 index.wxml 文件中编写组件模板,在 index.wxss 文件中加入组件样式,它们的写法与页面的写法类似;

编写组件样式时,需要注意以下几点:

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

(4)在index.js 文件中,需要使用 Component 来注册组件,并提供组件的属性定义、内部数据和自定义方法:

Component({
  behaviors: [],
  
  // 属性定义(详情参见下文)
  properties: {
    myProperty: { // 属性名
      type: String,//类型(必填)
      value: ''//属性初始值(选填),默认布尔值初始为false,数字初始为0
      observer: function(newVal, oldVal, changedPath){
			//属性值变化时的回调函数(选填),也可以写成在methods段中定义的方法名字符串,如'_propertyChange'
			//newVal是新设置的数据,oldVal是旧数据
		}
    },
    myProperty2: String // 简化的定义方式
  },

  data: {}, // 私有数据,可用于模板渲染

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

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

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

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

    }
  }

})

下面具体实现。

  • index.wxml:
<view class="container" bind:tap="onLike">
  <image src="{{like?yes_url:no_url}}" />
   <text>{{count}}</text> 
</view>

组件中最好不要留有无意义的间距,例如文字的行间距,设置line-height为文字大小可消除行间距。

  • index.wxss:
.container{
  display: flex;
  flex-direction: row;
  /* 必须指定宽度,否则会出现移动 */
  /* width:120rpx;  */
  padding:10rpx;
}

.container text{
  font-size:24rpx;
  font-family: "PingFangSC-Thin";//苹果手机的默认字体是“苹方”,而安卓是“思源”。
  color: #bbbbbb;
  line-height:24rpx;//用于消除文字的上下间距
  position:relative;//相对定位
  bottom:10rpx;
  left:6rpx; 
}

.container image{
  width:32rpx;
  height:28rpx;
}
  • index.js:
Component({
  properties: {
    like: Boolean,
    count: Number,
    readOnly:Boolean
  },

  data: {
    yes_url: 'images/like.png',
    no_url: 'images/like@dis.png'
  },

  methods: {
    onLike: function (event) {
      if(this.properties.readOnly){
        return
      }
      let count = this.properties.count
      count = this.properties.like ? count - 1 : count + 1
      this.setData({
        count: count,
        like: !this.properties.like
      })
      let behavior = this.properties.like ? 'like' : 'cancel'
      this.triggerEvent('like', {
        behavior: behavior
      }, {})
    }
  }
})

properties中定义的属性是需要从外部,比如服务器获取的数据;
而data中的数据是从本地加载的,或者是不需要在外部改变的;
但是最终小程序会将properties和data中的数据指向同一个JavaScript对象。

2、使用自定义组件

在需要使用自定义组件的页面的json文件中定义:

{
  "usingComponents": {
    "like-cmp": "/components/like/index",
  }
}

like-cmp是组件的名字。
然后在wxml文件中使用:

<like-cmp bind:like="onLike" like="{{like}}" count="{{count}}" />

自定义组件中的data里的数据是私有的,不能在外部更改,只能被组件自身的wxml文件使用;而properties中的属性可以在外部更改。

3、数据传递的流程

在这里插入图片描述
(1)数据从服务器传递到页面的js文件;
(2)通过setData将数据绑定到页面的wxml文件中;
(3)由于使用了自定义组件,数据通过设置组件属性值的方式传递到组件的wxml中。

4、自定义事件的激活与监听

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html
如果在外部某个页面使用自定义组件时,要给该组件添加监听事件,则该事件的返回值里没有组件的数据。这就需要在自定义组件里的methods中的点击事件中触发一个自定义事件like。

  methods: {
    onLike: function (event) {
      if(this.properties.readOnly){
        return
      }
      let count = this.properties.count
      count = this.properties.like ? count - 1 : count + 1
      this.setData({
        count: count,
        like: !this.properties.like
      })
      let behavior = this.properties.like ? 'like' : 'cancel'
      this.triggerEvent('like', {
        behavior: behavior
      }, {})
    }
  }

其中triggerEvent(’’,{},{})用来触发事件behavior,三个参数指定事件名、detail对象和事件选项;
这时,在外部使用自定义组件时就能得到组件中like属性的值,:

<like-cmp bind:like="onLike" class="like" like="{{like}}" count="{{count}}" />

like的值在event.detail中:
在这里插入图片描述

  onLike:function(event){
    let like_or_cancel = event.detail.behavior
  },

5、组件的生命周期函数

最重要的生命周期是 created、attached、detached ,包含一个组件实例生命流程的最主要时间点。

created:在组件实例刚刚被创建时执行
attached:在组件实例进入页面节点树时执行
ready:在组件在视图层布局完成后执行
moved :在组件实例被移动到节点树另一个位置时执行
detached:在组件实例被从页面节点树移除时执行
error: 每当组件方法抛出错误时执行

6、属性值变化时的回调函数observer

属性值的改变情况可以使用 observer 来监听。目前,在新版本基础库中不推荐使用这个字段,而是使用 Component 构造器的 observers 字段代替,它更加强大且性能更好。
数据监听器详见:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html

比如某一自定义组件要监听来自服务器的数据index,如果index为0~9,要在index前加0,否则不变:

 properties: {
    index:{
      type: Number,
      observer:function(newVal, oldVal, changedPath){
        if (newVal < 10) {
          this.setData({
            _index: '0' + newVal
          })
        }
      }
    }
  },

  /**
   * 组件的初始数据, data 的值也会被页面绑定, 但data的值不可以从组件外部设置
   */
  data: {
      _index:String//用来接收改变后的index值
  }

但是,千万不要在observer函数中修改自身的属性,否则就会无限递归
应该重新设置一个变量:_index。
也可以这样写:

 properties: {
    index:{
      type: Number,
      observer:'func'
        }
      }
    }
  },

  /**
   * 组件的初始数据, data 的值也会被页面绑定, 但data的值不可以从组件外部设置
   */
  data: {
      _index:String//用来接收改变后的index值
  }
  methosd: {
  func(newVal, oldVal, changedPath){
        if (newVal < 10) {
          this.setData({
            _index: '0' + newVal
          })
   }

事件在组件中的传递:

在这里插入图片描述
如果组件里还有组件,即组件嵌套,则当点击页面的组件时,事件响应会从最底层的组件逐级向上传递,最后传给页面的响应函数。

7、组建的behavior行为

behavior可以实现组件的继承机制。
定义方式和组件一样,把组件Component关键字换成Behavior,可以把几个组件共有的属性、方法等放在一个behavior里.

定义Behavior:

let classicBehavior = Behavior({
  properties: {
    type:String,
    img:String,
    content:String
  },
  data: {
  }
})

export { classicBehavior }

继承Behavior:
properties、data、methods、生命周期函数都可以被组件继承。
在需要继承Behavior的组件里导入:

import {classicBehavior} from '../classic-beh.js'
Component({
  /**
   * 组件的属性列表
   */
  behaviors:[classicBehavior],//若要继承多个Behavior,以逗号分隔
  
  properties: {

  },

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

  },

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

  }
})

说明:
(1)组件继承符合一般继承规则,如果子类和父类有同名属性,子类属性会覆盖父类属性。

(2)多继承时,且子类没有同名属性而几个父类之间有同名属性是,写在 behaviors:[a, b, c]括号中最后一个会覆盖其他的。

(3)生命周期函数不会有覆盖情况,小程序会依次执行父类的生命周期函数,再执行子类的生命周期函数。

8、组件的hidden属性

当需要组件切换显示/隐藏时,可以使用wx:if条件渲染,也可以给组件加hidden属性
wx:if vs hidden

因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。

同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。

相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。

一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。

因此一般使用hidden更好。
但是在自定义组件里使用hidden无效。
所以为了让自定义组件也能使用hidden,可以在组件的properties里加入hidden属性,然后在组件的wxml外部的view标签内添加hidden属性:

  properties: {
	hidden:flase
  },
<view hidden={{hidden}} class="classic-container">
  <image src="{{img}}" class="classic-img"></image>
  <image class='tag' src="images/essay@tag.png" />
  <text class="content">{{content}}</text>
</view>

在使用该组件时:
wx:if 写法:

<movie-cmp wx:if="{{classic.type==100}}" img="{{classic.image}}" content="{{classic.content}}" />

hidden写法:

<movie-cmp hidden="{{classic.type!=100}}" img="{{classic.image}}" content="{{classic.content}}" />

注:如果使用hidden属性,组件不会完整的执行一次生命周期,例如组件生命周期的detach()函数不会触发。
所以如果要执行detach()函数,还应该使用wx:if

9、组件中样式的复用

如果几个组件有相同的样式,则可以通过@import的方式导入共有的样式,实现样式的复用。这是template里的做法。
例如:

@import "../common.wxss";

后面一定要加封号。

10、组件间的通信

父子组件间的基本通信方式有以下几种。

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

11、点击组件进行页面跳转

点击组件进行页面跳转的事件函数可以直接写在组件里,而不用写在Page页面里,这样就不用在组件和页面之间传递参数了。
例如对于组件book,其bindtap事件写在methods里:

  methods: {
    onTap(event){
      const bid = this.properties.book.id
      wx.navigateTo({
        url:`/pages/book-detail/book-detail?bid=${bid}`
      })
      // 降低了组件的通用性
      // 非常方便
      // 服务于当前的项目 项目组件
      // 
    }
  }

12、组件wxml的slot(插槽)

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html
在组件的wxml中可以包含 slot 节点,用于承载组件使用者提供的wxml结构。

默认情况下,一个组件的wxml中只能有一个slot。需要使用多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>

其中,slot的样式可以写在页面的wxss里。
例如,将下图的tag组件后面加上数字:
在这里插入图片描述
组件wxml:

<view class="container tag-class ">
    <slot name="before"></slot>
    <text >{{text}}</text>
    <slot name="after"></slot>
</view>

页面wxml:

 <v-tag tag-class="{{tool.highlight(index)}}" text="{{item.content}}">
    <text class="num" slot="after">{{'+'+item.nums}}</text>
 </v-tag>

页面wxss中定义slot样式:

.num {
    margin-left: 10rpx;
    font-size: 22rpx;
    color: #aaa;
}

13、外部样式类externalClass

有时,组件希望接受外部传入的样式类,比如从页面传入样式到组件。此时可以在 Component 中用 externalClasses 定义段定义若干个外部样式类。
组件的js文件:

Component({
  externalClasses: ['tag-class']
})

组件wxml:

<view class="container tag-class ">
    <slot name="before"></slot>
    <text >{{text}}</text>
    <slot name="after"></slot>
</view>

页面wxml:

 <v-tag tag-class="ex-tag" text="{{item.content}}">

页面wxss:

.ex-tag {
    background-color: #fffbdd;
}

注意:在同一个节点上使用普通样式类和外部样式类时,比如container和tag-class,两个类的优先级是未定义的,因此最好避免这种情况。

可以使用!important来使外部样式强制覆盖普通样式
页面wxss:

.ex-tag {
    background-color: #fffbdd !important;
}

14、区分同一页面下的多个相同组件的点击事件

当一个页面加载了多个相同的组件,当点击其中一个,需要一个id来判断用户点击了哪个组件。
在这里插入图片描述
在页面的XML中,给每个组件加入一个data-id:

<van-button data-id="{{item._id}}" size="small" type='primary' plain bind:click='viewItem'>详情</van-button>

这样在每个组件的点击事件中就可以取出该id,从而知道用户点击了哪个组件的按钮:

  viewItem:function(event){
      //console.log(event);
      var id = event.currentTarget.dataset.id;
      wx.navigateTo({
        url: '../bookDetail/bookDetail?id='+id,
      })
  },
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值