需求场景: element-plus 的 el-link 组件的字号尺寸只有 14px,且没有响应配置去更改,未来可能会有更复杂的要求,那么该如何满足这个需求呢?

解决方法优缺点

解决方法有很多,这里只列出几个

  1. 额外设置类名书写额外 css

    优点:简单、高效

    缺点:只能操作 css ,代码逻辑不能修改

  2. 封装一个组件,在新组件内部设置 prop 控制类名

    优点:覆盖了第一点的缺点

    缺点:只能设置声明的 prop 值,其余的属性、事件、方法都难以修改

  3. 修改 element 源码

    优点:无懈可击

    缺点:修改难度大,源码看不懂

  4. 声明一个新组件 “ 继承 ” 原有组件,在此基础上修改组件

    优点:能 “ 继承 ” 所有功能,只需关注需要修改的部分

    缺点:仍然存在局限性,且新组件会失去 typescript 代码提示

前两点比较简单,相信大家都能实现,权衡3、4之下还是选择4

前置知识

Props

透传 attribute

插槽 slot

开始 “ 继承 ”

首先贴上 el-link 组件的 api 截图

在这里插入图片描述

“ 继承 ” 属性

尽管 link 组件的属性不多,但是要一个一个在 defineProps 里面声明然后再一个一个通过 v-bind 绑定上去就太麻烦了,即:太不优雅

这里我们可以使用 $attrs/useAttrs 来简化操作

下段代码比较简单,做了两件事,一是新加了一个 size 属性,用于控制类名,二是修改了 link 组件 underline 的默认值

<template>
  <ElLink v-bind="{ ...props, ...$attrs }" :class="[size + '-size']">
    <slot></slot>
  </ElLink>
</template>
<script lang="ts" setup>
const props = defineProps({
  // 新的属性
  size: {
    validator(value: string) {
      return ["small", "medium", "large"].includes(value);
    },
    default: "small",
  },
  // 覆写原有默认值
  underline: {
    type: Boolean,
    default: false,
  },
});
</script>

<style scoped lang="scss">
.medium-size {
  font-size: 14px;
}
.small-size {
  font-size: 12px;
}
.large-size {
  font-size: 16px;
}
</style>

父组件调用

<template>
  <el-table size="large" :data="tableData">
    <el-table-column prop="username" label="用户名" />
    <el-table-column label="操作" width="220">
      <template #default="{ row }">
        <Link type="primary">授权</Link>
        <Link type="primary">重置密码</Link>
        <Link type="warning">编辑</Link>
        <Link type="danger">删除</Link>
      </template>
    </el-table-column>
  </el-table>
</template>
<script lang="ts" setup>
import Link from "@/components/Link.vue";
</script>

非常完美,type 属性就算没有在组件中声明也正确渲染了

“ 继承 ” 插槽

复杂的做法是将插槽一个个声明,如果是作用域插槽一并把数据传递过去

这里以 el-table-column为例,这个组件有两个插槽,default和header,都是作用域插槽

// 如果我们本身就需要操作插槽,那么分开写是必然,但如果我们并不需要更改,但是组件插槽又比较多就得一个一个补上
// v-bind="scope"相当于不动原有的数据结构,因为不清楚组件需要的属性数量和名称
<template>
  <el-table-column v-bind="$attrs">
    <template #default="scope">
      <slot v-bind="scope"></slot>
    </template>
    <template #header="scope">
      <slot name="header" v-bind="scope"></slot>
    </template>
  </el-table-column>
</template>

简单的做法就需要了解 $slots/useSlots

// $slots包含了所有父组件使用到的插槽
// #[key]等同于v-bind="[key]"
<template>
  <el-table-column v-bind="$attrs">
    <template v-for="(item, key) in $slots" :key="key" #[key]="scoped">
      <slot :name="key" v-bind="scoped"></slot>
    </template>
  </el-table-column>
</template>
// 如果需要对某个插槽进行处理,通过v-if筛选
<template>
  <el-table-column v-bind="$attrs">
    <template v-for="(item, key) in $slots" :key="key" #[key]="scoped">
      <template v-if="key === 'default'">
        <div>before</div>
        <slot v-bind="scoped"></slot>
      </template>
      <slot :name="key" v-bind="scoped" v-else></slot>
    </template>
  </el-table-column>
</template>

父组件调用

// 和 el-table-column 一样
<y-table-column prop="status" label="状态" width="100">
  <template #default="{ row }">
    <el-switch
      v-model="row.status"
      @change="switchUserStatus(row, $event as boolean)"
    ></el-switch>
  </template>
</y-table-column>
“ 继承 ” 事件

事件的 “ 继承 ” 其实在 “ 继承 ” 属性中已经完成了。

当然,如果你对属性进行了操作,还需要注意修改属性的命名

“ 继承 ” 方法

先回顾一下一般调用组件方法的步骤,这里以 el-table 为例

通过给组件设置 ref 属性,声明同名的 ref 值为 null 的属性

onMounted 钩子之后就可以拿到组件实例

<template>
  <el-table size="large" :data="tableData" ref="dataTable">
    <el-table-column prop="username" label="用户名" />
    <el-table-column prop="roleName" label="角色" />
    <el-table-column prop="status" label="状态">
    </el-table-column>
  </el-table>
</template>
<script setup lang="ts">
import { ref } from "vue";
const dataTable = ref(null);
表格数据
const tableData = ref([])
// 表格选中第一行
onMounted(()=>{
    const first = dataTable.value[0]
    dataTable.value.setCurrentRow(first)
})
</script>

vue3.2 中组件需要通过 defineExport 选择性暴露 <script setup> 中要暴露出去的属性

" 继承 " 的子组件

<template>
  <el-table ref="table" v-bind="$attrs">
    <template v-for="(item, key) in $slots" :key="key" #[key]="scoped">
      <slot :name="key" v-bind="scoped"></slot>
    </template>
  </el-table>
  <el-table> </el-table>
</template>

<script setup lang="ts">
import { ref } from "vue";
const table = ref(null);
defineExpose({
  table: table,
});
</script>

父组件获取

<template>
  <el-table size="large" :data="tableData" ref="dataTable">
    <el-table-column prop="username" label="用户名" />
    <el-table-column prop="roleName" label="角色" />
    <el-table-column prop="status" label="状态">
    </el-table-column>
  </el-table>
</template>
<script setup lang="ts">
import { ref } from "vue";
const dataTable = ref(null);
表格数据
const tableData = ref([])
// 表格选中第一行
// 多一层 table
onMounted(()=>{
    const first = dataTable.value[0]
    dataTable.value.table.setCurrentRow(first)
})
</script>

总结

至此,基本的 “ 继承 ” 就已经完成了,笔者水平有限,如果有遗漏或者有更好的写法,欢迎大家友好讨论

Logo

前往低代码交流专区

更多推荐