文章目录
一、组件模版和样式
类似于页面,自定义组件拥有自己的 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>
组件的属性 propA
和 propB
将收到页面传递的数据。
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
的选项包括:
选项名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部 |
capturePhase | Boolean | 否 | false | 事件是否拥有捕获阶段 |
四、组件生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
最重要的生命周期是 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 | 无 | 组件所在的页面被隐藏时执行 |
resize | Object Size | 组件所在的页面尺寸变化时执行 |
Component({
pageLifetimes: {
show: function() {
// 页面被展示
},
hide: function() {
// 页面被隐藏
},
resize: function(size) {
// 页面尺寸变化
}
}
})
五、behaviors
behaviors
是用于组件间代码共享的特性。
每个 behavior
可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。 每个组件可以引用多个 behavior
,behavior
也可以引用其他 behavior
。
1、behavior
Behavior(Object object)
注册一个 behavior
,接受一个 Object
类型的参数。
参数:Object object
定义段 | 类型 | 是否必填 | 描述 |
---|---|---|---|
properties | Object Map | 否 | 同组件的属性 |
data | Object | 否 | 同组件的数据 |
methods | Object | 否 | 同自定义组件的方法 |
behaviors | String Array | 否 | 引入其它的 behavior |
created | Function | 否 | 生命周期函数 |
attached | Function | 否 | 生命周期函数 |
ready | Function | 否 | 生命周期函数 |
moved | Function | 否 | 生命周期函数 |
detached | Function | 否 | 生命周期函数 |
代码示例:
// 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>
这种结构的组件关系十分熟悉,像swiper
和 swiper-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.name
和 this.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-radio
和 custom-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
只能是静态值,不能包含数据绑定