[举个栗子]增加组件通用性的几个点

[举个栗子]增加组件通用性的几个点最近在做项目的时候,看到有两个功能一样,但是交互,样式不一样的需求,为了图方便维护,就封装了组件,发现一个看似简单的组件,如果要封装得通用些,要考虑的东西其实也不少。 然后组件就无法使用了。 这个时候,肯定不是复制一个文件,改下样式再写一个组件,只能把原来的组件改得通用些,能适…

写代码有时候就和弹簧加工一样。一个看似简单的物品,加工起来未必简单

1.前言

最近在做项目的时候,看到有两个功能一样,但是交互,样式不一样的需求,为了图方便维护,就封装了组件,发现一个看似简单的组件,如果要封装得通用些,要考虑的东西其实也不少。

该文章只是举例说明可以从哪些点入手,增加组件的通用性。以及提供一些封装的思路。提及的组件仍然与项目需求有挺大的关系,差不多是针对项目的定制开发,在其他项目上可能还不能开箱即用,要使用的话,还需要对组件进行修改。

2.先看组件

[举个栗子]增加组件通用性的几个点

这个组件看着就很简单,一下就写好了

出于篇幅的考虑,css ,以及一些不关联的 js 代码就不提供了,需要看源码可以移步: 文章例子源码:HandleButtonOld,项目完整代码:项目代码

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index}" @click="switchCur(item,index)" >
      <ec-text v-if="item.fileType==='text'" :src="item.fileUrl"></ec-text>
      <video :src="item.fileUrl" v-if="item.fileType==='video'"></video>
      <audio :src="item.fileUrl" controls="controls" v-if="item.fileType==='audio'"></audio>
      <ec-image :src="item.fileUrl" v-if="item.fileType==='image'" />
      <ul class="customer-form-view-action-box">
        <li class="iconfont icon-icon-cus-edit" @click.stop="handleEvent('edit',index)"></li>
        <li class="iconfont icon-icon-cus-up" @click.stop="handleEvent('up',index)" v-if="index!==0" ></li>
        <li class="iconfont icon-icon-cus-down" @click.stop="handleEvent('down',index)" v-if="index!==value.length-1" ></li>
        <li class="iconfont icon-icon-cus-del" @click.stop="handleEvent('delete',index)"></li>
      </ul>
    </div>
  </div>
</template>
<script> export default { name: 'HandleButton', componentName: 'HandleButton', props: { value: { type: Array, default () { return [] } } }, data () { return { nowClickIndex: '' } }, mounted () {}, methods: { handleEvent (type, index) { let _list = JSON.parse(JSON.stringify(this.value)) let _nowItem = _list[index] switch (type) { case 'up': this.nowClickIndex-- _list.splice(index, 1) _list.splice(index - 1, 0, _nowItem) break case 'down': this.nowClickIndex++ _list.splice(index, 1) _list.splice(index + 1, 0, _nowItem) break case 'delete': _list.splice(index, 1) } this.$emit('input', _list) this.$emit(type, _nowItem, index) }, switchCur (item, index) { this.nowClickIndex = index } } } </script>
<style lang="scss" scoped>
// 略
</style>

3.改进优化

组件用起来也很简单,简单一行代码就出来了

<handle-button-old v-model="sortData"/>

sortData

sortData: [
    {
      fileType: 'text',
      content: '前端开发',
      index: 2,
      size: 12
    },
    {
      fileNmae: '251bb6d882024b11a6051d604ac51fc3.jpeg',
      fileType: 'image',
      fileUrl:
        'https://file-cdn-china.wechatify.net/marketing/sms/mms_material/53ce422f14e516af0eb9a5c7251cc1ca.jpeg',
      index: 3,
      size: 101109,
      fileName: '53ce422f14e516af0eb9a5c7251cc1ca.jpeg'
    },
    {
      fileType: 'text',
      content: '守候',
      index: 5,
      size: 12
    }
 ]

但是如果页面上又有这样一个需求,功能一样,样式排版不一样,比如下图这样

[举个栗子]增加组件通用性的几个点

然后组件就无法使用了。

这个时候,肯定不是复制一个文件,改下样式再写一个组件,只能把原来的组件改得通用些,能适合更多需求。

遇到这样的需求,非常不建议复制一个文件,再写一个组件。如果下次再有这种情况,又要再复制一个文件,再写一个组件。就可能会导致组件文件非常多,影响维护

让组件更通用些,能适合更多需求,主要就是要把经常会变的因素抽取出来,交给用户自定义,至于有哪些地方可以改进优化?下面就简单列举一下

3-1.支持自定义内容

首页,看到两个需求,排版样式和显示字段就不一样了。不知道以后第三种,第四种排版样式,也不知道会显示什么字段。所以这里最好是他操作按钮抽出来,作为组件封装,至于怎么排版,显示什么字段,组件不管,只需要提供一个 slot 由用户自定义。

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index}" @click="switchCur(index)" >
      <!-- 提供slot -->
      <slot :data="item"></slot>
      
      <ul class="customer-form-view-action-box">
        <!--重复代码略-->
      </ul>
    </div>
  </div>
</template>

页面调用

<handle-button-old v-model="sortData">
<!--提供 slot-scope 需要什么字段以及排版可以自定义-->
  <div slot-scope="item" class="view-item">
    <span v-if="item.data.fileType==='text'">{{item.data.content}}123</span>
    <video :src="item.data.fileUrl" v-if="item.data.fileType==='video'"></video>
    <audio :src="item.data.fileUrl" controls="controls" v-if="item.data.fileType==='audio'"></audio>
    <img :src="item.data.fileUrl" v-if="item.data.fileType==='image'" />
  </div>
</handle-button-old>

3-2.支持自定义选中样式

来到这里,看一下选中的效果,

[举个栗子]增加组件通用性的几个点

除了显示几个操作按钮之外,还有一个蓝色的边框线,但是不同需求,选中效果可能是不一样的,比如有一个地方要用灰色双实线,再有一个地方要用白色实现,边距增加 30px 等等。无法猜测下一次用这个组件的时候,选中样式是什么。所以选中样式不能在 handle-button-old 内部写死或者判断,只能让用户自定义。我们能提供的,就是给一个字段,告诉用户哪一项是当前选中的。

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index}" @click="switchCur(item,index)" >
      <!--对 item 进行封装-->
      <slot :data="getItem(item,index)"></slot>
      //代码略 
    </div>
  </div>
</template>
<script> export default { //代码略 methods: { getItem (item, index) { // 把索引($index) 和 当前是否选中($select) 字段合并到 item 里面 //这里是顺便把索引传过去了,是为了以后的不时之需,这里不展开讲 return Object.assign({}, item, { $index: index, $select: this.nowClickIndex === index }) } //代码略 } } </script>

页面调用

<!--根据 $select 判断是否添加 cur 这个 class-->
<handle-button-old v-model="sortData">
  <div slot-scope="item" class="view-item" :class="{'cur':item.data.$select}">
    //代码略
  </div>
</handle-button-old>
<style lang="scss"> .view-item { padding: 10px; border:4px dashed transparent; &.cur{ border:4px double #ccc; } } </style>

这样就可以让用户自定义选中的样式了

[举个栗子]增加组件通用性的几个点

3-2.设置操作按钮的显示位置和方向

再看一下两个需求的样式

[举个栗子]增加组件通用性的几个点

[举个栗子]增加组件通用性的几个点

首先看到按钮的位置和方向是不一样的。按钮的位置,可以给默认值,但也要让用户可以自定义。要确定按钮的定位,则 handle-button-old 组件需要提供 top,right,bottom,left,四个参数。为了方便定位,除了可以设置具体像素,百分比之外,还要支持用户输入’center’,方便用户设置垂直或者水平居中。

按钮的方向就需要提供 direction 参数,用户输入 horizontal 就垂直显示,输入 vertical 就水平显示

handle-button-old

<template>
  <div class="ec-handle">
    <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index}" @click="switchCur(item,index)" >
      <slot :data="getItem(item,index)"></slot>
      <!--绑定style,以及根据direction 设置 class,设置ul的样式-->
      <ul class="customer-form-view-action-box" :style="ulPosition" :class="{'handle-vertical':direction==='vertical'}" >
        //代码略
      </ul>
    </div>
  </div>
</template>
<script> export default { //代码略 props: { //代码略 top: { type: [String, Number], default: '0' }, bottom: { type: [String, Number], default: 'auto' }, left: { type: [String, Number], default: 'auto' }, right: { type: [String, Number], default: '-26px' }, direction: { type: String, default: 'horizontal' } }, computed: { ulPosition () { let obj = { left: this.left, right: this.right, top: this.top, bottom: this.bottom } let _x = '0' let _y = '0' if (this.top === 'center' || this.bottom === 'center') { obj.top = '50%' obj.bottom = 'auto' _y = '-50%' obj.transform = `translate(${_x},${_y})` } if (this.left === 'center' || this.right === 'center') { obj.left = '50%' obj.right = 'auto' _x = '-50%' obj.transform = `translate(${_x},${_y})` } return obj } } } </script>
<style lang="scss" scoped> .ec-handle--item { position: relative; ul { position: absolute; right: -26px; top: 0; display: none; line-height: 24px; &.handle-vertical { li { display: inline-block; vertical-align: top; } } } } </style>

页面调用

<!--设置按钮的位置和方向-->
<handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      bottom="center"
    >
  <div slot-scope="item" class="handle-item">
    //代码略
  </div>
</handle-button-old>
export default {
  data () {
    return {
      iconByFileType: {
        text: 'icon-wenben',
        image: 'icon-tupian1',
        video: 'icon-shipin',
        audio: 'icon-yinpin',
        link: 'icon-duanlian'
      },
      //代码略
    }
  },
  //代码略
  methods: {
    formatSize (val) {
      if (val === 0) {
        return '0B'
      }
      let sizeObj = {
        MB: 1048576,
        KB: 1024,
        B: 1
      }
      val = +val
      for (let key in sizeObj) {
        if (val >= sizeObj[key]) {
          return +(val / sizeObj[key]).toFixed(2) + key
        }
      }
    },
    //代码略
  }
}
</script>
<style lang="scss" scoped>
//代码略
</style>

[举个栗子]增加组件通用性的几个点

这样效果就实现了

3-3.设置操作按钮的显示方式

想必大家已经看到问题了,【3-2】最后看到的结果是只有其中一项是有操作按钮的,而【3-2】一开始,看到的需求是所有的结果都要显示出来。那么这里就要设置一个 display 属性了,设置操作按钮的显示方式。目前提供三个值’default’-选中的项显示,’visible’-所有项显示,’none’-不显示。

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot>
      <ul class="customer-form-view-action-box"
          :style="ulPosition"
          v-if="display!=='none'"
          :class="{'handle-vertical':direction==='vertical'}"
      >
        //代码略
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    display: {
      type: [String],
      default: 'default'
    },
    //代码略
  },
  //代码略
}
</script>

页面调用

<handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      display="visible"
      bottom="center"
    >
    //代码略
</handle-button-old>

这样就能实现了

[举个栗子]增加组件通用性的几个点

3-4.点击操作按钮前的触发动作

很多人在开发上会遇到一些需求,特别是在执行比如删除,清空等“危险操作”之前,要给一个弹窗或者其他方式的提醒,让用户谨慎操作。而这个组件的操作按钮,也有可能是“危险操作”。所以需要让用户可以自定义操作前的回调。

拿文章提及的 handle-button-old 组件来说,如果需求是“删除”按钮前需要给提醒弹窗,其他的按钮直接操作。

handle-button-old

<template>
    <!--代码略-->
</template>
<script>
export default {
  props: {
    beforeDelete: {
      type: Function
    },
    beforeUp: {
      type: Function
    },
    beforeDown: {
      type: Function
    },
    beforeEdit: {
      type: Function
    }
  },
  data () {
    return {
      nowClickIndex: '',
      eventType: '',
      curHandleIndex: ''
    }
  },
  methods: {
    /** * @description 执行事件 */
    handle () {
      let _list = this.value
      let _nowItem = _list[this.curHandleIndex]
      switch (this.eventType) {
        case 'up':
          this.nowClickIndex--
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex - 1, 0, _nowItem)
          break
        case 'down':
          this.nowClickIndex++
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex + 1, 0, _nowItem)
          break
        case 'delete':
          _list.splice(this.curHandleIndex, 1)
      }
      this.$emit('input', _list)
      this.$forceUpdate()
      this.$emit(this.eventType, _nowItem, this.curHandleIndex)
    },
    /** * @description 处理事件 */
    handleEvent (eventType, item, index) {
      // 记录事件类型
      this.eventType = eventType
      // 记录当前操作项的索引
      this.curHandleIndex = index
      let _type = eventType.substr(0, 1).toUpperCase() + eventType.substr(1)
      if (typeof this[`before${_type}`] === 'function') {
        // 把当前操作的函数,当前项,索引作为参数,传给调用函数
        this[`before${_type}`](this.handle, item, index)
      } else {
        this.handle()
      }
    },
  }
  // 代码略
}
</script>

页面调用

<template>
    <handle-button-old v-model="sortData" direction="vertical" right="6px" display="visible" bottom="center" :beforeDelete="handleBefore" >
      <!--代码略-->
    </handle-button-old>
</template>
<script> methods: { /** * @description 操作前的回调 * @augments done - 用于执行操作 * @augments item - 当前项 * @augments index - 当前索引 */ handleBefore (done, item, index) { // 点击确认才进行操作,点击取消不做处理 this.$confirm('确认进行删除操作?') .then(() => { done() }) .catch(() => {}) } } </script>

[举个栗子]增加组件通用性的几个点

3-5.切换选中的项的触发动作

比如有需求,点击切换选中的时候,需要拿当前项的数据,做为请求的参数。实现这个需求,只需要在 handle-button-old 组件里面需要提供一个自定义事件即可

handle-button-old

methods:{
    switchCur (item, index) {
      this.nowClickIndex = index
      //触发自定义事件
      this.$emit('change', item, index)
    }
}

页面调用

<handle-button-old
  style="margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  right="6px"
  display="visible"
  bottom="center"
  @change="handleChange"
>
  
</handle-button-old>

3-6.取消选中

可能大家在一早已经发现了这个问题,如果选中了某一项,出现了下面情况。

[举个栗子]增加组件通用性的几个点

但是如果需求是取消选中呢?那就做不到了。从代码逻辑上来讲,只要选中了,就要选中一项,没办法取消。所以,在 3-5 的 switchCur 函数就需要判断一下,如果点击的是当前项,就取消选中

handle-button-old

methods:{
    switchCur (item, index) {
      
      if (this.display === 'visible') {
        return
      }
      // 如果点击的是当前项,就取消选中
      this.nowClickIndex = this.nowClickIndex !== index ? index : ''
      this.$emit('change', item, index)
    }
}

3-7.按钮折叠显示

在上面的图片可以看到,按钮要么是横向排列,要么是竖向排列。如果哪天需求觉得按钮太占位置,需要折叠显示按钮,这个也很简单就可以兼容了,给 handle-button-old 加个 type 参数判断下要根据什么方式显示就可以了。

handle-button-old

<template>
  <div class="ec-handle"> <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index || display==='visible'}" @click="switchCur(item,index)" > <slot :data="getItem(item,index)"></slot> <!--如果不是 dropdown 类型以及 dispaly 不为 none--> <ul class="customer-form-view-action-box" :style="ulPosition" v-if="type!=='dropdown'&&display!=='none'" :class="{'handle-vertical':direction==='vertical'}" > <!--代码略--> </ul> <!--如果是dropdown类型--> <el-dropdown v-else-if="type==='dropdown'" class="customer-form-view-action-box" :style="ulPosition" style="position:absolute;"> <span class="el-dropdown-link"> 操作<i class="el-icon-arrow-down el-icon--right"></i> </span> <el-dropdown-menu> <el-dropdown-item><div @click.stop="handleEvent('edit',item,index)">编辑</div></el-dropdown-item> <el-dropdown-item v-if="index!==0"><div @click.stop="handleEvent('up',item,index)">上移</div></el-dropdown-item> <el-dropdown-item v-if="index!==value.length-1"><div @click.stop="handleEvent('down',item,index)">下移</div></el-dropdown-item> <el-dropdown-item><div @click.stop="handleEvent('delete',item,index)">删除</div></el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div>
</template>
export default {
  name: 'HandleButton',
  componentName: 'HandleButton',
  props: {
    type: {
      type: String
    }
    // 代码略
  }
  // 代码略
}

页面调用

type=”dropdown” 之后,direction 参数会不起效

<!--type="dropdown" 折叠显示操作按钮,否则为平铺并列显示-->
<handle-button-old
  style="margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  type="dropdown"
  right="6px"
  display="visible"
  bottom="center"
  :beforeDelete="handleBefore"
>
  <div slot-scope="item" class="handle-item">
    <div class="message-item___box">
      <span class="message-item___icon iconfont" :class="iconByFileType[item.data.fileType]"></span>
    </div>
    <div class="message-item___info">
      <p v-if="item.data.fileType==='text'">
        <span>{{item.data.content}}</span>
      </p>
      <p v-else>{{item.data.fileName}}</p>
      <span class="message-item___info_size">{{formatSize(item.data.size)}}</span>
    </div>
  </div>
</handle-button-old>

[举个栗子]增加组件通用性的几个点

3-8.按钮显示方式

回到这个场景,可能大家在开发的时候已经想到了,要出现操作按钮,必须要点击某一项才会出现。但很多时候的需求,需要鼠标 放上去的时候就显示操作按钮,不需要点击。要实现这个,只需要添加 一个 trigger 参数,triggle 默认为 ‘click’-点击出现,’hover’-鼠标放上去出现

[举个栗子]增加组件通用性的几个点

<template>
  <div class="ec-handle">
    <div class="ec-handle--item" v-for="(item,index) in value" :key="index" :class="{'cur':nowClickIndex===index || display==='visible'}" @click="switchCur(item,index,'click')" @mouseenter="switchCur(item,index,'hover')" @mouseleave="handleMouseLeave" >
        <!--代码略-->
    </div>
  </div>
</template>
<script> export default { props: { // 代码略 triggle: { type: String, default: 'click' } }, methods: { // 加上 eventType 参数区分当前触发的事件 switchCur (item, index, eventType) { if (this.display === 'visible') { return } //如果当前触发事件与 triggle 不同,则不执行操作 if (eventType !== this.triggle) { return } this.nowClickIndex = this.nowClickIndex !== index ? index : '' this.$emit('change', item, index) }, handleMouseLeave () { // 如果triggle 为 hover ,鼠标移除的时候,取消选中当前项 if (this.triggle === 'hover') { this.nowClickIndex = '' } } } // 代码略 } </script>

页面调用

<handle-button-old v-model="sortData" triggle="hover">
  <!--代码略-->
</handle-button-old>

[举个栗子]增加组件通用性的几个点

3-9.关于其他

首次 handle-button-old 这个组件为例,列举了一些改进优化的功能。如果想折腾,还是有不少功能可以折腾的,比如按钮的样式(图标的颜色、形状、背景颜色、大小等等)、间距,自定义按钮等。至于要不要折腾,就看需求有没有必要了,具体情况,具体分析。文章的这个,还是很简单的一个组件,如果是复杂的组件,需要优化的点可能就更多了。

4.小结

封装组件的时候,如果一开始对组件的要求比较单一,或者时间比较紧急,也可以先封装个有基本功能,能满足需求的组件。之后如果发现组件不能满足业务需求了,再进行改进和优化也不迟。

————————-华丽的分割线——————–

想了解更多,和我交流,请添加我微信。或者关注我的微信公众号:守候书阁

[举个栗子]增加组件通用性的几个点[举个栗子]增加组件通用性的几个点

今天的文章[举个栗子]增加组件通用性的几个点分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/13668.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注