# Custom Component Extension
To better customize the functionality of custom components, you can use the custom component extension mechanism. This mechanism is supported from Mini Program base library 2.2.3 and later.
# Effect of Extension
In order to better understand the effect of the extension, look at the example below:
// 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) // You can see that the output here is behavior instead of component
}
})
As you can see from the example, extensions to custom components provide the ability to modify the data
definition field in custom component.
# Using Extensions
The Behavior()
constructor provides a new definition field, definitionFilter
to support the custom component extension. The definitionFilter
function is injected with the following two parameters when it is called: the component/behavior definition object that uses this behavior, and the definitionFilter
function list of the behavior used by this behavior.
Here is an example:
// 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')],
})
The code above declares 1 custom component and 3 behaviors. Each behavior uses a definitionFilter
definition field. Then, the following events occur in the order of the declaration:
- When the behavior2 declaration is made, the
definitionFilter
function of behavior3 is called. ThedefFields
parameter is the definition field of behavior2, and thedefinitionFilterArr
parameter is an empty array because behavior3 does not use any other behavior. - When the behavior1 declaration is made, the
definitionFilter
function of behavior2 is called. ThedefFields
parameter is the definition field of behavior1, thedefinitionFilterArr
parameter is an array with a length of 1, anddefinitionFilterArr[0]
is thedefinitionFilter
function of behavior3 because behavior3 is used by behavior2. You can decide whether to call thedefinitionFilter
function of behavior3 when the behavior1 declaration is made. If the call is needed, add the codedefinitionFilterArr[0](defFields)
here and thedefinitionFilterArr
parameter will be passed by the base library. - Similarly, when component is declared, the
definitionFilter
function of behavior1 is called.
To put it simply, the definitionFilter
function can be interpreted as the fact that when A uses B, the A declaration calls the definitionFilter
function of B and passes the definition object of A for B to filter. If B also uses C and D, it can decide whether to call the definitionFilter
function of C and D to filter the defined object of A.
Code example:
# Use Case
The following is a simple implementation of the computed property for a custom component with an extension:
// behavior.js
module.exports = Behavior({
lifetimes: {
created() {
this._originalSetData = this.setData // Original setData
this.setData = this._setData // Packaged setData
}
},
definitionFilter(defFields) {
const computed = defFields.computed || {}
const computedKeys = Object.keys(computed)
const computedCache = {}
// Calculate computed
const calcComputed = (scope, insertToData) => {
const needUpdate = {}
const data = defFields.data = defFields.data || {}
for (let key of computedKeys) {
const value = computed[key].call(scope) // Calculate new value
if (computedCache[key] !== value) needUpdate[key] = computedCache[key] = value
if (insertToData) data[key] = needUpdate[key] // Insert the value directly into the data, which is only needed during initialization
}
return needUpdate
}
// Override the setData method
defFields.methods = defFields.methods || {}
defFields.methods._setData = function (data, callback) {
const originalSetData = this._originalSetData // Original setData
originalSetData.call(this, data, callback) // Perform setData for data
const needUpdate = calcComputed(this) // Calculate computed
originalSetData.call(this, needUpdate) // Perform setData for computed
}
// Initialize computed
calcComputed(defFields, true) // Calculate computed
}
})
Used in components:
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>
The implementation principle is simple: Perform a secondary encapsulation for an existing setData, calculate the value of each field in computed each time you perform setData, and then set it into the data to implement computed property.
This example is for reference only. Do not use it directly for production.