组件库搭建封装的一些想法

组件库搭建封装的一些想法

最近一直在进行组件封装开发,在这儿记录一下开发心得。

组件进行二次封装

对于element-ui,大家应该都有使用过,而每次开发都需要设置一些默认值,或者多个组件需要进行组合使用...,对于这些每次开发都需要设置一遍,而且每个人的开发风格不同,导致一些本应该有的小功能,没能体现出来,因而都需要进行处理。

属性,事件

在二次封装时,需要借助v-bind="$attrs",v-on="$listeners",inheritAttrs: false 来进行组件属性事件传递

tips:

  • 属性:props
  • 事件:event 通过@ 进行调用的
  • 方法:method 通过$refs进行调用的

示例:

  <el-select
    v-model="value"
    clearable
    placeholder="请选择"    
  >
    <el-option
      v-for="(item, key) in options"
      :key="key"
      :label="item.label"
      :value="item.value"
    ></el-option>
  </el-select>

  <script>
    export default {
      data() {
        return {
          value: '',
          list: [
            { 1: '第一' },
            { 2: '第二' },
            { 3: '第三' },
            { 4: '第四' },
            { 5: '第五' }
          ]
        }
      },
      computed: {
        options() {
          return this.list.map(item => {
            const value = Object.keys(options)[0]
            const label = item[value]
            return {
              value,
              label
            }
          })
        }
      }
    }
  </script>

对于上面的形式应该都有遇见过,这样每次使用都要如此处理,特别是大型表单,涉及到多个el-select时,写起来就有些枯燥了,因而进行简化处理(这个过程也意味着使用时需要知晓规则)

  • 组件封装
  <el-select
    v-bind="$attrs"
    v-on="$listeners"
    :clearable="clearable"
    :placeholder="placeholder"    
  >
    <slot>
      <el-option
        v-for="(item, key) in relOptions"
        :key="key"
        :label="item.label"
        :value="item.value"
      ></el-option>
    </slot>
  </el-select>

  <script>
    export default {
      name: 'my-select',
      inheritAttrs: false, // 此处加上是为了防止一些不必要的问题 https://cn.vuejs.org/v2/api/#inheritAttrs
      props: {
        placeholder: {
          type: String,
          default: '请选择'
        },
        clearable: {
          type: Boolean,
          default: true
        },
        options: {
          type: Array,
          default: () => []
        }
      },
      computed: {
        relOptions() {
          return this.options.map(item => {
            const value = Object.keys(options)[0]
            const label = item[value]
            return {
              value,
              label
            }
          })
        }
      }
    }
  </script>

那之后每次使用就会很轻松了,对于上面的例子就可以简单地使用即可,而且 el-select 原有的属性和事件都可以继续使用

  <my-select v-model="value" :options="list"></my-select>
  <script>
    export default {
      data() {
        return {
          value: '',
          list: [
            { 1: '第一' },
            { 2: '第二' },
            { 3: '第三' },
            { 4: '第四' },
            { 5: '第五' }
          ]
        }
      }
    }
  </script>

若是想进一步处理,可以继续进行定制处理即可。这里需要补充一下,对于方法需要额外处理一下。上面的封装的还不是很完善,方法的focus,blur还不支持(除非自己通过$refs一层层去找),接着进行处理。

  <el-select
    v-bind="$attrs"
    v-on="$listeners"
    :clearable="clearable"
    :placeholder="placeholder"
    ref="my_select"  
  >
    <slot>
      <el-option
        v-for="(item, key) in relOptions"
        :key="key"
        :label="item.label"
        :value="item.value"
      ></el-option>
    </slot>
  </el-select>

  <script>
    const methodList = ['blur', 'focus']
    const methods = {}
    methodList.forEach(methodName => {
      methods[methodName] = function(...args) {
        const _ref = this.$refs.my_select
        if (_ref && _ref[methodName]) {
          _ref[methodName](...args)
        }
      }
    })

    export default {
      name: 'my-select',
      inheritAttrs: false, // 此处加上是为了防止一些不必要的问题 https://cn.vuejs.org/v2/api/#inheritAttrs
      mixins: [methods],
      props: {
        placeholder: {
          type: String,
          default: '请选择'
        },
        clearable: {
          type: Boolean,
          default: true
        },
        options: {
          type: Array,
          default: () => []
        }
      },
      computed: {
        relOptions() {
          return this.options.map(item => {
            const value = Object.keys(options)[0]
            const label = item[value]
            return {
              value,
              label
            }
          })
        }
      }
    }
  </script>

至此,my-select 正常使用算是差不多了(数据格式需要前后台统一即可),想要百分百保持与el-select还是有一些困难,但是对于自己负责的相关项目都是完美支持,这样就可以了。但是对于一些特殊的组件有时还需要看源码才知道如何应对。

双向数据绑定

v-model用起来很舒服,其实本质是 v-bind,v-on简化的语法糖,项目中的使用

实例:

<template>
  <el-cascader v-bind="$attrs" v-on="$listeners" v-model="model" ref="sti_cascader"></el-cascader>
</template>

<script>
import { getMethods } from './../../../utils/mixin.js'
const METHOD_NAMES = [
  'getCheckedNodes'
]
const methods = getMethods(METHOD_NAMES, 'sti_cascader') // 用来生成 methods方法

export default {
  name: 'my-cascader',
  inheritAttrs: true,
  mixins: [methods],
  props: {
    value: {
      type: Array,
      default: () => []
    }
  },
  computed: {
    model: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    }
  }
}
</script>

层级

组件层级嵌套未知时,想要获取父子的事件,需要通过父调子或子调父递归查找,但是这个存在很多不确定性。因而在封装组件时,推荐使用 provide,inject 的方式进行处理

实例:

  • my-anchor
<template>
  <div class="my-anchor-container" :style="`height: ${realHeight}`">
    <ul class="link-wrapper">
      <slot name="link"></slot>
    </ul>
    <div class="list-wrapper" ref="listWrapper" id="listWrapper">
      <slot />
    </div>
  </div>
</template>

<script>
import scrollTo, { getTargetScrollLocation } from './scroll.js'

export default {
  name: 'my-anchor',

  provide() {
    return {
      anchor: this
    }
  },

  data() {
    return {
      active: ''
    }
  },

  methods: {
    handleAnchorClick(id) {
      this.active = id
      this.init(id)
    },
  }
  ...
}
</script>
  • my-anchor-link
<template>
  <li
    :class="['my-anchor-link',  isActive ? 'my-anchor-link--active' : '' ]"
    :data-id="id"
    @click="handleClick"
  >
    <slot>
      <span class="my-anchor-link__title">{{title}}</span>
    </slot>
  </li>
</template>

<script>
export default {
  name: 'my-anchor-link',
  inject: ['anchor'],
  props: {
    
    id: {
      type: String,
      required: true
    },

  },

  computed: {
    isActive () {
      return this.anchor.active === this.id
    }
  },

  methods: {
    handleClick () {
      this.anchor.handleAnchorClick(this.id)
    }
  }
}
</script>

包体积

由于在打包发布时,发现组件库偏大,通过vue-cli-service build --report 进行查看,发现很多插件都只是使用了其中一小部分功能,从而导致整个包偏大

  • 插件

    • moment => day.js
    • lodash => 手写相应的方法 lodash-es
    • vuedraggable => html5sortable
  • gizp

总结

开发的过程中,更多地是考虑如何工程化,如何更好地兼容之前的和为未来做扩展,这几块还要好好努力。依旧记得尤大说过,如何做到解耦及模块化,这个是我所欠缺的,也许已经实现但是不知道如何去描述了。

下一步是对工程化进一步发起奋斗了。

posted @ 2020-11-21 16:23  木石心  阅读(273)  评论(0编辑  收藏  举报