在 setup 中结合 TypeScript 编写强类型组件,核心是利用类型声明与响应式 API 泛型能力,实现 props、ref、reactive、emit、defineExpose 等全面类型约束,确保输入输出精准可控。
在 setup 中结合 TypeScript 编写强类型组件,核心是利用类型声明 + 响应式 API 的泛型能力,让变量、函数、props、事件等全部具备可推导、可约束、可检查的类型。关键不在“能不能写”,而在于“怎么写才真正发挥类型安全的价值”。
ref 和 reactive 是最常用的响应式 API,它们都支持泛型,这是实现强类型的第一步。
ref('hello') 或 ref([1, 2, 3])。即使不写泛型,TypeScript 也能自动推导(如 ref(42) → Ref),但显式声明更清晰、更防误改。interface User { id: number; name: string; email?: string }
const user = reactive<User>({ id: 1, name: 'Alice' })
</script>
不推荐直接 reactive({}) 后再赋值,那样会导致类型宽松甚至 any 化。
Vue 3 推荐用泛型方式定义 props,比运行时声明更简洁、更类型安全。
defineProps<{ title: string; count?: number }>()
interface Props {
title: string
disabled?: boolean
onConfirm: (id: string) => void
}
const props = defineProps<Props>()
这样不仅 props 有完整类型,连事件回调参数也受约束。
{ type: String })和泛型,除非要兼容某些动态校验逻辑;若必须用 PropType,记得导入:import type { PropType } from 'vue',然后写 book: Object as PropType<Book>。emit 不再是随意字符串,而是可被 IDE 提示、编译检查的函数调用。
const emit = defineEmits<{ (e: 'change', value: string): void; (e: 'submit'): void }>()
type Emits = {
'update:modelValue': [value: string],
'error': [msg: string, code: number]
}
const emit = defineEmits<Emits>()
模板中触发 @click="emit('update:modelValue', 'new')",传参错误会在开发阶段报错。
当父组件需调用子组件方法或读取内部状态时,defineExpose 是唯一安全出口。
const inputValue = ref('')
const validate = () => !!inputValue.value
defineExpose({ inputValue, validate })
ref 获取实例后,能获得完整类型提示,比如 childRef.value.validate() 返回 boolean,且参数不可乱传。不复杂但容易忽略:类型不是越多越好,而是要在关键输入输出点(props / emit / expose / API 返回值)做精准约束。一次写对,后续所有调用都自动受益。