blog
blog copied to clipboard
vue3 组件 props 透传
前言
有这样一个场景,页面渲染的信息是配置出来的,也就是我们渲染的内容是动态的。这种场景我们往往需要开发动态组件去支持。而动态组件可能会使用多种组件去实现。下面将介绍在 vue3 中,如何进行组件props 透传。
以下结合 element-plus 组件为例。
动态组件
下面是动态组件里包含的几种 element-plus 组件:
- dynamic-comp
- el-input 组件
- el-date-picker 组件
- el-time-picker 组件
- el-select 组件
而每一种 element-plus 组件都可能包含不同的 属性、事件、插槽、实例方法。比如 el-input 组件,如何让动态组件支持 el-input 所有的功能呢。一种方法是将 el-input 所有支持的属性、事件、插槽、实例方法在动态中都去实现一遍。但是如果动态组件也需要支持 el-date-picker 组件、 el-select 组件呢。那么工作量将会翻几番,并且当这几种组件有更新,你的动态组件也要进行调整。
如果要满足上面的所有需求,那么要支持: 1、属性透传:props属性、事件、style样式、class 名称 2、slots 插槽透传 3、v-model 指令的支持 4、实例方法的支持
注意:而为了同时满足上面的需求,目前发现只有使用 jsx
方式才能支持,所以下面的示例也基本是基于 jsx
去实现了。
属性透传
首先定义一个 props 去接收 element-plus 组件的动态属性
props: {
// element-plus props
elCompProps: {
type: Object,
default: null,
},
}
属性绑定。使用对象结构的方式
// DynamicComp
import { ElInput } from "element-plus";
export default defineComponent({
props: {
// element-plus props
elCompProps: {
type: Object,
default: null,
},
},
setup(props, { attrs, slots, emit, expose }) {
return () => (
<div>
<ElInput
/*
支持属性绑定、事件传递
*/
{...props.elCompProps}
>
</ElInput>
</div>
);
},
});
而如果仅仅是支持该功能,也可以通过 template
模板方式,可以通过 v-bind
指令实现。这里不多介绍。
在使用动态组件时,我们就可以轻易的将 props属性、事件、style样式、class 名称 进行透传了:
<script setup lang="tsx">
import DynamicComp from "./dynamic-comp";
const handlerValueChange = (val: string) => {
alert(val);
};
</script>
<template>
<div style="margin-top: 100px">
<DynamicComp
:el-comp-props="{
/*
attrbute props
可以支持 style、class
*/
style: 'width:300px;font-size:20px;',
class: 'custom-class-name',
placeholder: '随便录入',
/*
event props
记得加上前缀 'on'
*/
onChange: handlerValueChange,
}"
>
</DynamicComp>
</div>
</template>
slots 插槽透传
slots 插槽透传,目前只有 jsx
方式可以支持(或许其他方式我不知道),而通过jsx
方式实现起来也非常简单。
// DynamicComp
import { ElInput } from "element-plus";
export default defineComponent({
setup(props, { attrs, slots, emit, expose }) {
return () => (
<div>
<ElInput >
{/* 支持插槽 */}
{slots}
</ElInput>
</div>
);
},
});
使用动态组件的地方就和你直接使用 el-input 方式一样:
<script setup lang="tsx">
import DynamicComp from "./dynamic-comp";
</script>
<template>
<div style="margin-top: 100px">
<DynamicComp>
<!-- el-input 组件支持的2种插槽 -->
<template #suffix><div style="color: red">111</div></template>
<template #prefix>222</template>
</DynamicComp>
</div>
</template>
v-model 指令的支持
对于动态组件,支持v-model 会提高使用组件的友好度。v-mode 在jsx 中的使用,你也参考官方文档
// DynamicComp
import { ElInput } from "element-plus";
export default defineComponent({
props: {
// 可以通过 v-model 使用该组件
modelValue: [String, Number],
},
setup(props, { attrs, slots, emit, expose }) {
return () => (
<div>
<ElInput
/*
支持 v-model
*/
modelValue={props.modelValue}
onUpdate:modelValue={(value) => {
emit("update:modelValue", value);
}}
>
</ElInput>
</div>
);
},
});
使用动态组件
<script setup lang="tsx">
import DynamicComp from "./dynamic-comp";
// v-model 值
const modelValue = ref("1123");
</script>
<template>
<div style="margin-top: 100px">
<DynamicComp v-model="modelValue">
</DynamicComp>
</div>
</template>
实例方法的支持
像 el-input 组件存在一些实例方法或者属性,我们同样可以在jsx中通过expose
暴露出去。
暴露el-input 组件的实例,我是通过定义一个变量去操作的:
// element-plus 组件的ref
const elCompRef = ref<ComponentPublicInstance | null>(null);
动态组件实现:
// DynamicComp
import { ElInput } from "element-plus";
import type { ComponentPublicInstance } from "vue";
export default defineComponent({
setup(props, { attrs, slots, emit, expose }) {
// element-plus 组件的ref
const elCompRef = ref<ComponentPublicInstance | null>(null);
const currentInstanceMethod = () => {
alert(1);
};
expose({
// el-plus 相关组件的实例
elCompRef,
// 动态组件本身提供的实例
test: currentInstanceMethod,
});
return () => (
<div>
<ElInput
ref={elCompRef}
>
</ElInput>
</div>
);
},
});
使用动态组件
<script setup lang="tsx">
import DynamicComp from "./dynamic-comp";
const dynamicCompRef = ref<InstanceType<typeof DynamicComp> | null>(null);
const handlerValueChange = (val: string) => {
alert(modelValue.value);
};
const clearTest = () => {
// @ts-ignore
dynamicCompRef.value?.elCompRef.clear();
};
</script>
<template>
<div style="margin-top: 100px">
<DynamicComp
ref="dynamicCompRef"
>
</DynamicComp>
<button @click="clear">清除</button>
</div>
</template>
总结
动态组件支持了上述的功能,你将大大减少维护成本。
--完--
<template>
<a-table ref="component" v-bind="props">
<template v-for="(slot, k) in $slots" :key="k" v-slot:[k]="slotProps">
<component :is="slot" v-bind="slotProps"></component>
</template>
</a-table>
</template>
<script lang="ts" setup>
import { Table } from 'ant-design-vue' // 引入 antd 组件
import { tableProps } from 'ant-design-vue/es/table/Table' // 引入组件定义的 props
import { onMounted, ref } from 'vue';
const props = defineProps(tableProps()) // 定义 props
const component = ref()
</script>
slots
是可以通过 <component :is="slot">
传递的
@SublimeCT 谢谢你的方案