Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

WeChat gadgets Custom component extensions


May 17, 2021 WeChat Mini Program Development Document


Table of contents


Custom component extensions

To better customize the functionality of custom components, you can use custom component extension mechanisms. Support starts with the small program base library version 2.2.3.

The effect of the extension

To better understand the effect of the extension, give an example first:

// 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
  }
})

As you can see from the examples, the extension of a custom component actually provides the ability to modify the custom component definition segment, as in the example above, to modify the contents of the data definition segment in the custom component.

Use extensions

The Behavior() constructor provides a new definition segment, definitionFilter, to support custom component extensions. DefinitionFilter is a function that injects two arguments when called, the first is the definition object that uses the behavior's component/behavior, and the second argument is the list of the behavior's definitionFilter functions used by the behavior.

Here's 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')],
})

One custom component and three behaviors are declared in the code above, each using a definitionFilter definition segment. So in the order of the declaration, something like this will happen:

  1. The definitionFilter function of behavior3 is called when the declaration of behavior2 is made, where the defFields parameter is the definition segment of behavior2, and the definitionFilterArr argument is an empty array because behavior3 does not use other behaviors.
  2. The definitionFilter function of behavior2 is called when the declaration of behavior1 is made, where the defFields parameter is the definition segment of behavior1, the definitionFilterArr parameter is an array of lengths of 1, and the definitionFilterArr is the definitionFilter function of behavior3 because B ehavior2 uses behavior3. The user can decide here whether to call the definitionFilter function of behavior3 when the behavior1 declaration is made, and if the call is required, the code definitionFilterArr is supplemented here, and the definitionFilterArr parameter is supplemented by the underlying library.
  3. Similarly, the definitionFilter function of behavior1 is called when the component declaration is made.

In a nutscape, the definitionFilter function can be understood as saying that when A uses B, the A declaration calls the definitionFilter function of B and passs in A's definition object for B to filter. At this point, if B also uses C and D, B can decide whether to call the definition of C and D functions to filter the defined objects of A.

Real-life case

Here's how easy it is to implement the computational properties of custom components with extensions:

// 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
  }
})

Use 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: the existing setData is re-encapsulated, the values of the fields in the computed are calculated each time the setData is calculated, and then set to data, which has the effect of calculating properties.

This implementation is only shown as a simple case study and should not be used directly in a production environment.

Official extension package