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

一、组件模版和样式

类似于页面,自定义组件拥有自己的 wxml 模板和 wxss 样式。

1、组件模版

组件模版和页面模版的写法相同。

完成的组件可以在页面中引用。

创建组件component-tag-name

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

在page页面中应用

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

引用的页面也可以理解成一个组件,称为父组件,被引用的组件component-tag-name是子组件

2、组件wxml的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>

3、组件样式

组件对应 wxss 文件的样式,只对组件wxml内的节点生效。

二、Component 构造器

Component 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法等。

Component({

  behaviors: [],
	//组件的对外属性,由父组件传入
  properties: {
    myProperty: { // 属性名
      type: String,
      value: ''
    },
    myProperty2: String // 简化的定义方式
  },
  //组件的内部数据
  data: {}, 
  //组件数据字段监听器,用户监听properties和data的变化
  observers: {}

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

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

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

})

三、组件间通信

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

  • WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据。(父传子)
  • 事件:用于子组件向父组件传递数据,可以传递任意数据。(子传父)
  • 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。

1、父传子

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

组件的属性 propApropB 将收到页面传递的数据。

2、子传父

子组件为:test-component

<!-- test-component.wxml -->
<button bind:tap="onTap">点击这个按钮将触发“myevent”事件</button>
//test-component.js
methods: {
  onTap: function () {
    var myEventDetail = {
      "name": "zhangsan"
    } // detail对象,提供给事件监听函数,用户传递参数
    var myEventOption = {} // 触发事件的选项
    this.triggerEvent('myevent', myEventDetail, myEventOption)
  }
}

父组件为:home

<!-- home.wxml -->
<test-component bind:myevent="onMyEvent"/>
//home.js
onMyEvent(e) {
  console.log(e);
  console.log(e.detail.name);	//zhangsan
}

ps: 触发事件 myEventOption 的选项包括:

选项名类型是否必填默认值描述
bubblesBooleanfalse事件是否冒泡
composedBooleanfalse事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部
capturePhaseBooleanfalse事件是否拥有捕获阶段

四、组件生命周期

组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。

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

1、created

组件实例刚刚被创建好时,created生命周期被触发。

此时,组件数据 this.data 就是在 Component 构造器中定义的数据 data此时还不能调用 setData

通常情况下,这个生命周期只应该用于给组件 this添加一些自定义属性字段。

2、attached

在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。

此时, this.data 已被初始化为组件的当前值。

这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。

3、detached

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

4、定义生命周期方法

生命周期方法可以直接定义在 Component 构造器的第一级参数中。

自小程序基础库版本 2.2.3 起,组件的的生命周期也可以在 lifetimes 字段内进行声明(这是推荐的方式,其优先级最高)。

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

5、组件所在页面的生命周期

这些特殊的生命周期和组件本身并没有很大的关系。但有时组件需要获知,以便组件内部处理。

pageLifetimes 定义段中定义。其中可用的生命周期包括:

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

五、behaviors

behaviors 是用于组件间代码共享的特性。

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

1、behavior

Behavior(Object object)

注册一个 behavior,接受一个 Object 类型的参数。

参数:Object object

定义段类型是否必填描述
propertiesObject Map同组件的属性
dataObject同组件的数据
methodsObject同自定义组件的方法
behaviorsString Array引入其它的 behavior
createdFunction生命周期函数
attachedFunction生命周期函数
readyFunction生命周期函数
movedFunction生命周期函数
detachedFunction生命周期函数

代码示例:

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

2、组件中使用

其他组件在引用时,在 behaviors 定义段中将他们引用即可。

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

my-component 引入 my-behavior 后 ,my-component 就有了 my-behavior 的所有属性、数据和方法(包括生命周期函数 attached)。

当组件触发 attached 生命周期时,会依次触发 my-behavior 中的 attached 生命周期函数和 my-component 中的 attached 生命周期函数

3、字段的覆盖规则

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

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

六、组件间关系

1、组件间关系

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

这种结构的组件关系十分熟悉,像swiperswiper-item,如果现在自定义这样的组件,他们之间的通信往往比较复杂,这时就可以在组件定义时加入 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生命周期之后
      }
    }
  }
})

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

七、数据监听器

数据监听器observers可以用于监听和响应 properties/data 的变化。

1、使用数据监听器

有时,在一些数据字段被 setData 设置时,需要执行一些操作。

例如,在 this.data.namethis.data.dialogVision 发生变化时,将其输出。

  properties: {
    dialogVision: {
      type: Boolean,
      value: false
    }
  },
  data: {
    "name": "zhangsan",
    "age": "18"
  },
  observers: {
    //监听this.data.name
    'name': function(name) {
      console.log(name + " name的值发生了变化");
    },
    //监听this.data.dialogVision
    'dialogVision': function (dialogVision) {
      console.log(dialogVision + " dialogVision的值发生了变化");
    }
  },
  methods: {
    changeName() {
      this.setData({
        name: "lisi",
      })
    }
  },

2、监听字段语法

数据监听器支持监听属性或者内部数据的变换,可以同时监听多个。

监听器也可以监听子数据字段。

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]
    },
  }
})

如果需要监听所有子数据字段的变化,可以使用通配符 **

Component({
  observers: {
    'some.field.**': function(field) {
      // 使用 setData 设置 this.data.some.field 本身或其下任何子数据字段时触发
      // (除此以外,使用 setData 设置 this.data.some 也会触发)
      field === this.data.some.field
    },
  },
  methods: {
    changeSome() {
      // 这样会触发上面的 observer
      this.setData({
        'some.field': { /* ... */ }
      })
      // 这样也会触发上面的 observer
      this.setData({
        'some.field.xxx': { /* ... */ }
      })
      // 这样还是会触发上面的 observer
      this.setData({
        'some': { /* ... */ }
      })
  	}
  }
})

特别地,仅使用通配符 ** 可以监听全部 setData 。

3、Bugs & Tips:

  • 组件的 properties 在使用时也会放在 data 属性中;

  • 数据监听器监听的是 setData 设置了的数据字段。即使这些数据字段的值没有发生变换,监听器也会触发;

  • 在数据监听器中不要使用setData 设置本身监听的字段,否则会造成死循环;

  • 组件的properties中有一个observer字段,也可以用于监听属性值的改变情况,但是其性能不如Component 构造器的 observers 好。

八、纯数据字段

有时候,一些 data 中的字段既不会显示在界面上,也不会传递给其他组件,仅仅只在当前组件内部使用。

这个时候,就可以指定这样的数据字段为“纯数据字段”,它们将只被记录在 this.data 中,而不参与任何界面渲染过程,有助于提升页面更新性能。 -->因为在该数据更新时,界面不需要重新渲染

1、指定纯数据字段

指定方法:在 options 定义段中指定 pureDataPattern 为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段。

Component({
  options: {
    pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
  },
  properties: {
    a: Boolean,
    _b: {
      type: Boolean,
      observer() {
        // 不要这样做!这个 observer 永远不会被触发
      }
    },
  },
  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>

注意:纯数据字段的属性 observer 永远不会触发!如果想要监听属性值变化,使用 数据监听器 代替

九、抽象节点

组件模版中的一些节点,它对应的自定义组件并不是由该组件本身决定,而是调用者决定的。

例如:

1、定义抽象节点

<!-- selectable-group.wxml -->
<view wx:for="{{labels}}">
  <label>
    <selectable disabled="{{false}}"></selectable>
    {{item}}
  </label>
</view>

上面代码中的 selectable 不是任何在 json 文件的 usingComponents 字段中声明的组件,而是一个抽象节点。

抽象节点的声明方式:

{
  "componentGenerics": {
    "selectable": true
  }
}

2、使用包含抽象节点的组件

在使用 selectable-group 组件时,必须指定“selectable”具体是哪个组件:

<!-- home.wxml -->
<selectable-group generic:selectable="custom-radio" />

这样,在生成这个组件时,“selectable”节点被替换为“custom-radio”的组件实例。

如果这样使用:

<selectable-group generic:selectable="custom-checkbox" />

“selectable”节点则会生成“custom-checkbox”组件实例。

注意:上面的custom-radiocustom-checkbox 需要包含在 home.json 文件中

{
  "usingComponents": {
    "custom-radio": "path/to/custom/radio",
    "custom-checkbox": "path/to/custom/checkbox"
  }
}

3、指定默认组件

抽象节点可以指定一个默认组件,当具体组件未被指定时,将使用默认组件。

{
  "componentGenerics": {
    "selectable": {
      "default": "path/to/default/component"
    }
  }
}

4、tips

  • 调用者在引用时,在 generic:xxx="yyy" 中,值 yyy 只能是静态值,不能包含数据绑定
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值