json
wxml
wxss
js
4个文件组成。要编写一个自定义组件,首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可将这一组文件设为自定义组件):{"component": true}
<!-- 这是自定义组件的内部WXML结构 --><view class="inner">{{innerText}}</view><slot></slot>
/* 这里的样式只应用于这个自定义组件 */.inner {color: red;}
Component()
来注册组件,并提供组件的属性定义、内部数据和自定义方法。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
节点结构在与数据结合之后,将被插入到引用位置内。usingComponents
会使得页面的 this
对象的原型稍有差异,包括:usingComponents
页面的原型与不使用时不一致,即 Object.getPrototypeOf(this)
结果不同。usingComponents
时会多一些方法,如 selectComponent
。usingComponents
时, setData
内容不会被直接深复制,即 this.setData({ field: obj })
后 this.data.field === obj
。(深复制会在这个值被组件间传递时发生。)usingComponents
定义段时建议重新测试一下。wxml
模板和wxss
样式。<slot>
节点,用于承载组件引用时提供的子节点。<!-- 组件模板 --><view class="wrapper"><view>这里是组件的内部节点</view><slot></slot></view>
<!-- 引用组件的页面模板 --><view><component-tag-name><!-- 这部分内容将被放置在组件 <slot> 的位置上 --><view>这里是插入到组件slot中的内容</view></component-tag-name></view>
<!-- 引用组件的页面模板 --><view><component-tag-name prop-a="{{dataFieldA}}" prop-b="{{dataFieldB}}"><!-- 这部分内容将被放置在组件 <slot> 的位置上 --><view>这里是插入到组件slot中的内容</view></component-tag-name></view>
propA
和 propB
将收到页面传递的数据。页面可以通过 setData
来改变绑定的数据字段。slot
节点,用于承载组件使用者提供的 wxml 结构。slot
。需要使用多 slot
时,可以在组件 js 中声明启用。Component({options: {multipleSlots: true // 在组件定义时的选项中启用多slot支持},properties: { /* ... */ },methods: { /* ... */ }})
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 内的节点生效。编写组件样式时,需要注意以下几点:#a
)、属性选择器([a]
)和标签名选择器,请改用 class 选择器。.a .b
)在一些极端情况下会有非预期的表现,如遇,请避免使用。.a>.b
)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。font
、color
,会从组件外继承到组件内。app.wxss
中的样式、组件所在页面的样式对自定义组件无效(除非更改组件样式隔离选项)。#a { } /* 在组件中不能使用 */[a] { } /* 在组件中不能使用 */button { } /* 在组件中不能使用 */.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
:host
选择器。/* 组件 custom-component.wxss */:host {color: yellow;}
<!-- 页面的 WXML --><custom-component>这段文本是黄色的</custom-component>
Component
中用 externalClasses
定义段定义若干个外部样式类。view
组件的 hover-class
属性:页面可以提供一个样式类,赋予 view
的 hover-class
,这个样式类本身写在页面中而非 view
组件的实现中。/* 组件 custom-component.js */Component({externalClasses: ['my-class']})
<!-- 组件 custom-component.wxml --><custom-component class="my-class">这段文本的颜色由组件外的 class 决定</custom-component>
<!-- 页面的 WXML --><custom-component my-class="red-text" /><custom-component my-class="large-text" />
.red-text {color: red;}.large-text {font-size: 1.5em;}
app.wxss
或页面的 wxss
中使用了标签名选择器(或一些其他特殊选择器)来直接指定样式,这些选择器会影响到页面和全部组件。通常情况下这是不推荐的做法。addGlobalClass
选项,这使得这个自定义组件能被 app.wxss
或页面的 wxss
中的所有的样式定义影响。addGlobalClass
选项,只需要在 Component
构造器中将 options.addGlobalClass
字段置为 true
。addGlobalClass
选项后,存在外部样式污染组件样式的风险,请谨慎选择。/* 组件 custom-component.js */Component({options: {addGlobalClass: true,}}
<!-- 组件 custom-component.wxml --><text class="red-text">这段文本的颜色由 `app.wxss` 和页面 `wxss` 中的样式定义来决定</text>
/* app.wxss */.red-text {color: red;}
Component
构造器可用于定义组件,调用 Component
构造器时可以指定组件的属性、数据、方法等。Component({behaviors: [],properties: {myProperty: { // 属性名type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个observer(newVal, oldVal, changedPath) {// 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'// 通常 newVal 就是新设置的数据, oldVal 是旧数据}},myProperty2: String // 简化的定义方式},data: {}, // 私有数据,可用于模板渲染lifetimes: {// 生命周期函数,可以为函数,或一个在methods段中定义的方法名attached() { },moved() { },detached() { },},// 生命周期函数,可以为函数,或一个在methods段中定义的方法名attached() { }, // 此处attached的声明会被lifetimes字段中的声明覆盖ready() { },pageLifetimes: {// 组件所在页面的生命周期函数show() { },hide() { },resize() { },},methods: {onMyButtonTap() {this.setData({// 更新属性和数据的方法与更新页面数据的方法类似})},// 内部方法建议以下划线开头_myPrivateMethod() {// 这里将 data.A[0].B 设为 'myPrivateData'this.setData({'A[0].B': 'myPrivateData'})},_propertyChange(newVal, oldVal) {}}})
properties
定义段中,属性名采用驼峰写法(propertyName
);在 wxml 中,指定属性值时则对应使用连字符写法(component-tag-name property-name="attr value"
),应用于数据绑定时采用驼峰写法(attr="{{propertyName}}"
)。Component
构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含 usingComponents
定义段。/pages/index/index?paramA=123¶mB=xyz
,如果声明有属性 paramA
或 paramB
,则它们会被赋值为 123
或 xyz
。on
开头的方法),应写在 methods
定义段中。{"usingComponents": {}}
Component({properties: {paramA: Number,paramB: String,},methods: {onLoad: function() {this.data.paramA // 页面参数 paramA 的值this.data.paramB // 页面参数 paramB 的值}}})
Component
构造器来构造页面的一个好处是可以使用 behaviors
来提取所有页面中公用的代码段。behaviors
中。// page-common-behavior.jsmodule.exports = Behavior({attached: function() {// 页面创建时执行console.info('Page loaded!')},detached: function() {// 页面销毁时执行console.info('Page unloaded!')}})
// 页面 Avar pageCommonBehavior = require('./page-common-behavior')Component({behaviors: [pageCommonBehavior],data: { /* ... */ },methods: { /* ... */ },})
// 页面 Bvar pageCommonBehavior = require('./page-common-behavior')Component({behaviors: [pageCommonBehavior],data: { /* ... */ },methods: { /* ... */ },})
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.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}}})
this.selectComponent
,获取子组件的实例对象。selector
,如:this.selectComponent(".my-component")
。// 父组件Page({data: {},getChildComponent: function () {const child = this.selectComponent('.my-component');console.log(child)}})
class
为 my-component
的子组件实例对象,即子组件的 this 。selectComponent
返回的数据,可使用内置 behavior: wx://component-export
behavior
时,自定义组件中的 export
定义段将用于指定组件被 selectComponent
调用时的返回值。// 自定义组件 my-component 内部Component({behaviors: ['wx://component-export'],export() {return { myField: 'myValue' }}})
<!-- 使用自定义组件时 --><my-component id="the-id" />
// 父组件调用const child = this.selectComponent('#the-id') // 等于 { myField: 'myValue' }
id
为 the-id
的子组件实例的时候,得到的是对象{ myField: 'myValue' }
。created
attached
detached
,包含一个组件实例生命流程的最主要时间点。created
生命周期被触发。此时,组件数据 this.data
就是在 Component
构造器中定义的数据data
。 此时还不能调用setData
。 通常情况下,这个生命周期只应该用于给组件 this
添加一些自定义属性字段。attached
生命周期被触发。此时,this.data
已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。detached
生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached
会被触发。Component
构造器的第一级参数中。lifetimes
字段内进行声明(这是推荐的方式,其优先级最高)。
代码示例:Component({lifetimes: {attached: function() {// 在组件实例进入页面节点树时执行},detached: function() {// 在组件实例被从页面节点树移除时执行},},// 以下是旧式的定义方式,可以保持对 <1.0.2 版本基础库的兼容attached: function() {// 在组件实例进入页面节点树时执行},detached: function() {// 在组件实例被从页面节点树移除时执行},// ...})
生命周期 | 参数 | 描述 |
created | 无 | 在组件实例刚刚被创建时执行 |
attached | 无 | 在组件实例进入页面节点树时执行 |
ready | 无 | 在组件在视图层布局完成后执行 |
moved | 无 | 在组件实例被移动到节点树另一个位置时执行 |
detached | 无 | 在组件实例被从页面节点树移除时执行 |
error | Object Error | 每当组件方法抛出错误时执行 |
pageLifetimes
定义段中定义。其中可用的生命周期包括:生命周期 | 参数 | 描述 |
show | 无 | 组件所在的页面被展示时执行 |
hide | 无 | 组件所在的页面被隐藏时执行 |
resize | Object Size | 组件所在的页面尺寸变化时执行 |
Component({pageLifetimes: {show: function() {// 页面被展示},hide: function() {// 页面被隐藏},resize: function(size) {// 页面尺寸变化}}})
behaviors
是用于组件间代码共享的特性,类似于一些编程语言中的 “mixins” 或 “traits”。behavior
可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。 每个组件可以引用多个 behavior
,behavior
也可以引用其它 behavior
。behavior
需要使用 Behavior()
构造器定义。// my-behavior.jsmodule.exports = Behavior({behaviors: [],properties: {myBehaviorProperty: {type: String}},data: {myBehaviorData: {}},attached() {},methods: {myBehaviorMethod() {}}})
behaviors
定义段中将它们逐个列出即可。// my-component.jsvar myBehavior = require('my-behavior')Component({behaviors: [myBehavior],properties: {myProperty: {type: String}},data: {myData: 'my-component-data'},created: function () {console.log('[my-component] created')},attached: function () {console.log('[my-component] attached')},ready: function () {console.log('[my-component] ready')},methods: {myMethod: function () {console.log('[my-component] log by myMethod')},}})
my-component
组件定义中加入了my-behavior
,my-behavior
的结构为:myBehaviorProperty
myBehaviorData
myBehaviorMethod
attached
、created
、ready
myBehaviorProperty
、myProperty
myBehaviorData
、myData
myBehaviorMethod
、myMethod
attached
、created
、ready
behavior
中可以包含同名的字段,对这些字段的处理方法如下:behavior
中的同名属性或方法;behaviors
字段中定义靠后的 behavior
的属性或方法会覆盖靠前的同名属性或方法;behavior
的情况,则规则为:引用者 behavior
覆盖 被引用的 behavior
中的同名属性或方法。behavior
> 被引用的 behavior
、 靠后的 behavior
> 靠前的 behavior
。(优先级高的覆盖优先级低的,最大的为优先级最高)。behavior
优先于组件执行;behavior
优先于 引用者 behavior
执行;behavior
优先于 靠后的 behavior
执行;behavior
被一个组件多次引用,它定义的生命周期函数和 observers 不会重复执行。behavior
来获得内置组件的一些行为。Component({behaviors: ['wx://form-field']})
wx://form-field
代表一个内置 behavior
,它使得这个自定义组件有类似于表单控件的行为。behavior
往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的 type
或添加 observer
。属性名 | 类型 | 描述 |
name | String | 在表单中的字段名 |
value | 任意 | 在表单中的字段值 |
// custom-form-field.jsComponent({behaviors: ['wx://form-field'],data: {value: ''},methods: {onChange: function (e) {this.setData({value: e.detail.value,})}}})
export
定义段。这个定义段可以用于指定组件被 selectComponent
调用时的返回值。<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生命周期之后}}}})
<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
定义段包含目标组件路径及其对应选项,可包含的选项见下表。选项 | 类型 | 是否必填 | 描述 |
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件 attached 生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件 moved 生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件 detached 生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一 behavior 的组件节点都会被关联 |
this.data.sum
永远是 this.data.numberA
与 this.data.numberB
的和。此时,可以使用数据监听器进行如下实现。Component({attached: function() {this.setData({numberA: 1,numberB: 2,})},observers: {'numberA, numberB': function(numberA, numberB) {// 在 numberA 或者 numberB 被设置时,执行这个函数this.setData({sum: numberA + numberB})}}})
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},},attached: function() {// 这样会触发上面的 observerthis.setData({'some.field': { /* ... */ }})// 这样也会触发上面的 observerthis.setData({'some.field.xxx': { /* ... */ }})// 这样还是会触发上面的 observerthis.setData({'some': { /* ... */ }})}})
**
可以监听全部 setData。Component({observers: {'**': function() {// 每次 setData 都触发},},})
data
字段,可以用于提升页面更新性能。data
中的字段(包括 setData
设置的字段)既不会展示在界面上,也不会传递给其他组件,仅仅在当前组件内部使用。this.data
中,而不参与任何界面渲染过程,这样有助于提升页面更新性能。Component
构造器的 options
定义段中指定 pureDataPattern
为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段。Component({options: {pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段},data: {a: true, // 普通数据字段_b: true, // 纯数据字段},methods: {myMethod() {this.data._b // 纯数据字段可以在 this.data 中获取this.setData({c: true, // 普通数据字段_d: true, // 纯数据字段})}}})
<view wx:if="{{a}}"> 这行会被展示 </view><view wx:if="{{_b}}"> 这行不会被展示 </view>
pureDataPattern
的正则表达式)。Component({options: {pureDataPattern: /^_/},properties: {a: Boolean,_b: {type: Boolean,observer() {// 不要这样做!这个 observer 永远不会被触发}},}})
Component({options: {pureDataPattern: /^timestamp$/ // 将 timestamp 属性指定为纯数据字段},properties: {timestamp: Number,},observers: {timestamp: function () {// timestamp 被设置时,将它展示为可读时间字符串var timeString = new Date(this.data.timestamp).toLocaleString()this.setData({timeString: timeString})}}})
<view>{{timeString}}</view>
<!-- selectable-group.wxml --><view wx:for="{{labels}}"><label><selectable disabled="{{false}}"></selectable>{{item}}</label></view>
{"componentGenerics": {"selectable": true}}
<selectable-group generic:selectable="custom-radio" />
<selectable-group generic:selectable="custom-checkbox" />
custom-radio
和 custom-checkbox
需要包含在这个 wxml 对应 json 文件的 usingComponents
定义段中。{"usingComponents": {"custom-radio": "path/to/custom/radio","custom-checkbox": "path/to/custom/checkbox"}}
componentGenerics
字段中指定:{"componentGenerics": {"selectable": {"default": "path/to/default/component"}}}
generic:xxx="yyy"
中,值yyy
只能是静态值,不能包含数据绑定。因而抽象节点特性并不适用于动态决定节点名的场景。// behavior.jsmodule.exports = Behavior({definitionFilter(defFields) {defFields.data.from = 'behavior'},})// component.jsComponent({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.jsmodule.exports = Behavior({definitionFilter(defFields, definitionFilterArr) {},})// behavior2.jsmodule.exports = Behavior({behaviors: [require('behavior3.js')],definitionFilter(defFields, definitionFilterArr) {// definitionFilterArr[0](defFields)},})// behavior1.jsmodule.exports = Behavior({behaviors: [require('behavior2.js')],definitionFilter(defFields, definitionFilterArr) {},})// component.jsComponent({behaviors: [require('behavior1.js')],})
definitionFilter
函数,其中 defFields
参数是 behavior2 的定义段, definitionFilterArr
参数即为空数组,因为 behavior3 没有使用其他的 behavior ;definitionFilter
函数,其中 defFields
参数是 behavior1 的定义段, definitionFilterArr
参数是一个长度为1的数组,definitionFilterArr[0]
即为 behavior3 的 definitionFilter
函数,因为 behavior2 使用了 behavior3。用户在此处可以自行决定在进行 behavior1 的声明时要不要调用 behavior3 的 definitionFilter
函数,如果需要调用,在此处补充代码 definitionFilterArr[0](defFields)
即可,definitionFilterArr
参数会由基础库补充传入;definitionFilter
函数。definitionFilter
函数可以理解为当 A 使用了 B 时,A 声明就会调用 B 的 definitionFilter
函数并传入 A 的定义对象让 B 去过滤。此时如果 B 还使用了 C 和 D ,那么 B 可以自行决定要不要调用 C 和 D 的 definitionFilter
函数去过滤 A 的定义对象。// behavior.jsmodule.exports = Behavior({lifetimes: {created() {this._originalSetData = this.setData // 原始 setDatathis.setData = this._setData // 封装后的 setData}},definitionFilter(defFields) {const computed = defFields.computed || {}const computedKeys = Object.keys(computed)const computedCache = {}// 计算 computedconst 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] = valueif (insertToData) data[key] = needUpdate[key] // 直接插入到 data 中,初始化时才需要的操作}return needUpdate}// 重写 setData 方法defFields.methods = defFields.methods || {}defFields.methods._setData = function (data, callback) {const originalSetData = this._originalSetData // 原始 setDataoriginalSetData.call(this, data, callback) // 做 data 的 setDataconst needUpdate = calcComputed(this) // 计算 computedoriginalSetData.call(this, needUpdate) // 做 computed 的 setData}// 初始化 computedcalcComputed(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>
componentPlaceholder
字段用于指定占位组件,如:{"usingComponents": {"comp-a": "../comp/compA","comp-b": "../comp/compB","comp-c": "../comp/compC"},"componentPlaceholder": {"comp-a": "view","comp-b": "comp-c"}}
comp-a
的占位组件为内置组件 view
comp-b
的占位组件为自定义组件 comp-c
(其路径在 usingComponents
中配置)<button ontap="onTap">显示组件</button><comp-a wx-if="{{ visible }}"><comp-b prop="{{ p }}">text in slot</comp-b></comp-a>
visible
为 false
,那么只有 button
会被渲染;点击按钮后,this.setData({ visible: true })
被执行,此时如果 comp-a
, comp-b
均不可用,则页面将被渲染为:<button>显示组件</button><view><comp-c prop="{{ p }}">text in slot</comp-c></view>
comp-a
与 comp-b
准备完成后,页面被替换为:<button>显示组件</button><comp-a><comp-b prop="{{ p }}">text in slot</comp-b></comp-a>
comp-c
),为其指定占位组件是无效的。可以理解为如果一个组件需要作为其他组件的占位组件,则它必须在一开始就是可用的;usingComponents
,收集其将使用到的所有组件的信息;在这个过程中,如果某个被使用到的组件不可用,基础库会先检查其是否有对应的占位组件。如果没有,基础库会中断渲染并抛出错误;如果有,则会标记并在后续渲染流程中使用占位组件替换该不可用的组件进行渲染。不可用的组件会在当前渲染流程结束后尝试准备(下载分包或注入代码等);等到准备过程完成后,再尝试渲染该组件(实际上也是在执行这个流程),并替换掉之前渲染的占位组件。
本页内容是否解决了您的问题?