Vue 3的UI组件库二次封装技巧
背景
在Vue
开发过程中,对UI组件库进行二次封装可以极大地提高代码复用性,从而更好地满足项目需求。在组件库的封装过程中,我们通常需要关注四个方面:属性、事件、插槽和方法。下面我们以对ElementPlus
的Input
组件进行二次封装为例进行说明。
属性、事件
封装MyInput
组件,添加一个名为box-shadow
的属性,该属性可以用于设置组件的阴影效果。
<!-- MyInput.vue -->
<template>
<el-input :class="{box_shadow: boxShadow}"> </el-input>
</template>
<script setup>
defineProps({
boxShadow: {
type: Boolean,
default: false,
},
});
</script>
<style scoped>
.box_shadow {
box-shadow: 4px 4px 10px #40a0ff7b;
}
</style>
使用v-bind="$attrs"
在 el-input
组件中,存在许多可选的属性和事件。在封装组件时,逐个定义这些属性和事件是不现实的。为了更便捷地处理这种情况,我们可以使用 v-bind="$attrs"
。该指令允许将父组件传递给子组件的非props属性绑定到子组件内部的元素上,从而实现更灵活的数据传递和绑定。
例如:
<template>
<MyInput
placeholder="请输入关键内容"
v-model="value"
@input="inputValue"
>
</MyInput>
</template>
<script setup>
import { ref } from "vue";
import MyInput from "./components/MyInput.vue";
const value = ref("123");
const inputValue = (v) => {
console.log(v);
};
</script>
<!-- MyInput.vue -->
<template>
<el-input v-bind="$attrs" :class="{box_shadow: boxShadow}"> </el-input>
</template>
<script setup>
defineProps({
boxShadow: {
type: Boolean,
default: false,
},
});
</script>
<style scoped>
.box_shadow {
box-shadow: 4px 4px 10px #40a0ff7b;
}
</style>
在这个例子中,父组件中的 placeholder
、v-model
属性和 input
事件并没有在 MyInput
的props中进行显式声明。然而,通过使用 v-bind="$attrs"
,我们能够将这些属性和事件传递给 el-input
元素。
上面代码等同与:
<!-- MyInput.vue -->
<template>
<el-input
v-model="value"
:placehoder="placehoder"
:class="{box_shadow: boxShadow}"
@input="$emit('input', $event)"
>
</el-input>
</template>
<script setup>
defineProps({
boxShadow: {
type: Boolean,
default: false,
},
});
</script>
<style scoped>
.box_shadow {
box-shadow: 4px 4px 10px #40a0ff7b;
}
</style>
插槽
在el-input
组件中,同样存在许多可选的插槽。逐个去定义这些插槽是不现实的,因此我们可以使用useSlots
方法来获取父组件传入的插槽内容,并通过遍历这些插槽来支持它们的使用。
<template>
<!-- MyInput.vue -->
<el-input v-bind="$attrs" :class="{ box_shadow: boxShadow }">
<template v-for="(value, name) in slots" #[name]="scope">
<slot :name="name" v-bind="scope || {}"></slot>
</template>
</el-input>
</template>
<script setup>
import { onMounted, ref, useSlots, defineExpose } from "vue";
defineProps({
boxShadow: {
type: Boolean,
default: false,
},
});
const slots = useSlots();
</script>
<style scoped>
.box_shadow {
box-shadow: 4px 4px 10px #40a0ff7b;
}
</style>
使用
<template>
<MyInput
placeholder="请输入关键内容"
v-model="value"
@input="inputValue"
>
<template #prepend>Http://</template>
</MyInput>
</template>
<script setup>
import { ref } from "vue";
import MyInput from "./components/MyInput.vue";
const value = ref("123");
const inputValue = (v) => {
console.log(v);
};
</script>
方法
在el-input
组件中,暴露了许多方法。逐个去定义这些方法是不现实的。因此,我们可以通过 ref
来获取el-input
组件的实例,然后定义一个expose
变量,通过遍历ref
获取的方法,将它们存放到expose
变量中。接着,我们可以使用defineExpose
将expose
暴露出来,这样父组件就能通过ref
使用这些方法了。
<template>
<!-- MyInput.vue -->
<el-input v-bind="$attrs" ref="elInputRef" :class="{ box_shadow: boxShadow }">
<!-- // -->
</el-input>
</template>
<script setup>
import { onMounted, ref, useSlots, defineExpose } from "vue";
// ...
const expose = {};
onMounted(() => {
const entries = Object.entries(elInputRef.value);
for (const [method, fn] of entries) {
expose[method] = fn;
}
});
defineExpose(expose);
</script>
<style scoped>
/* ... */
</style>
使用
<template>
<MyInput
placeholder="请输入关键内容"
v-model="value"
@input="inputValue"
ref="myInputRef"
>
<template #prepend>Http://</template>
</MyInput>
</template>
<script setup>
import { onMounted, ref } from "vue";
import MyInput from "./components/MyInput.vue";
const value = ref("123");
const myInputRef = ref();
const inputValue = (v) => {
console.log(v);
};
onMounted(() => {
myInputRef.value.focus();
});
</script>
<style>
/* ... */
</style>
转载自:https://juejin.cn/post/7262343902889197629