组件模版和样式
组件模版和样式
自定义组件拥有自己的 jxml 模版和 jxss 样式。
组件模版
组件模版的写法与页面模板相同。组件模版与组件数据结合后生成的节点树,将被插入到组件的引用位置上。
在组件模板中可以提供一个 <slot>
节点,用于承载组件引用时提供的子节点。
代码示例:
<!-- 组件模板 -->
<view class="box">
<view>组件的内部节点</view>
<slot></slot>
</view>
<!-- 引用组件的页面模版 -->
<view>
<component-tag-name>
<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
<view>这里是插入到组件slot中的内容</view>
</component-tag-name>
</view>
注意,在模版中引用到的自定义组件及其对应的节点名需要在 json 文件中显式定义,否则会被当作一个无意义的节点。除此以外,节点名也可以被声明为抽象节点。
模版数据绑定
写法、规则、作用同页面的 JXML 模版一致。
代码示例:
<!-- 引用组件的页面模版 -->
<view>
<component-tag-name prop-a="{{a}}" prop-b="{{b}}">
<!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
<view>这里是插入到组件slot中的内容</view>
</component-tag-name>
</view>
在以上例子中,组件的属性 propA
和 propB
将收到页面传递的数据。页面可以通过 setData
来改变绑定的数据字段。
注意:这样的数据绑定只能传递 JSON
组件 jxml 的 slot
在组件的 jxml 中可以包含 slot 节点,用于承载组件使用者提供的 jxml 结构。
默认情况下,一个组件的 jxml 中只能有一个 slot。需要使用多 slot 时,可以在组件 js 中声明启用。
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: { /* ... */ },
methods: { /* ... */ }
})
组件的 jxml 中的多个 slot,以不同的 name 来区分。
<!-- 组件模板 -->
<view class="wrapper">
<slot name="c1"></slot>
<view>演示</view>
<slot name="c2"></slot>
</view>
使用时,用 slot 属性来将节点插入到不同的 slot 上。
<!-- 引用组件的页面模版 -->
<view>
<component-tag-name>
<!-- 这部分内容将被放置在组件 <slot name="c1"> 的位置上 -->
<view slot="c1">这里是插入到组件slot name="c1"中的内容</view>
<!-- 这部分内容将被放置在组件 <slot name="c2"> 的位置上 -->
<view slot="c2">这里是插入到组件slot name="c2"中的内容</view>
</component-tag-name>
</view>
组件样式
组件对应 jxss 文件的样式,只对组件 jxml 内的节点生效。编写组件样式时,需要注意以下几点:
- 组件和引用组件的页面不能使用 id 选择器(#a)、属性选择器([a])和标签名选择器,请改用 class 选择器。
- 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。
- 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
- 继承样式,如 font 、 color ,会从组件外继承到组件内。
- 除继承样式外, app.jxss 中的样式、组件所在页面的的样式对自定义组件无效。
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
除此以外,组件可以指定它所在节点的默认样式,使用 :host 选择器。
代码示例:
/* 组件 custom-component.jxss */
:host {
color: #000;
}
<!-- 页面的 JXML -->
<custom-component>黑色文本</custom-component>
外部样式类
有时,组件希望接受外部传入的样式类(类似于 view 组件的 hover-class 属性)。此时可以在 Component 中用 externalClasses 定义段定义若干个外部样式类。
注意:在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,因此最好避免这种情况。
代码示例:
/* 组件 custom-component.js */
Component({
externalClasses: ['out-class']
})
<!-- 组件 custom-component.jxml -->
<custom-component class="out-class">这段文本的颜色由组件外的 class 决定 </custom-component>
这样,组件的使用者可以指定这个样式类对应的 class ,就像使用普通属性一样。
代码示例:
<!-- 页面的 JXML -->
<custom-component out-class="blue" />
.blue {
color: blue;
}
全局样式类
使用外部样式类可以让组件使用指定的组件外样式类,如果希望组件外样式类能够完全影响组件内部,可以将组件构造器中的options.addGlobalClass 字段置为 true。
当开放了全局样式类,存在外部样式污染组件样式的风险,请谨慎选择。
代码示例:
/* 组件 custom-component.js */
Component({
options: {
addGlobalClass: true,
}
})
<!-- 组件 custom-component.jxml -->
<text class="blue">这段文本的颜色由组件外的 class 决定</text>
/* 组件外的样式定义 */
.blue {
color: blue;
}
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 | 否 | 组件间关系定义,参见 组件间关系 |
externalClasses | String Array | 否 | 组件接受的外部样式类,参见 外部样式类 |
options | Object Map | 否 | 一些组件选项,请参见文档其他部分的说明 |
lifetimes | Object | 否 | 组件生命周期声明对象,组件的生命周期:created、attached、ready、moved、detached将收归到lifetimes字段内进行声明,原有声明方式仍旧有效,如同时存在两种声明方式,则 lifetimes 字段内声明方式优先级最高 |
pageLifetimes | Object | 否 | 组件所在页面的生命周期声明对象,目前仅支持页面的 show 和 hide 两个生命周期 |
definitionFilter | Function | 否 | 定义段过滤器,用于自定义组件扩展,参见 自定义组件扩展 |
生成的组件实例可以在组件的方法、生命周期函数和属性 observer 中通过 this 访问。组件包含一些通用属性和方法。
属性名 | 类型 | 描述 |
---|---|---|
is | String | 组件的文件路径 |
id | String | 节点id |
dataset | String | 节点 dataset |
data | Object | 组件数据,包括内部数据和属性值 |
properties | Object | 组件数据,包括内部数据和属性值(与 data 一致) |
方法名 | 参数 | 描述 |
---|---|---|
setData | Object newData | 设置data并执行视图层渲染 |
hasBehavior | Object behavior | 检查组件是否具有 behavior (检查时会递归检查被直接或间接引入的所有behavior) |
triggerEvent | String name, Object detail, Object options | 触发事件,参见 组件事件 |
createSelectorQuery | 创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内 | |
selectComponent | String selector | 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象(会被 jd://component-export 影响) |
selectAllComponents | String selector | 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 |
getRelationNodes | String relationKey | 获取所有这个关系对应的所有关联节点,参见 组件间关系 |
代码示例:
Component({
behaviors: [],
properties: {
myProperty: { // 属性名
type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个
observer: function(newVal, oldVal, changedPath) {
// 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'
// 通常 newVal 就是新设置的数据, oldVal 是旧数据
}
},
myProperty2: String // 简化的定义方式
},
data: {}, // 私有数据,可用于模版渲染
lifetimes: {
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { },
moved: function () { },
detached: function () { },
},
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖
ready: function() { },
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
},
methods: {
onMyButtonTap: function(){
this.setData({
// 更新属性和数据的方法与更新页面数据的方法类似
})
},
// 内部方法建议以下划线开头
_myPrivateMethod: function(){
// 这里将 data.B[0].C 设为 'privateData'
this.setData({
'B[0].C': 'privateData'
})
},
_propertyChange: function(newVal, oldVal) {
}
}
})
注意:在 properties 定义段中,属性名采用驼峰写法(propertyName);在 jxml 中,指定属性值时则对应使用连字符写法(component-tag-name property-name="attr value"),应用于数据绑定时采用驼峰写法(attr="")。
使用 Component 构造器构造页面
事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用 Component 构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含 usingComponents 定义段。
此时,组件的属性可以用于接收页面的参数,如访问页面 /pages/index/index?paramA=123¶mB=xyz ,如果声明有属性 paramA 或 paramB ,则它们会被赋值为 123 或 xyz 。
代码示例:
{
"usingComponents": {}
}
Component({
properties: {
paramA: Number,
paramB: String,
},
methods: {
onLoad: function() {
this.data.paramA // 页面参数 paramA 的值
this.data.paramB // 页面参数 paramB 的值
}
}
})
Bug & Tips:
- 使用 this.data 可以获取内部数据和属性值,但不要直接修改它们,应使用 setData 修改。
- 生命周期函数无法在组件方法中通过 this 访问到。
- 属性名应避免以 data 开头,即不要命名成 dataXyz 这样的形式,因为在 JXML 中, data-xyz="" 会被作为节点 dataset 来处理,而不是组件属性。
- 在一个组件的定义和使用时,组件的属性名和 data 字段相互间都不能冲突(尽管它们位于不同的定义段中)。
- bug : 对于 type 为 Object 或 Array 的属性,如果通过该组件自身的 this.setData 来改变属性值的一个子字段,则依旧会触发属性 observer ,且 observer 接收到的 newVal 是变化的那个子字段的值, oldVal 为空, changedPath 包含子字段的字段名相关信息。
组件间通信与事件
组件间通信
组件间的基本通信方式有以下几种。
- JXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据
- 事件:用于子组件向父组件传递数据,可以传递任意数据。
- 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。
监听事件
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见 事件 。
监听自定义组件事件的方法与监听基础组件事件的方法完全一致:
代码示例:
<!-- 当自定义组件触发“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.jxml
<another-component bindcustomevent="pageEventListener1">
<my-component bindcustomevent="pageEventListener2"></my-component>
</another-component>
// 组件 another-component.jxml
<view bindcustomevent="anotherEventListener">
<slot />
</view>
// 组件 my-component.jxml
<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
定义和使用 behaviors
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 被一个组件多次引用,它定义的生命周期函数只会被执行一次。
内置 behaviors
自定义组件可以通过引用内置的 behavior 来获得内置组件的一些行为。
代码示例:
Component({
behaviors: ['jd://form-field']
})
在上例中,jd://form-field
代表一个内置 behavior
,它使得这个自定义组件有类似于表单控件的行为。
内置 behavior
往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的 type 或添加 observer 。
jd://form-field
使自定义组件有类似于表单控件的行为。 form 组件可以识别这些自定义组件,并在 submit 事件中返回组件的字段名及其对应字段值。这将为它添加以下两个属性。
属性名 | 类型 | 描述 |
---|---|---|
name | String | 在表单中的字段名 |
value | 任意 | 在表单中的字段值 |
jd://component-export
使自定义组件支持 export 定义段。这个定义段可以用于指定组件被 selectComponent 调用时的返回值。
未使用这个定义段时, selectComponent 将返回自定义组件的 this (插件的自定义组件将返回 null )。使用这个定义段时,将以这个定义段的函数返回值代替。
代码示例:
// 自定义组件 my-component 内部
Component({
behaviors: ['jd://component-export'],
export() {
return { myField: 'myValue' }
}
})
<!-- 使用自定义组件时 -->
<my-component id="the-id" />
this.selectComponent('the-id') // 等于 { myField: 'myValue' }
组件间关系
定义和使用组件间关系
有时需要实现这样的组件:
<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 定义段
relations 定义段包含目标组件路径及其对应选项,可包含的选项见下表。
选项 | 类型 | 是否必填 | 描述 |
---|---|---|---|
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件 attached 生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件 moved 生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件 detached 生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的 behavior,所有拥有这一 behavior 的组件节点都会被关联 |
抽象节点
在组件中使用抽象节点
有时,自定义组件模版中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。
例如,我们现在来实现一个“选框组”(selectable-group)组件,它其中可以放置单选框(custom-radio)或者复选框(custom-checkbox)。这个组件的 jxml 可以这样编写:
<!-- selectable-group.jxml -->
<view jd:for="{{labels}}">
<label>
<selectable disabled="{{false}}"></selectable>
{{item}}
</label>
</view>
其中,“selectable” 不是任何在 json 文件的 usingComponents
字段中声明的组件,而是一个抽象节点。它需要在 componentGenerics
字段中声明:
{
"componentGenerics": {
"selectable": true
}
}
使用包含抽象节点的组件
在使用 selectable-group 组件时,必须指定“selectable”具体是哪个组件:
<selectable-group generic:selectable="custom-radio" />
这样,在生成这个 selectable-group 组件的实例时,“selectable”节点会生成 “custom-radio” 组件实例。类似地,如果这样使用:
<selectable-group generic:selectable="custom-checkbox" />
“selectable”节点则会生成 “custom-checkbox” 组件实例。
注意:上述的 custom-radio
和 custom-checkbox
需要包含在这个 jxml 对应 json 文件的 usingComponents
定义段中。
{
"usingComponents": {
"custom-radio": "path/to/custom/radio",
"custom-checkbox": "path/to/custom/checkbox"
}
}
抽象节点的默认组件
抽象节点可以指定一个默认组件,当具体组件未被指定时,将创建默认组件的实例。默认组件可以在 componentGenerics
字段中指定:
{
"componentGenerics": {
"selectable": {
"default": "path/to/default/component"
}
}
}
Tips:
- 节点的 generic 引用
generic:xxx="yyy"
中,值yyy
只能是静态值,不能包含数据绑定。因而抽象节点特性并不适用于动态决定节点名的场景。
自定义组件扩展
为了更好定制自定义组件的功能,可以使用自定义组件扩展机制。
扩展后的效果
为了更好的理解扩展后的效果,先举一个例子:
// behavior.js
module.exports = Behavior({
definitionFilter(defFields) {
defFields.data.from = 'behavior'
},
})
// component.js
Component({
data: {
from: 'component'
},
behaviors: [require('behavior.js')],
ready() {
console.log(this.data.from) // 此处会发现输出 behavior 而不是 component
}
})
通过例子可以发现,自定义组件的扩展其实就是提供了修改自定义组件定义段的能力,上述例子就是修改了自定义组件中的 data
定义段里的内容。
使用扩展
Behavior()
构造器提供了新的定义段 definitionFilter
,用于支持自定义组件扩展。 definitionFilter
是一个函数,在被调用时会注入两个参数,第一个参数是使用该 behavior 的 component/behavior 的定义对象,第二个参数是该 behavior 所使用的 behavior 的 definitionFilter
函数列表。
以下举个例子来说明:
// behavior3.js
module.exports = Behavior({
definitionFilter(defFields, definitionFilterArr) {},
})
// behavior2.js
module.exports = Behavior({
behaviors: [require('behavior3.js')],
definitionFilter(defFields, definitionFilterArr) {
// definitionFilterArr[0](defFields)
},
})
// behavior1.js
module.exports = Behavior({
behaviors: [require('behavior2.js')],
definitionFilter(defFields, definitionFilterArr) {},
})
// component.js
Component({
behaviors: [require('behavior1.js')],
})
上述代码中声明了 1 个自定义组件和 3 个 behavior,每个 behavior 都使用了 definitionFilter
定义段。那么按照声明的顺序会有如下事情发生:
当进行 behavior2 的声明时就会调用 behavior3 的
definitionFilter
函数,其中 defFields 参数是 behavior2 的定义段,definitionFilterArr
参数即为空数组,因为 behavior3 没有使用其他的 behavior 。当进行 behavior1 的声明时就会调用 behavior2 的
definitionFilter
函数,其中 defFields 参数是 behavior1 的定义段,definitionFilterArr
参数是一个长度为1的数组,definitionFilterArr[0]
即为 behavior3 的definitionFilter
函数,因为 behavior2 使用了 behavior3。用户在此处可以自行决定在进行 behavior1 的声明时要不要调用 behavior3 的definitionFilter
函数,如果需要调用,在此处补充代码definitionFilterArr[0](defFields)
即可,definitionFilter
Arr 参数会由基础库补充传入。同理,在进行 component 的声明时就会调用 behavior1 的
definitionFilter
函数。
简单概括,definitionFilter
函数可以理解为当 A 使用了 B 时,A 声明就会调用 B 的 definitionFilter
函数并传入 A 的定义对象让 B 去过滤。此时如果 B 还使用了 C 和 D ,那么 B 可以自行决定要不要调用 C 和 D 的 definitionFilter
函数去过滤 A 的定义对象。
代码示例:
真实案例
下面利用扩展简单实现自定义组件的计算属性功能:
// behavior.js
module.exports = Behavior({
lifetimes: {
created() {
this._originalSetData = this.setData // 原始 setData
this.setData = this._setData // 封装后的 setData
}
},
definitionFilter(defFields) {
const computed = defFields.computed || {}
const computedKeys = Object.keys(computed)
const computedCache = {}
// 计算 computed
const calcComputed = (scope, insertToData) => {
const needUpdate = {}
const data = defFields.data = defFields.data || {}
for (let key of computedKeys) {
const value = computed[key].call(scope) // 计算新值
if (computedCache[key] !== value) needUpdate[key] = computedCache[key] = value
if (insertToData) data[key] = needUpdate[key] // 直接插入到 data 中,初始化时才需要的操作
}
return needUpdate
}
// 重写 setData 方法
defFields.methods = defFields.methods || {}
defFields.methods._setData = function (data, callback) {
const originalSetData = this._originalSetData // 原始 setData
originalSetData.call(this, data, callback) // 做 data 的 setData
const needUpdate = calcComputed(this) // 计算 computed
originalSetData.call(this, needUpdate) // 做 computed 的 setData
}
// 初始化 computed
calcComputed(defFields, true) // 计算 computed
}
})
在组件中使用:
const beh = require('./behavior.js')
Component({
behaviors: [beh],
data: {
a: 0,
},
computed: {
b() {
return this.data.a + 100
},
},
methods: {
onTap() {
this.setData({
a: ++this.data.a,
})
}
}
})
<view>data: {{a}}</view>
<view>computed: {{b}}</view>
<button bindtap="onTap">click</button>
实现原理很简单,对已有的 setData 进行二次封装,在每次 setData 的时候计算出 computed 里各字段的值,然后设到 data 中,已达到计算属性的效果。
此实现只是作为一个简单案例来展示,请勿直接在生产环境中使用。