vue 3 的 泛型组件需通过 generic="t" 声明并配合 defineprops 和 defineemits 实现类型安全复用,父组件调用时须显式指定具体类型(如 user),才能触发完整类型推导与校验。

Vue 3 的 <script setup></script> 语法糖配合 TypeScript 泛型,是编写高复用通用子组件的核心能力。它让组件既能保持类型安全,又能灵活适配不同数据结构(如 User、Product、Order),而无需为每种类型重复写逻辑。
泛型组件必须用 defineProps + defineEmits 显式声明
在 <script setup></script> 中,泛型不能直接作用于整个组件,而是通过 defineProps 和 defineEmits 的泛型参数实现。这是 Vue 官方支持的、类型推导最准确的方式。
<script setup lang="ts" generic="T">
import { ref, computed } from 'vue'
// ✅ 正确:用泛型 T 约束 props 类型
const props = defineProps<{
list: T[]
keyField?: keyof T
renderItem?: (item: T) => string
}>()
// ✅ 正确:泛型也用于 emits 的 payload 类型
const emit = defineEmits<{
(e: 'select', item: T): void
(e: 'delete', id: T[keyof T & string]): void
}>()
const selected = ref<T | null>(null)
const defaultKey = props.keyField ?? 'id' as keyof T
const displayedItems = computed(() =>
props.list.map(item =>
props.renderItem ? props.renderItem(item) : String(item[defaultKey])
)
)
</script>
⚠️ 注意:
generic="T"是编译器宏,不是 TypeScript 语法;它只用于告诉 Vue 编译器该组件接受泛型参数,必须配合 defineProps 的泛型签名才能生效。
模板中使用泛型类型要靠 $props / $emit 推导(或 useSlots/useAttrs)
泛型类型不会自动“穿透”到模板,但 Vue 会基于 defineProps 的类型自动推导 $props,因此你在 <template></template> 中访问 props.list[0] 时,IDE 和 TS 编译器能正确识别其为 T 类型:
<template><div class="list">
<div v-for="(item, i) in props.list" :key="i" item class="item">
{{ props.renderItem ? props.renderItem(item) : item[props.keyField || 'id'] }}
</div>
</div>
</template>
只要 props 类型定义完整,模板里对 item 的属性访问(如 item.name)就有完整类型提示和校验。
父组件调用时传入具体类型(关键!)
泛型组件只有被调用时指定具体类型,才真正“实例化”。方式有两种:
✅ 方式一:用 as const + defineProps 显式标注(推荐,类型最稳)
<!-- UserList.vue -->
<script setup lang="ts">
import GenericList from './GenericList.vue'
// ? 显式告知 GenericList<T> 中的 T 是 User 类型
interface User {
id: number
name: string
email: string
}
const users = ref<User[]>([
{ id: 1, name: 'Alice', email: 'a@b.c' },
{ id: 2, name: 'Bob', email: 'b@c.d' }
])
const handleSelect = (user: User) => {
console.log('selected:', user.name)
}
</script><template><genericlist :list="users" key-field="name"></genericlist></template>
✅ 方式二:用 defineComponent 包裹(适合复杂场景,如需 name 或 render)
// 如果你需要命名组件或使用 render 函数,泛型需这样写:
import { defineComponent } from 'vue'
export default defineComponent({
name: 'GenericTable',
props: {
data: { type: Array as PropType }
}
} as const)
但 <script setup></script> 中优先用第一种——简洁且类型收敛好。
进阶:泛型 + 默认值 + 可选 props 组合技巧
泛型组件常需兜底逻辑。比如 keyField 不传时默认用 'id',但 'id' 是否一定存在于 T?可以用 keyof T + 条件类型保障:
const props = defineProps string
}>()
// ✅ 安全取 key:如果 keyField 没传,就尝试用 'id';否则 fallback 到第一个可枚举 key
const getKey = (item: T): string => {
const key = props.keyField || ('id' in item ? 'id' : Object.keys(item)[0] as keyof T)
return String(item[key])
}
这样既保持泛型灵活性,又避免运行时报错。
不复杂但容易忽略:泛型组件的复用性不在“写得多”,而在“约束得准”——把 T 的边界(如 T extends { id: string })提前想清,比后期加类型断言更可靠。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










