vue3组件继承
Vue3 "继承" 第三方组件
需求场景: element-plus 的 el-link 组件的字号尺寸只有 14px,且没有响应配置去更改,未来可能会有更复杂的要求,那么该如何满足这个需求呢?
解决方法优缺点
解决方法有很多,这里只列出几个
-
额外设置类名书写额外 css
优点:简单、高效
缺点:只能操作 css ,代码逻辑不能修改
-
封装一个组件,在新组件内部设置 prop 控制类名
优点:覆盖了第一点的缺点
缺点:只能设置声明的 prop 值,其余的属性、事件、方法都难以修改
-
修改 element 源码
优点:无懈可击
缺点:修改难度大,源码看不懂
-
声明一个新组件 “ 继承 ” 原有组件,在此基础上修改组件
优点:能 “ 继承 ” 所有功能,只需关注需要修改的部分
缺点:仍然存在局限性,且新组件会失去 typescript 代码提示
前两点比较简单,相信大家都能实现,权衡3、4之下还是选择4
前置知识
开始 “ 继承 ”
首先贴上 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>
总结
至此,基本的 “ 继承 ” 就已经完成了,笔者水平有限,如果有遗漏或者有更好的写法,欢迎大家友好讨论
更多推荐
所有评论(0)