微信小程序——自定义组件

2022/2/14 1:14:40

本文主要是介绍微信小程序——自定义组件,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

文章目录

  • 自定义组件
    • 创建自定义组件
    • 使用自定义组件
    • 注意事项
  • 数据传递
    • 父组件向子组件传递数据
    • 子组件向父组件传递数据
    • 获取子组件实例对象
  • 组件 wxml 的 slot
  • Component 构造器(组件 js 方法属性)
    • properties 定义
    • observers 数据监听器
    • behaviors
    • created
    • attached
    • detached
    • pageLifetimes
    • lifetimes
  • 生命周期
    • 组件间关系(relations 定义段)
  • 占位组件

 
 

  

自定义组件

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

创建自定义组件

  类似于页面,一个自定义组件由 json、wxml、wxss、js 这 4 个文件组成。
  要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):

{
  "component": true
}

也可以通过右键之间创建 componet 文件会直接创建 json、wxml、wxss、js 这 4 个文件。

  • 这样创建的 js 文件中的方法都会是组件形式的。
  • json 文件“component”也会设置为 true。

创建
同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式。

  • wxss 中的样式只应用于这个自定义组件(在组件 wxss 中不应使用 ID 选择器、属于选择器和标签名选择器)
  • 组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。

 
 
 
 

使用自定义组件

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

{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"
  }
}

  这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

<view>
  <!-- 以下是对一个自定义组件的引用 -->
  <component-tag-name></component-tag-name>
</view>

  自定义组件的 wxml 节点结构在与数据结合之后,将被插入到引用位置内。

注意事项

  • 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
  • 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
  • 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。

注意,是否在页面文件中使用 usingComponents 会使得页面的 this 对象的原型稍有差异,包括:

  • 使用 usingComponents 页面的原型与不使用时不一致,即 Object.getPrototypeOf(this) 结果不同。
  • 使用 usingComponents 时会多一些方法,如 selectComponent 。
  • 出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj }) 后 this.data.field === obj 。(深复制会在这个值被组件间传递时发生。)
  • 如果页面比较复杂,新增或删除 usingComponents 定义段时建议重新测试一下。

 
 
 
 

 
 
 
 

数据传递

父组件向子组件传递数据

父组件(页面)向子组件传递数据通过标签属性的方式来传递。

parent.wxml

<parent name="{{name}}" age="{{age}}"></parent>

parent.js

data:{
	name: 'qw',
	age: 22
}

child.js

properties:{
	name:{
		type: String,
		value: 'qy'
	},
	age: Number
}

  父组件向子组件传值以属性的形式,子组件以 properties 接收,并可指定数据类型 type 以及默认值 value。在 wxml 里可直接以 {{name}} 的形式使用,而在 js 中以 this.properties.name 获取。
 
 
 
 

子组件向父组件传递数据

子组件向父组件传递数据,通过事件的方式传递:

  1. 在子组件的标签上加入一个自定义事件;
  2. 子组件通过 this.triggerEvent 方法调用自定义事件,传递数据。

子组件向父组件传递数据使用 this.triggerEvent 方法,这个方法接受 3 个参数:

thiis.triggerEvent(‘myevent’, myEventDetail, myEventOption);

  • myevent:为方法名。
  • myEventDetail:传到组件外的数据。
  • myEventOption:具有 3 个参数:
    ① bubbles——Boolean 类型——非必填——默认 false——作用:事件是否冒泡
    ② composed——Boolean 类型——非必填——默认 false——作用:事件是否可以穿越组件边界,为 false 时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部。
    ③ capturePhase——Boolean 类型——非必填——默认 false——作用:事件是否拥有捕获阶段。

 
事件分为冒泡事件非冒泡事件

  1. 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
  2. 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。

捕获阶段:捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。
在下面的代码中,点击 inner view 会先后调用handleTap2、handleTap4、handleTap3、handleTap1。

<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2。

<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

 
 
child.js

//child.js
methods: {
	changeName() {
		this.triggerEvent('changeName', {name: 'yj'});
	}
}

parent.wxml

<parent name="{{name}}" age="{{age}}" bindchangeName="changeName"></parent>

parent.js

//parent.js
changeName(event) {
	console.log(event.detail);
	//{name: 'yj'}
}

 
 
 
 

获取子组件实例对象

可在父组件里调用 this.selectComponent,获取子组件的实例对象。
调用时需要传入一个匹配选择器 selector,如:this.selectComponent(".my-component")。

 
selector 语法
selector类似于 CSS 的选择器,但仅支持下列语法。

  • ID选择器:#the-id
  • class选择器(可以连续指定多个):.a-class.another-class
  • 子元素选择器:.the-parent > .the-child
  • 后代选择器:.the-ancestor .the-descendant
  • 跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant
  • 多选择器的并集:#a-node,.some-other-nodes
// 父组件
Page({
  data: {},
  getChildComponent: function () {
    const child = this.selectComponent('.my-component');
    console.log(child)
  }
})

在上例中,父组件将会获取 class 为 my-component 的子组件实例对象,即子组件的 this 。
注意:默认情况下,小程序与插件之间、不同插件之间的组件将无法通过 selectComponent 得到组件实例(将返回 null)。如果想让一个组件在上述条件下依然能被 selectComponent 返回,可以自定义其返回结果(见下)。

需要自定义 selectComponent 返回的数据,可使用内置 behavior: wx://component-export
使用该 behavior 时,自定义组件中的 export 定义段将用于指定组件被 selectComponent 调用时的返回值。

// 自定义组件 my-component 内部
Component({
  behaviors: ['wx://component-export'],
  export() {
    return { myField: 'myValue' }
  }
})

父组件调用
const child = this.selectComponent(’#the-id’) // 等于 { myField: ‘myValue’ }
父组件获取 id 为 the-id 的子组件实例的时候,得到的是对象 { myField: ‘myValue’ } 。
结果

 
 
 
 

 
 
 
 

组件 wxml 的 slot

  slot 标签其实就是一个占位符(插槽),等到父组件调用子组件的时候再传递标签过来,最终这些被传递的标签就会替换 slot 插槽的位置。
默认情况下,一个组件的 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>

 
 
 
 

 
 
 
 

Component 构造器(组件 js 方法属性)

定义段类型是否必填描述
propertiesObject Map组件的对外属性,是属性名到属性设置的映射表
dataObject组件的内部数据,和 properties 一同用于组件的模板渲染
observersObject组件数据字段监听器,用于监听 properties 和 data 的变化
methodsObject组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用
behaviorsString Array类似于mixins和traits的组件间代码复用机制
createdFunction组件生命周期函数-在组件实例刚刚被创建时执行,注意此时不能调用 setData )
attachedFunction组件生命周期函数-在组件实例进入页面节点树时执行)
readyFunction组件生命周期函数-在组件布局完成后执行)
movedFunction组件生命周期函数-在组件实例被移动到节点树另一个位置时执行)
detachedFunction组件生命周期函数-在组件实例被从页面节点树移除时执行)
lifetimesObject组件生命周期声明对象
pageLifetimesObject组件所在页面的生命周期声明对象

properties 定义

定义段类型是否必填描述
type属性的类型
optionalTypesArray属性的类型(可以指定多个)
属性的类型可以为 String Number Boolean Object Array 其一,也可以为 null 表示不限制类型
多数情况下,属性最好指定一个确切的类型
value属性的初始值
observerFunction属性值变化时的回调函数(不推荐使用,用 Component 构造器的 observers)
Component({
  properties: {
    min: {
      type: Number,
      value: 0
    },
    min: {
      type: Number,
      value: 0,
      observer: function(newVal, oldVal) {
        // 属性值变化时执行
      }
    },
    lastLeaf: {
      // 这个属性可以是 Number 、 String 、 Boolean 三种类型中的一种
      type: Number,
      optionalTypes: [String, Object],
      value: 0
    }
  }
})

 
 
 
 

observers 数据监听器

数据监听器可以用于监听和响应任何属性和数据字段的变化。

Component({
  attached: function() {
    this.setData({
      numberA: 1,
      numberB: 2,
    })
  },
  observers: {
    'numberA, numberB': function(numberA, numberB) {
      // 在 numberA 或者 numberB 被设置时,执行这个函数
      this.setData({ // this.data.sum 永远是 this.data.numberA 与 this.data.numberB 的和
        sum: numberA + numberB
      })
    }
  }
})

数据监听器支持监听属性或内部数据的变化,可以同时监听多个。一次 setData 最多触发每个监听器一次。
同时,监听器可以监听子数据字段。(① 使用通配符 **,监听所有子数据字段的变化;② 使用通配符 **可以监听全部 setData 。)

Component({
  observers: {
    'some.subfield': function(subfield) {
      // 使用 setData 设置 this.data.some.subfield 时触发
      // (除此以外,使用 setData 设置 this.data.some 也会触发)
      subfield === this.data.some.subfield
    },
    'arr[12]': function(arr12) {
      // 使用 setData 设置 this.data.arr[12] 时触发
      // (除此以外,使用 setData 设置 this.data.arr 也会触发)
      arr12 === this.data.arr[12]
    },
    'some.field.**': function(field) { // 使用通配符 **,监听所有子数据字段的变化
      // 使用 setData 设置 this.data.some.field 本身或其下任何子数据字段时触发
      // (除此以外,使用 setData 设置 this.data.some 也会触发)
      field === this.data.some.field
    },
    '**': function() {
      // 每次 setData 都触发
    },
  }
})

注意事项

  • 数据监听器监听的是 setData 涉及到的数据字段,即使这些数据字段的值没有发生变化,数据监听器依然会被触发。
  • 如果在数据监听器函数中使用 setData 设置本身监听的数据字段,可能会导致死循环,需要特别留意。
  • 数据监听器和属性的 observer 相比,数据监听器更强大且通常具有更好的性能。

 
 
 
 

behaviors

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

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

 
 
 
 

created

组件实例刚刚被创建好时, created 生命周期被触发。此时,组件数据 this.data 就是在 Component 构造器中定义的数据 data 。 此时还不能调用 setData 。 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。

 
 
 
 

attached

在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。

 
 
 
 

detached

在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发。

 
 
 
 

pageLifetimes

生命周期参数描述
show组件所在的页面被展示时执行
hide组件所在的页面被隐藏时执行
resizeObject Size组件所在的页面尺寸变化时执行
Component({
  pageLifetimes: {
    show: function() {
      // 页面被展示
    },
    hide: function() {
      // 页面被隐藏
    },
    resize: function(size) {
      // 页面尺寸变化
    }
  }
})

 
 
 
 

lifetimes

组件的的生命周期也可以在 lifetimes 字段内进行声明(这是推荐的方式,其优先级最高)。

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

 
 
 
 

 
 
 
 

生命周期

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

 
 
 
 

 
 
 
 

组件间关系(relations 定义段)

relations 定义段包含目标组件路径及其对应选项,可包含的选项见下表。

选项类型是否必填描述
typeString目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant
linkedFunction关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后
linkChangedFunction关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后
unlinkedFunction关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后
targetString如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联
<custom-ul>
  <custom-li> item 1 </custom-li>
  <custom-li> item 2 </custom-li>
</custom-ul>
// path/to/custom-ul.js
Component({
  relations: {
    './custom-li-component': {
      type: 'child', // 关联的目标节点应为子节点
      linked: function (target) {
        // 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后
        console.log('[custom-ul] a child is linked: ', target)
      },
      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('./custom-li-component')
      console.log(nodes)
    }
  },
  ready: function () {
    this._getAllLi()
  }
})
// path/to/custom-li.js
Component({
  relations: {
    './custom-ul-component': {
      type: 'parent', // 关联的目标节点应为父节点
      linked: function (target) {
        // 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后
        console.log('child linked to ', target)
      },
      linkChanged: function (target) {
        // 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后
      },
      unlinked: function (target) {
        // 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后
      }
    }
  }
})

注意:必须在两个组件定义中都加入relations定义,否则不会生效。
运行结果
 
 
 
 

 
 
 
 

占位组件

在使用如 分包异步化 或 用时注入 等特性时,自定义组件所引用的其他自定义组件,在刚开始进行渲染时可能处于不可用的状态。此时,为了使渲染过程不被阻塞,不可用的自定义组件需要一个 「占位组件」(Component placeholder)。基础库会用占位组件替代不可用组件进行渲染,在该组件可用后再将占位组件替换回该组件。

一个自定义组件的占位组件可以是另一个自定义组件、或一个内置组件。

页面或自定义组件对应的 JSON 配置中的 componentPlaceholder 字段用于指定占位组件,如:

{
  "usingComponents": {
    "comp-a": "../comp/compA",
    "comp-b": "../comp/compB",
    "comp-c": "../comp/compC"
  },
  "componentPlaceholder": {
    "comp-a": "view",
    "comp-b": "comp-c"
  }
}

该配置表示:

  • 组件 comp-a 的占位组件为内置组件 view
  • 组件 comp-b 的占位组件为自定义组件 comp-c(其路径在 usingComponents 中配置)
<button ontap="onTap">显示组件</button>
<comp-a wx-if="{{ visible }}">
  <comp-b prop="{{ p }}">text in slot</comp-b>
</comp-a>

小程序启动时 visible 为 false,那么只有 button 会被渲染;点击按钮后,this.setData({ visible: true }) 被执行,此时如果 comp-a, comp-b 均不可用,则页面将被渲染为:

<button>显示组件</button>
<view>
  <comp-c prop="{{ p }}">text in slot</comp-c>
</view>

comp-a 与 comp-b 准备完成后,页面被替换为:

<button>显示组件</button>
<comp-a>
  <comp-b prop="{{ p }}">text in slot</comp-b>
</comp-a>

注意事项

  1. 当一个组件被指定为占位组件时(如上例中的 comp-c),为其指定占位组件是无效的。可以理解为如果一个组件需要作为其他组件的占位组件,则它必须在一开始就是可用的;
  2. 目前自定义组件不可用的情况包括:
    1.使用分包异步化特性的情况下,引用了其他分包的组件,而对应分包还未下载;
    2.使用用时注入特性的情况下,该组件还未注入;
  3. 如果一个组件不可用,且其占位组件不存在,则渲染时会报错并抛出;
  4. 如果一个组件不存在,但为其指定了可用的占位组件,则占位组件可以被正常渲染,但后续尝试准备替换时会报错并抛出。


这篇关于微信小程序——自定义组件的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程