在日常开发的过程中,使用Vue
的组件进行业务拆分,代码解耦是一个很好的选择;
今天就来分享一下我在使用Vue3
进行组件封装的一些技巧和心得,希望能够帮助到大家;
1. 组件特性
在Vue
中组件是一个独立的实例,每个组件都有共通点,就是:属性
、插槽
、事件
、方法
;
在日常我们使用第三方组件库的时候,组件库的文档都会说明上面四个特性,而组件封装就是围绕这四个特性进行的;
2. 组件封装
2.1 组件继承
很多情况下,我们会在一个组件的基础上进行扩展,这个时候就需要用到组件继承;
在Vue2
的时候,我们可以使用extends
关键字进行组件继承,但是在Vue3
中,extends
关键字已经被废弃了;
在Vue3
中,如果想要实现组件继承其实很简单,要明白一个组件其实就是一个js
对象,我们可以直接将一个组件对象合并,然后注册成一个新的组件;
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus, { ElInput } from "element-plus";
import "element-plus/dist/index.css";
import { merge } from "lodash";
const app = createApp(App);
app.use(ElementPlus);
// 组件继承,将ElInput组件的placeholder属性默认值改为"请输入"
app.component(
"ElInput",
merge(ElInput, {
props: {
placeholder: {
default: "请输入"
}
}
})
);
app.mount("#app");
这里直接使用了lodash
的merge
方法,将ElInput
组件的props
属性进行了合并,然后覆盖注册成了一个新的组件;
因为有很多小伙伴遇到一个问题就是需要固定ElTable
组件的一些属性,比如border
、stripe
、size
等,这个时候用这种方法就非常方便;
2.2 组件插槽
上面的组件继承只是简单的改变了组件的默认属性,但是如果我们想要改变组件的结构,就需要用到组件插槽;
通常情况下我们要拆分组件的业务,然后封装成业务组件,这个时候可能会使用到多个组件;
这个时候组件里面有很多组件,需要替换组件里面的组件里面的插槽,这个时候就需要透传插槽;
<!-- 透传插槽 -->
<template>
<div>
区域A这里有一个组件,这个组件需要替换插槽
<el-tree :data="treeData">
<template v-if="$slots.tree" #default="{ node, data }">
<slot name="tree" :node="node" :data="data" />
</template>
</el-tree>
</div>
<div>
区域B这里有一个组件,这个组件需要替换插槽
<el-table :data="tableData">
<template v-if="$slots.default">
<slot />
</template>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
treeData: new Array(10)
.fill(0)
.map((_, index) => ({ label: "label" + index })),
tableData: [],
};
},
};
</script>
通过使用$slots
可以获取到组件的插槽,然后通过v-if
判断是否有插槽,如果有插槽就进行透传;
除了这种方式之外,还可以使用jsx
语法,这种方式更加灵活;
<script lang="jsx">
export default {
render() {
const areaA = (
<div>
区域A这里有一个组件,这个组件需要替换插槽
<el-tree data={treeData}>
{{
default: this.$slots.tree
}}
</el-tree>
</div>
);
const areaB = (
<div>
区域B这里有一个组件,这个组件需要替换插槽
<el-table data={tableData}>
{{
default: this.$slots.default
}}
</el-table>
</div>
);
return (
<div>
{areaA}
{areaB}
</div>
);
}
}
</script>
在
setup
语法中是没有this
的,这个使用需要获取$slots
的时候需要使用useSlots
方法;
2.3 组件事件和透传 attrs
在Vue2
中,我们可以使用$listeners
来获取组件的事件,然后进行透传;
而在Vue3
中,$listeners
已经被废弃了,$listeners
和$attrs
都被合并到了$attrs
中;
<!-- 组件 -->
<template>
<div v-bind="$attrs"></div>
</template>
<!-- 父组件 -->
<template>
<div>
<MyComponent
class="my-class"
@click="handleClick"
/>
</div>
</template>
在Vue3
中,我们可以直接使用$attrs
来获取组件的事件,然后进行透传;
例如上面的例子,我们可以直接在组件中使用$attrs
来获取到class
和@click
事件,等同于下面的写法;
<!-- 组件 -->
<template>
<div class="my-class" @click="handleClick"></div>
</template>
但是这里其实有一个小技巧,就是Vue3
默认属性是可以透传的,例如上面的例子其实可以简化成下面的写法;
<!-- 组件 -->
<template>
<div></div>
</template>
<!-- 父组件 -->
<template>
<div>
<MyComponent
class="my-class"
@click="handleClick"
/>
</div>
</template>
就是组件里面什么都不写,最后在父组件中使用这个组件的时候,属性会透传到组件中的根元素上;
参考:透传 Attributes[1]
了解这个特性就可以这样封装组件:
<!-- 组件 -->
<template>
<el-dialog>
</el-dialog>
</template>
<!-- 父组件 -->
<template>
<div>
<MyComponent
v-model="visible"
width="500px"
/>
</div>
</template>
通常我们会封装一个Dialog
组件来解耦业务,这个时候直接将Dialog
作为根元素,然后可以将v-model
和width
属性透传到Dialog
组件上;
这样不需要写Dialog
组件开启关闭的双向绑定的代码,前提是不需要在组件内部操作Dialog
的开启关闭;
2.4 组件方法
在Vue2
中,我们可以通过this.$refs.xxx
来获取到组件的实例,然后调用组件的方法;
在Vue3
中,我们可以通过ref
来获取到组件的实例,然后调用组件的方法;
但是不管是Vue2
还是Vue3
,在组件内部想要使用组件的子组件的方法都不是一件容易的事情;
通常都是手动将组件的实例获取到,然后再重新定义在组件的methods
中;
<!-- 组件 -->
<template>
<div>
<el-input ref="input" />
</div>
</template>
<script>
export default {
methods: {
focus() {
this.$refs.input.focus();
},
},
};
</script>
组件的方法通常没有啥特别好的方式,除了我上面的这种方式之外,还有小伙伴是直接将ref
返回出去:
<template>
<div>
<el-input ref="input" />
</div>
</template>
<script>
export default {
methods: {
inputRef() {
return this.$refs.input
},
},
};
</script>
当然还有一种偷懒的方式:
<template>
<div>
<el-input ref="input" />
</div>
</template>
<script>
export default {
mounted() {
Object.values(this.$refs.input).forEach((value) => {
if (typeof value === 'function') {
this[value.name] = (...args) => value(...args);
}
});
},
methods: {
inputRef() {
return this.$refs.input
},
},
};
</script>
不过这种偷懒的方式只能在options api
中使用,因为在composition api
中是没有this
的;
对于setup
语法,如果需要使用组件的方法,可以使用getCurrentInstance
来获取到组件的实例,然后将方法挂载到exposed
上;
<template>
<div>
<el-input ref="input" />
</div>
</template>
<script setup>
import { getCurrentInstance, onMounted, ref } from "vue";
const instance = getCurrentInstance();
const input = ref(null);
onMounted(() => {
Object.values(input.value).forEach((value) => {
if (typeof value === "function") {
instance.exposed[value.name] = (...args) => value(...args);
}
});
});
</script>
这种方式不太稳定,因为
exposed
是Vue3
的一个私有属性,不建议使用;
在
setup
语法中如果需要暴露组件的内部方法,需要使用defineExpose
来暴露;
<script setup>
// ... 省略其他代码
defineExpose({
focus: () => {
input.value.focus();
},
});
</script>
总结
这次带来的是Vue3
的组件封装的一些技巧,主要是setup
语法的一些特性,以及Vue3
中的一些奇淫技巧;
Vue3
的组件封装相比Vue2
来说更加的灵活,但是也更加的复杂,需要我们在使用的时候多加注意;
这次分享的只是一些技术上的点,组件封装是一门非常大的学问,需要我们在实际的项目中多加实践;
作者:田八
链接:https://juejin.cn/post/7247050634191454266