从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
类似于页面,一个自定义组件由 json
wxml
wxss
js
4个文件组成。要编写一个自定义组件,首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可这一组文件设为自定义组件):
{
"component": true
}
同时,还要在 wxml
文件中编写组件模版,在 wxss
文件中加入组件样式,它们的写法与页面的写法类似。
代码示例:
<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:
usingComponents
字段)。类似于页面,自定义组件拥有自己的 wxml
模版和 wxss
样式。
组件模版的写法与页面模板相同。组件模版与组件数据结合后生成的节点树,将被插入到组件的引用位置上。
在组件模板中可以提供一个
节点,用于承载组件引用时提供的子节点。
代码示例:
<view class="wrapper">
<view>这里是组件的内部节点view>
<slot>slot>
view>
<view>
<component-tag-name>
<view>这里是插入到组件slot中的内容view>
component-tag-name>
view>
在组件的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>
<view slot="before">这里是插入到组件slot name="before"中的内容view>
<view slot="after">这里是插入到组件slot name="after"中的内容view>
component-tag-name>
view>
组件对应 wxss
文件的样式,只对组件wxml内的节点生效。编写组件样式时,需要注意以下几点:
#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;
}
<custom-component>这段文本是黄色的custom-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
访问到。dataXyz
这样的形式,因为在 WXML 中, data-xyz=""
会被作为节点 dataset 来处理,而不是组件属性。事件系统是组件间交互的主要形式。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见 事件 。
监听自定义组件事件的方法与监听基础组件事件的方法完全一致:
代码示例:
<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.js
Component({
methods: {
onTap: function(){
this.triggerEvent('customevent', {}) // 只会触发 pageEventListener2
this.triggerEvent('customevent', {}, { bubbles: true }) // 会依次触发 pageEventListener2 、 pageEventListener1
this.triggerEvent('customevent', {}, { bubbles: true, composed: true }) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1
}
}
})
behaviors
是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。
每个 behavior
可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior
。 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(){}
}
})
在上例中, 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
被一个组件多次引用,它定义的生命周期函数只会被执行一次。自定义组件可以通过引用内置的 behavior
来获得内置组件的一些行为。
代码示例:
Component({
behaviors: ['wx://form-field']
})
在上例中, wx://form-field
代表一个内置 behavior
,它使得这个自定义组件有类似于表单控件的行为。
内置 behavior
往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的 type
或添加 observer
。
使自定义组件有类似于表单控件的行为。 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.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定义,否则不会生效。
有时,需要关联的是一类组件,如:
<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.js
module.exports = Behavior({
// ...
})
// path/to/custom-input.js
var customFormControls = require('./custom-form-controls')
Component({
behaviors: [customFormControls],
relations: {
'./custom-form': {
type: 'ancestor', // 关联的目标节点应为祖先节点
}
}
})
// path/to/custom-submit.js
var customFormControls = require('./custom-form-controls')
Component({
behaviors: [customFormControls],
relations: {
'./custom-form': {
type: 'ancestor', // 关联的目标节点应为祖先节点
}
}
})
则在 relations
关系定义中,可使用这个behavior来代替组件路径作为关联的目标节点:
// path/to/custom-form.js
var customFormControls = require('./custom-form-controls')
Component({
relations: {
'customFormControls': {
type: 'descendant', // 关联的目标节点应为子孙节点
target: customFormControls
}
}
})
relations
定义段包含目标组件路径及其对应选项,可包含的选项见下表。
选项 | 类型 | 是否必填 | 描述 |
---|---|---|---|
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联 |