在微信小程序中更好的封装一个组件

在最开始接触微信小程序的时候,觉得这门语言还挺简单的,感觉就是一个vue的翻版,但是在真正着手开发以后才发现他们两个之间简直有点天差地别,vue语法是很灵活的,但是如果我们把做开发VUE时的那些套路搬到开发微信小程序中的时候,却发现这也不能用了那也不能用了,大有一种有种被束缚住了手脚英雄无用武之地的感觉,甚是蛋疼。但是相较于微信小程序庞大的生态圈,就算这门语言再难使用,我们也要努力在它的基础上做出出色的产品。

本文介绍的是如何在微信小程序中更好的封装一个组件,这里讲解封装一个简单的XButton组件。

在做XButton组件之前,我们思考一下组件中哪些部分可能是公用的,我们需要先将组件的公用部分抽取出来:

const computedBehavior = require('./computed');

module.exports = Behavior({
  behaviors: [computedBehavior],
  properties: {
    customClass: {
      type: String,
      value: ''
    }
  },
  options: {
    addGlobalClass: true
  }
})

这里只是抽离了自定义class,全局样式影响,加了一个官方的computed行为,用于实现和vue中compute计算方法类似的行为。computed行为的代码实现如下:

module.exports = Behavior({
  lifetimes: {
    created() {
      this._computedCache = {}
      this._originalSetData = this.setData
      this.setData = this._setData
      this._doingSetData = false
    }
  },
  definitionFilter(defFields) {
    const computed = defFields.computed || {}
    const computedKeys = Object.keys(computed)

    // 计算 computed
    const calcComputed = (scope) => {
      const needUpdate = {}
      const computedCache = scope._computedCache || scope.data

      for (let i = 0, len = computedKeys.length; i < len; i++) {
        const key = computedKeys[i]
        const getter = computed[key]

        if (typeof getter === 'function') {
          const value = getter.call(scope)

          if (computedCache[key] !== value) {
            needUpdate[key] = value
            computedCache[key] = value
          }
        }
      }

      return needUpdate
    }

    // 初始化 computed
    const initComputed = () => {
      defFields.data = defFields.data || {}

      // 先将 properties 里的字段写入到 data 中
      const data = defFields.data
      const properties = defFields.properties
      const hasOwnProperty = Object.prototype.hasOwnProperty
      if (properties) {
        // eslint-disable-next-line complexity
        Object.keys(properties).forEach(key => {
          const value = properties[key]
          let oldObserver

          // eslint-disable-next-line max-len
          if (value === null || value === Number || value === String || value === Boolean || value === Object || value === Array) {
            properties[key] = {
              type: value,
            }
          } else if (typeof value === 'object') {
            if (hasOwnProperty.call(value, 'value')) {
              // 处理值
              data[key] = value.value
            }

            if (hasOwnProperty.call(value, 'observer') && typeof value.observer === 'function') {
              oldObserver = value.observer
            }
          }

          // 追加 observer,用于监听变动
          properties[key].observer = function (...args) {
            const originalSetData = this._originalSetData

            if (this._doingSetData) {
              // eslint-disable-next-line no-console
              console.warn('can\'t call setData in computed getter function!')
              return
            }

            this._doingSetData = true

            // 计算 computed
            const needUpdate = calcComputed(this)

            // 做 computed 属性的 setData
            originalSetData.call(this, needUpdate)

            this._doingSetData = false

            if (oldObserver) oldObserver.apply(this, args)
          }
        })
      }

      // 计算 computed
      calcComputed(defFields, true)
    }

    initComputed()

    defFields.methods = defFields.methods || {}
    defFields.methods._setData = function (data, callback) {
      const originalSetData = this._originalSetData

      if (this._doingSetData) {
        // eslint-disable-next-line no-console
        console.warn('can\'t call setData in computed getter function!')
        return
      }

      this._doingSetData = true

      // TODO 过滤掉 data 中的 computed 字段
      const dataKeys = Object.keys(data)
      for (let i = 0, len = dataKeys.length; i < len; i++) {
        const key = dataKeys[i]

        if (computed[key]) delete data[key]
      }

      // 做 data 属性的 setData
      originalSetData.call(this, data, callback)

      // 计算 computed
      const needUpdate = calcComputed(this)

      // 做 computed 属性的 setData
      originalSetData.call(this, needUpdate)

      this._doingSetData = false
    }
  }
})

有朋友可能会说这么点公用的东西直接在组件中写不就是了吗?何必做抽离,这样想就错了,如果我们做了抽离,以后需要加一个组件的共有行为只需要改一个文件就好了,如果单独写每一个组件都要改一遍,那该是多么的痛苦呀,抽离了公用部分接着我们就开始实现我们的XButton组件了。

首先在我们的组件目录中新建XButton文件件然后分别新建如下文件,这个组件实现相对简单没什么可说的所以直接附上代码:

1、index.js,代码如下:

const baseComponent = require('../../behaviors/baseComponent');

Component({
  behaviors: [baseComponent],

  properties: {
    text: {
      type: String,
      value: '按钮'
    },
    customClass: {
      type: String,
      value: ''
    },
    disabled: {
      type: Boolean,
      value: false
    },
    loading: {
      type: Boolean,
      value: false
    }
  },

  data: {

  },

  methods: {
    handleTap: function(e) {
      this.triggerEvent('click', e);
    }
  }
});

2、index.json,代码如下:

  "component": true
}

3、index.wxml,代码如下:

<button class="btn {{customClass}}" type="primary" disabled="{{disabled}}" loading="{{loading}}" bindtap="handleTap">
  <slot></slot>
  {{text}}
</button>

4、index.wxss,代码如下:

.btn {
  width: 100%;
  height: 96rpx;
  line-height: 96rpx;
  text-align: center;
  font-size: 32rpx;
  color: #fff;
  font-weight: 500;
  box-sizing: border-box;
  background-color: #222222;
  padding: 0 15px;
}
.btn::after {
  border: none;
}
.btn.btn-white {
  color: #000;
  background-color: #fff;
  border: 1px solid #cbcbcb;
}
.btn.btn-yellow {
  background:linear-gradient(to right,#FEAB00,#FC7300);
  font-size: 32rpx;
  font-weight: 500;
  color: #fff;
}
.btn.btn-green {
  background-color: linear-gradient(to right,#1EB824,#1EB824);
  font-size: 28px;
}
.btn.btn-yellow.disabled {
  color: rgba(255,255,255,0.4);
}
.btn.btn-radius {
  border-radius: 60rpx;
}

使用的时候只需要在需要使用的页面或者组件的配置文件中配置好组件引用路径就好了,类似于下面这样:

{
  "usingComponents": {
    "XButton": "/common/components/XButton/index"
  }
}

注意引用的文件路径需要根据实际的项目来。

最后附上效果图:

  • 支付宝二维码 支付宝
  • 微信二维码 微信

本文地址: /wx-component-create.html

版权声明: 本文为原创文章,版权归 逐梦个人博客 所有,欢迎分享本文,转载请保留出处!

相关文章