先别着急封装组件,带你了解封装组件的一些思考

发布于:2024-05-08 ⋅ 阅读:(24) ⋅ 点赞:(0)

背景

众所周知,组件在项目中不可缺失的一部分,第三方组件库提供的往往不够,公司内部也会封装一些组件,有业务上的,ui上的,总之不满足条件就要自己做,封装组件往往是最考验开发能力的,

在这个过程中,你可以很好的锻炼到设计和开发能力,发现问题、分析问题到解决问题的能力,还有最后输出工具的能力。这难道不比乏味全是英文字母的学习有趣多了吗?

以下就是通过一个示例来描述整个过程,并分析一下我的思路

现实问题

比如这种常见的卡片,在首页和详情页或者其他页面都需要展示,这个时候就要考虑封装成一个通用的组件,来提高代码的复用性和可维护性。

先理一下这个结构,首先内容区相同,不同的是底部的按钮,在不同的页面显示不同的按钮,那么用什么方式去实现底部的功能呢?这个正是我们今天讨论的话题

image.png

解决方案

方案一

这种方案是我首先会想到的一个方法,也是最简单粗暴的,通过一个变量区分是首页还是详情页

// app.vue
<template>
  <div class="list-card">
    <Card type="index" />
    <Card type="details" />
  </div>
</template>
<script setup>
import Card from './components/Card.vue'
import { ref } from 'vue'

</script>

<style scoped>
.list-card {
  display: flex;
}
</style>

// card.vue
<template>
  <div class="card-box">
    <div class="card-content"></div>
    <div class="card-footer">
      <el-button @click="handleExamine">查看</el-button>
      <el-button v-if="type === 'index'" type="danger" @click="handleDelete">删除</el-button>
      <el-button v-if="type === 'index'" type="primary" @click="handleDownload">下载</el-button>
      <el-button v-if="type === 'details'" type="primary" @click="handleAdd">添加</el-button>
    </div>
  </div>
</template>
<script setup>
import { ref} from 'vue'
const props = defineProps(['type'])
const { type } = props
</script>
<style scoped>
.card-box {
  height: 250px;
  width: 350px;
  border: 1px solid #999;
  margin: 0 10px;
}
.card-content {
  height: 200px;
  border-bottom: 1px solid #999;
}
.card-footer {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: right;
  padding: 0 20px;
}
</style>

这种方式并不推荐使用,虽然可以实现功能,但是一旦有页面的按钮发生改变就要修改组件,可能还要连带着其他页面一起修改,扩展性极差,最重要的是组件中融入了业务场景

方案二

方案一中组件的扩展性不太友好,针对这一问题做了一些优化,干脆底部功能区就用插槽实现

// app.vue
<template>
  <div class="list-card">
    <Card >
      <el-button @click="handleExamine">查看</el-button>
      <el-button type="danger" @click="handleDelete">删除</el-button>
      <el-button type="primary" @click="handleDownload">下载</el-button>
    </Card>
    <Card >
      <el-button @click="handleExamine">查看</el-button>
      <el-button type="primary" @click="handleAdd">添加</el-button>
    </Card>
  </div>
</template>
<script setup>
import Card from './components/Card.vue'
import { ref } from 'vue'

</script>

<style scoped>
.list-card {
  display: flex;
}
</style>

// card.vue
<template>
  <div class="card-box">
    <div class="card-content"></div>
    <div class="card-footer">
      <slot></slot>
    </div>
  </div>
</template>
<script setup>
import { ref} from 'vue'
</script>
<style scoped>
.card-box {
  height: 250px;
  width: 350px;
  border: 1px solid #999;
  margin: 0 10px;
}
.card-content {
  height: 200px;
  border-bottom: 1px solid #999;
}
.card-footer {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: right;
  padding: 0 20px;
}
</style>

通过插槽的方式间接的放在了父组件的身上,这种方式虽然组件的扩展性得到了解决,但是总感觉按钮也属于组件的一部分,应该放在组件上,这里还可以优化

方案三

通过设置配置项,把按钮放在子组件(卡片组件)这样感觉整个组件的功能才完整

// app.vue
<template>
  <div class="list-card">
    <Card :btnList="indexBtn"/>
    <Card :btnList="detailsBtn"/>
  </div>
</template>
<script setup>
import Card from './components/Card.vue'
import { ref } from 'vue'
const indexBtn = ref([{
  type: '',
  label: '查看',
  event: 'handleExamine'
},{
  type: 'danger',
  label: '删除',
  event: 'handleDelete'
},{
  type: 'primary',
  label: '下载',
  event: 'handleDownload'
}])
const detailsBtn = ref([{
  type: '',
  label: '查看',
  event: 'handleExamine'
},{
  type: 'primary',
  label: '下载',
  event: 'handleAdd'
}])
</script>

<style scoped>
.list-card {
  display: flex;
}
</style>

// card.vue
<template>
  <div class="card-box">
    <div class="card-content"></div>
    <div class="card-footer">
      <el-button v-for="(item, index) in btnList" :key="index" :type="item.type" @click="callback(item.event)">{{ item.label }}</el-button>
    </div>
  </div>
</template>
<script setup>
import { ref} from 'vue'
const props = defineProps(['btnList'])
const { btnList } = props
const $emit = defineEmits(['handleExamine', 'handleDelete', 'handleDownload','handleAdd'])
const callback = (event) => {
  $emit(`${event}`)
}
</script>
<style scoped>
.card-box {
  height: 250px;
  width: 350px;
  border: 1px solid #999;
  margin: 0 10px;
}
.card-content {
  height: 200px;
  border-bottom: 1px solid #999;
}
.card-footer {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: right;
  padding: 0 20px;
}
</style>

这三种方案都是实现同一个效果 image.png

总结

我举了一个简单的例子来阐述自己的思考过程,从方案一到方案三,针对暴露出来的问题一次次的改进,我这个人比较完美主义,发现问题就想着更新优化,如果大家有其他想法欢迎互相交流学习