最近在封裝一個組件,使用的時候希望父組件能給子組件傳遞一個泛型,在網上搜了半天,答案都是説要用jsx才能實現。具體寫法如下:
使用JSX
這段代碼來自Eluxjs的示例項目elux-vue-antd-admin,感興趣的可以看下。
父組件: (為減少篇幅,代碼刪了很多,當偽代碼看吧)
const Component = defineComponent<Props>({
props: ['listPathname', 'mergeColumns'] as any,
setup(props) {
return () => {
return (
<MTable<ListItem> // 這裏使用子組件MTable,傳遞泛型ListItem
size={tableSize}
commonActions={commonActions}
selection={selection}
loading={listLoading === 'Start' || listLoading === 'Depth'}
/>
);
};
},
});
export default Component;
子組件:(子組件比較麻煩)
interface Props<T = Record<string, any>> extends TableProps<T> {
class?: string;
columns: MColumns<T>[];
selectedRows?: Partial<T>[];
selection?: MSelection<T>;
}
const Component = defineComponent<Props>({
name: styles.root,
props: [
'class',
'columns'
] as any,
setup(props: Props) {
return () => {
const {class: className = '', dataSource, onChange, rowKey = 'id', loading, size, bordered, locale, scroll} = props;
return (
<div class={styles.root + ' ' + className}>
{headArea.value}
<Table
columns={columnList.value}
rowSelection={rowSelection.value}
onChange={onChange}
size={size}
/>
</div>
);
};
},
}) as any;
export default Component as <T>(props: Props<T>) => JSX.Element;
這裏使用 Component as <T>(props: Props<T>) => JSX.Element; 來做到接收泛型,但消費它的父組件必須用 jsx。
注:Vue3.3版本給defineComponent增加了泛型函數的支持,不用這樣寫了。
因為項目不方便用jsx,我找了很久如何用template來傳遞泛型,終於找到了!
使用Template
先看子組件:
<script
lang="ts"
setup
// 關鍵:setup script有個generic屬性,可以聲明泛型,供組件使用
generic="T extends { name: string; age: number; }, Q extends { title:string }"
>
// 在props中使用泛型,rowKey使用keyof T,plainObj使用第二個泛型Q
defineProps<{
personList: T[];
rowKey: keyof T;
plainObj: Q;
}>();
</script>
<template>
<div>
// 模板中正常使用props
<h3 v-for="item in personList" :key="item.name">
{{ item.age }}
</h3>
// 用插槽向父組件暴露數據,plainObj的類型是Q
<slot :obj="plainObj" />
</div>
</template>
generic屬性的作用就是給組件中使用的泛型提供一個“來源”,這樣父組件消費子組件的時候,只要給personList傳遞一個數據,泛型T就會被自動推斷出來。
generic屬性的泛型可以選擇extends一些已知的屬性,因為子組件自身使用到props的哪些屬性,和消費該組件的地方是無關的。
父組件:
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
// 給list的item聲明類型,該類型可以是子組件T的超集
interface DataType {
name: string;
age: number;
gender: 'male' | 'female' | 'wallmart bag';
hobby?: 'jk' | 'dk' | '敵法師';
}
const list: DataType[] = [
{
name: 'ming',
age: 20,
gender: 'male',
hobby: '敵法師'
},
{
name: 'hong',
age: 20,
gender: 'female'
},
{
name: 'hua',
age: 20,
gender: 'wallmart bag'
}
];
</script>
<template>
<div>
<HelloWorld
:person-list="list"
row-key="genderr"
:plainObj="{ happy: true, title: '抬頭' }"
v-slot="{ obj }"
>
<!-- 👆 不能將類型“"agg"”分配給類型“keyof T”。 -->
{{ obj.happ }}
<!-- 👆 屬性“happ”在類型“{ happy: boolean; title: string; }”上不存在。你是否指的是“happy”? -->
</HelloWorld>
</div>
</template>
<style></style>
可以看到,子組件推斷出了T的類型是DataType,報錯 類型"genderr"不可分配給類型“keyof T”。你的意思是"gender"? 。
Q也成功的推斷了出來,報錯 屬性“happ”在類型“{ happy: boolean; title: string; }”上不存在,這裏的 { happy: boolean; title: string; } 即是我們傳給 plainObj 屬性的泛型Q
這樣就實現了父組件通過props,給子組件傳遞所需的泛型。