自定义组件
从小程序基础库版本
1.6.3 开始,小程序支持简洁的组件化编程。开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
创建自定义组件
类似于页面,一个自定义组件由
json
wxml
wxss
js
4个文件组成。要编写一个自定义组件,首先需要在
json
文件中进行自定义组件声明(将
component
字段设为
true
可这一组文件设为自定义组件):
{
"component": true
}
同时,还要在
wxml
文件中编写组件模版,在
wxss
文件中加入组件样式,它们的写法与页面的写法类似。代码示例:
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
/* 这里的样式只应用于这个自定义组件 */
.inner {
color: red;
}
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。在自定义组件的
js
文件中,需要使用
Component()
来注册组件,并提供组件的属性定义、内部数据和自定义方法。组件的属性值和内部数据将被用于组件
wxml
的渲染,其中,属性值是可由组件外部传入的。代码示例:
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
使用自定义组件
使用已注册的自定义组件前,首先要在页面的
json
文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
这样,在页面的
wxml
中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。代码示例:
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
自定义组件的
wxml
节点结构在与数据结合之后,将被插入到引用位置内。Tips:对于基础库的1.5.x版本,
1.5.7 也有部分自定义组件支持。因为WXML节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用
usingComponents
字段)。自定义组件和使用自定义组件的页面所在项目根目录名不能以“wx-”为前缀,否则会报错。旧版本的基础库不支持自定义组件,此时,引用自定义组件的节点会变为默认的空节点。
组件模版和样式
类似于页面,自定义组件拥有自己的
wxml
模版和
wxss
样式。
组件模版
组件模版的写法与页面模板相同。组件模版与组件数据结合后生成的节点树,将被插入到组件的引用位置上。在组件模板中可以提供一个
<slot>
节点,用于承载组件引用时提供的子节点。代码示例:
<!-- 组件模板 -->
<view class="wrapper">
<view>这里是组件的内部节点</view>
<slot></slot>
</view>
<!-- 引用组件的页面模版 -->
<view>
<component-tag-name>
<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
<view>这里是插入到组件slot中的内容</view>
</component-tag-name>
</view>
组件wxml的slot
在组件的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>
组件样式
组件对应
wxss
文件的样式,只对组件wxml内的节点生效。编写组件样式时,需要注意以下几点:组件和引用组件的页面不能使用id选择器(
#a
)、属性选择器(
[a]
)和标签名选择器,请改用class选择器。组件和引用组件的页面中使用后代选择器(
.a .b
)在一些极端情况下会有非预期的表现,如遇,请避免使用。子元素选择器(
.a>.b
)只能用于
view
组件与其子节点之间,用于其他组件可能导致非预期的情况。继承样式,如
font
、
color
,会从组件外继承到组件内。除继承样式外,
app.wxss
中的样式、组件所在页面的的样式对自定义组件无效。
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
除此以外,组件可以指定它所在节点的默认样式,使用
:host
选择器(需要包含基础库
1.7.2 或更高版本的开发者工具支持)。代码示例:
/* 组件 custom-component.wxss */
:host {
color: yellow;
}
<!-- 页面的 WXML -->
<custom-component>这段文本是黄色的</custom-component>
Component构造器
Component构造器可用于定义组件,调用Component构造器时可以指定组件的属性、数据、方法等。
| 定义段 | 类型 | 是否必填 | 描述 |
---|
properties | Object Map | 否 | 组件的对外属性,是属性名到属性设置的映射表,属性设置中可包含三个字段, type 表示属性类型、 value 表示属性初始值、 observer 表示属性值被更改时的响应函数 |
data | Object | 否 | 组件的内部数据,和 properties 一同用于组件的模版渲染 |
methods | Object | 否 | 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 组件事件 |
behaviors | String Array | 否 | 类似于mixins和traits的组件间代码复用机制,参见 behaviors |
created | Function | 否 | 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用 setData |
attached | Function | 否 | 组件生命周期函数,在组件实例进入页面节点树时执行 |
ready | Function | 否 | 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery ) |
moved | Function | 否 | 组件生命周期函数,在组件实例被移动到节点树另一个位置时执行 |
detached | Function | 否 | 组件生命周期函数,在组件实例被从页面节点树移除时执行 |
relations | Object | 否 | 组件间关系定义,参见 组件间关系 |
options | Object Map | 否 | 一些组件选项,请参见文档其他部分的说明 |
生成的组件实例可以在组件的方法、生命周期函数和属性
observer
中通过
this
访问。组件包含一些通用属性和方法。
| 属性名 | 类型 | 描述 |
---|
is | String | 组件的文件路径 |
id | String | 节点id |
dataset | String | 节点dataset |
data | Object | 组件数据,包括内部数据和属性值 |
| 方法名 | 参数 | 描述 |
---|
setData | Object newData | 设置data并执行视图层渲染 |
hasBehavior | Object behavior | 检查组件是否具有 behavior (检查时会递归检查被直接或间接引入的所有behavior) |
triggerEvent | String name , Object detail , Object options | 触发事件,参见 组件事件 |
createSelectorQuery | | 创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内 |
selectComponent | String selector | 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象 |
selectAllComponents | String selector | 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 |
getRelationNodes | String relationKey | 获取所有这个关系对应的所有关联节点,参见 组件间关系 |
代码示例:
Component({behaviors: [],properties: {myProperty: { // 属性名type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个observer: function(newVal, oldVal){} // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'},myProperty2: String // 简化的定义方式},data: {}, // 私有数据,可用于模版渲染// 生命周期函数,可以为函数,或一个在methods段中定义的方法名attached: function(){},moved: function(){},detached: function(){},methods: {onMyButtonTap: function(){this.setData({// 更新属性和数据的方法与更新页面数据的方法类似})},_myPrivateMethod: function(){// 内部方法建议以下划线开头this.replaceDataOnPath(['A', 0, 'B'], 'myPrivateData') // 这里将 data.A[0].B 设为 'myPrivateData'this.applyDataUpdates()},_propertyChange: function(newVal, oldVal) {}}})
注意:在
properties
定义段中,属性名采用驼峰写法(
propertyName
);在
wxml
中,指定属性值时则对应使用连字符写法(
component-tag-name property-name="attr value"
),应用于数据绑定时采用驼峰写法(
attr="{{propertyName}}"
)。Tips:
Component
构造器构造的组件也可以作为页面使用。使用
this.data
可以获取内部数据和属性值,但不要直接修改它们,应使用
setData
修改。生命周期函数无法在组件方法中通过
this
访问到。属性名应避免以 data 开头,即不要命名成
dataXyz
这样的形式,因为在 WXML 中,
data-xyz=""
会被作为节点 dataset 来处理,而不是组件属性。在一个组件的定义和使用时,组件的属性名和data字段相互间都不能冲突(尽管它们位于不同的定义段中)。
组件事件
事件系统是组件间交互的主要形式。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见
事件 。监听自定义组件事件的方法与监听基础组件事件的方法完全一致:代码示例:
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 --><component-tag-name bindmyevent="onMyEvent" /><!-- 或者可以写成 --><component-tag-name bind:myevent="onMyEvent" />
Page({onMyEvent: function(e){e.detail // 自定义组件触发事件时提供的detail对象}})
自定义组件触发事件时,需要使用
triggerEvent
方法,指定事件名、detail对象和事件选项:代码示例:
<!-- 在自定义组件中 --><button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({properties: {}methods: {onTap: function(){var myEventDetail = {} // detail对象,提供给事件监听函数var myEventOption = {} // 触发事件的选项this.triggerEvent('myevent', myEventDetail, myEventOption)}}})
触发事件的选项包括:
| 选项名 | 类型 | 是否必填 | 默认值 | 描述 |
---|
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部 |
capturePhase | Boolean | 否 | false | 事件是否拥有捕获阶段 |
关于冒泡和捕获阶段的概念,请阅读
事件 章节中的相关说明。代码示例:
// 页面 page.wxml<another-component bindcustomevent="pageEventListener1"><my-component bindcustomevent="pageEventListener2"></my-component></another-component>
// 组件 another-component.wxml<view bindcustomevent="anotherEventListener"><slot /></view>
// 组件 my-component.wxml<view bindcustomevent="myEventListener"><slot /></view>
// 组件 my-component.jsComponent({methods: {onTap: function(){this.triggerEvent('customevent', {}) // 只会触发 pageEventListener2this.triggerEvent('customevent', {}, { bubbles: true }) // 会依次触发 pageEventListener2 、 pageEventListener1this.triggerEvent('customevent', {}, { bubbles: true, composed: true }) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1}}})
behaviors
定义和使用 behaviors
behaviors
是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。每个
behavior
可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个
behavior
。
behavior
也可以引用其他
behavior
。
behavior
需要使用
Behavior()
构造器定义。代码示例:
// my-behavior.jsmodule.exports = Behavior({behaviors: [],properties: {myBehaviorProperty: {type: String}},data: {myBehaviorData: {}},attached: function(){},methods: {myBehaviorMethod: function(){}}})
组件引用时,在
behaviors
定义段中将它们逐个列出即可。代码示例:
// my-component.jsvar myBehavior = require('my-behavior')Component({behaviors: [myBehavior],properties: {myProperty: {type: String}},data: {myData: {}},attached: function(){},methods: {myMethod: function(){}}})
在上例中,
my-component
组件定义中加入了
my-behavior
,而
my-behavior
中包含有
myBehaviorProperty
属性、
myBehaviorData
数据字段、
myBehaviorMethod
方法和一个
attached
生命周期函数。这将使得
my-component
中最终包含
myBehaviorProperty
、
myProperty
两个属性,
myBehaviorData
、
myData
两个数据字段,和
myBehaviorMethod
、
myMethod
两个方法。当组件触发
attached
生命周期时,会依次触发
my-behavior
中的
attached
生命周期函数和
my-component
中的
attached
生命周期函数。
字段的覆盖和组合规则
组件和它引用的
behavior
中可以包含同名的字段,对这些字段的处理方法如下:如果有同名的属性或方法,组件本身的属性或方法会覆盖
behavior
中的属性或方法,如果引用了多个
behavior
,在定义段中靠后
behavior
中的属性或方法会覆盖靠前的属性或方法;如果有同名的数据字段,如果数据是对象类型,会进行对象合并,如果是非对象类型则会进行相互覆盖;生命周期函数不会相互覆盖,而是在对应触发时机被逐个调用。如果同一个
behavior
被一个组件多次引用,它定义的生命周期函数只会被执行一次。
内置 behaviors
自定义组件可以通过引用内置的
behavior
来获得内置组件的一些行为。代码示例:
Component({behaviors: ['wx://form-field']})
在上例中,
wx://form-field
代表一个内置
behavior
,它使得这个自定义组件有类似于表单控件的行为。内置
behavior
往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的
type
或添加
observer
。
wx://form-field
使自定义组件有类似于表单控件的行为。 form 组件可以识别这些自定义组件,并在 submit 事件中返回组件的字段名及其对应字段值。这将为它添加以下两个属性。
| 属性名 | 类型 | 描述 | 最低版本 |
---|
name | String | 在表单中的字段名 | 1.6.7 |
value | 任意 | 在表单中的字段值 | 1.6.7 |
组件间关系
定义和使用组件间关系
有时需要实现这样的组件:
<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.jsComponent({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.jsComponent({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定义,否则不会生效。
关联一类组件
有时,需要关联的是一类组件,如:
<custom-form><view>input<custom-input></custom-input></view><custom-submit> submit </custom-submit></custom-form>
custom-form
组件想要关联
custom-input
和
custom-submit
两个组件。此时,如果这两个组件都有同一个behavior:
// path/to/custom-form-controls.jsmodule.exports = Behavior({// ...})
// path/to/custom-input.jsvar customFormControls = require('./custom-form-controls')Component({behaviors: [customFormControls],relations: {'./custom-form': {type: 'ancestor', // 关联的目标节点应为祖先节点}}})
// path/to/custom-submit.jsvar customFormControls = require('./custom-form-controls')Component({behaviors: [customFormControls],relations: {'./custom-form': {type: 'ancestor', // 关联的目标节点应为祖先节点}}})
则在
relations
关系定义中,可使用这个behavior来代替组件路径作为关联的目标节点:
// path/to/custom-form.jsvar customFormControls = require('./custom-form-controls')Component({relations: {'customFormControls': {type: 'descendant', // 关联的目标节点应为子孙节点target: customFormControls}}})
relations 定义段
relations
定义段包含目标组件路径及其对应选项,可包含的选项见下表。
| 选项 | 类型 | 是否必填 | 描述 |
---|
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联 |