一、自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
1.创建自定义组件
自定义组件:json+wxml+wxss+js 4个文件组成
步骤:
首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可这一组文件设为自定义组件)
{
"component": true
}
同时,还要在 wxml 文件中编写组件模版,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似;(详情见组件模版和样式)
eg1:
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
/* WXSS 这里的样式只应用于这个自定义组件 */
.inner{
color: red;
}
ps:
在组件WXSS中,不应该使用ID选择器、属性选择器、签名选择器;
在自定义组件js文件中,需要使用Component()来注册组件,并提供组件的属性定义、内部数据、自定义方法;
组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的;
eg2:
//eg1.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type:String,
value: 'default value',
}
},
/**
* 组件的初始数据
*/
data: {
//组件内部数据
someData: {}
},
/**
* 组件的方法列表
*/
methods: {
//自定义方法
customMethod: function(){}
}
})
2.使用自定义组件
使用已注册的自定义组件前,首先要在使用的页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
//my.
{
"navigationBarTitleText": "我的",
"usingComponents": {
"toastTest": "../../Component/toastTest/toastTest"
}
}
在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值:
<view>
<!--以下是自定义组件的使用 -->
<toastTest>//组件名称
toastTest
</toastTest>
</view>
Tips:
对于基础库的1.5.x版本, 1.5.7 也有部分自定义组件支持。
因为WXML节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
自定义组件和使用自定义组件的页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
旧版本的基础库不支持自定义组件,此时,引用自定义组件的节点会变为默认的空节点。
二、组件模版和样式
1.组件模版
组件模版与组件数据结合后生成的节点树,将被插入到组件的引用位置上。在组件模板中可以提供一个<slot>节点,用于承载组件引用时提供的子节点。
<!-- 组件模版WXML -->
<view class='wrapper'>
<view>这里是组件的内部节点</view>
<slot></slot>
</view>
<!-- 引用组件的页面模版 -->
<view>
<toastTest>//组件名称
<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
<view>这里是插入到组件slot中的内容</view>
</toastTest>
</view>
tips:在模版中引用到的自定义组件及其对应的节点名需要在 json 文件中显式定义,否则会被当作一个无意义的节点。除此以外,节点名也可以被声明为抽象节点。
2.组件wxml的slot
在组件的wxml中可以包含slot节点,用于承载组件使用者提供的wxml结构,默认情况下,一个组件的wxml中只能有一个slot,需要使用多个slot时,可以在组件js中声明启用:
Component({
/**
* 在组件中使用多个slot
*/
options: {
multipleSlots: true
}
/**
* 组件的属性列表
*/
properties: {/*...*/},
/**
* 组件的初始数据
*/
data: {/*...*/},
/**
* 组件的方法列表
*/
methods: {/*...*/}
})
在组件的wxml中使用多个slot,以不同的name来区分:
组件wxml中:
<!-- 组件模板 -->
<view class="wrapper">
<slot name="before"></slot>
<view>这里是组件的内部细节</view>
<slot name="after"></slot>
</view>
页面的wxml中:
<!-- 引用组件的页面模版 -->
<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内的节点生效。编写组件样式时,需要注意以下几点:
组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。
组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。
子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
继承样式,如 font 、 color ,会从组件外继承到组件内。
除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效。
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
除此以外,组件可以指定它所在节点的默认样式,使用 :host 选择器
4.外部样式类
有时希望组件接受外部传入的样式类(类似于view组件的hover-class属性)。可以在Component中用externalClasses 定义段定义若干个外部样式类。
/* 组件 custom-component.js */
Component({
externalClasses: ['my-class']
})
/* my.wxml */
/* my.wxss */
.myclass{
color: green;
}
三、Componet构造器
1.调用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 | 否 | 组件间关系定义,参见 组件间关系 |
externalClasses | String Array | 否 | 组件接受外部样式类 |
options | Object Map | 否 | 一些组件选项,请参见文档其他部分的说明 |
2.生成的组件实例可以在组件的方法、生命周期函数和属性 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 对象,选择器选取范围为这个组件实例内 | |
selecteComponent | String selector | 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象 |
selecteAllComponents | String selector | 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 |
getRelationsNodes | String relationKey | 获取所有这个关系对应的所有关联节点,参见 组件间关系 |
eg:
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) {
}
}
})
tips:
在 properties 定义段中,属性名采用驼峰写法(propertyName);
在 wxml 中,指定属性值时则对应使用连字符写法(component-tag-name property-name="attr value"),
应用于数据绑定时采用驼峰写法(attr="{{propertyName}}").
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)
}
}
})
1.触发事件选项包括:
选项名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
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
1.定义和使用
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(){}
}
})
// 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 生命周期函数。
2.字段的覆盖和组合规则
组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:
如果有同名的属性或方法,组件本身的属性或方法会覆盖 behavior 中的属性或方法,如果引用了多个 behavior ,在定义段中靠后 behavior 中的属性或方法会覆盖靠前的属性或方法;
如果有同名的数据字段,如果数据是对象类型,会进行对象合并,如果是非对象类型则会进行相互覆盖;
生命周期函数不会相互覆盖,而是在对应触发时机被逐个调用。如果同一个 behavior 被一个组件多次引用,它定义的生命周期函数只会被执行一次。
3.内置behaviors
自定义组件可以通过引用内置的 behavior 来获得内置组件的一些行为。
Component({
behaviors: ['wx://form-field']
})
wx://form-field 代表一个内置 behavior ,它使得这个自定义组件有类似于表单控件的行为。
内置 behavior 往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的 type 或添加 observer 。
六、组件间关系
1.需求分析:
<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定义,否则不会生效.
2.关联一类组件
<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
}
}
})
3.relations定义段
选项 | 类型 | 是否必填 | 描述 |
---|---|---|---|
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联 |
七、抽象节点
1.定义:自定义组件模版中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。
2.在组件中使用抽象节点
3.使用包含抽象节点的组件
4.抽象节点的默认组件
参考资料:
1.自定义组件
2.抽象节点