当前位置: 首页 > news >正文

15TypeScript 与组合式 API

为组件的 props 标注类型

使用 <script setup>

当使用 <script setup> 时,defineProps() 宏函数支持从它的参数中推导类型:

<script setup lang="ts">
const props = defineProps({foo: { type: String, required: true },bar: Number
})props.foo // string
props.bar // number | undefined
</script>

这被称之为“运行时声明”,因为传递给 defineProps() 的参数会作为运行时的 props 选项使用。

然而,通过泛型参数来定义 props 的类型通常更直接:

<script setup lang="ts">
const props = defineProps<{foo: stringbar?: number
}>()
</script>

这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。

基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。

我们也可以将 props 的类型移入一个单独的接口中:

<script setup lang="ts">
interface Props {foo: stringbar?: number
}const props = defineProps<Props>()
</script>

这同样适用于 Props 从另一个源文件中导入的情况。该功能要求 TypeScript 作为 Vue 的一个 peer dependency。

<script setup lang="ts">
import type { Props } from './foo'const props = defineProps<Props>()
</script>

语法限制​

在 3.2 及以下版本中,defineProps() 的泛型类型参数仅限于类型字面量或对本地接口的引用。

这个限制在 3.3 中得到了解决。最新版本的 Vue 支持在类型参数位置引用导入和有限的复杂类型。但是,由于类型到运行时转换仍然基于 AST,一些需要实际类型分析的复杂类型,例如条件类型,还未支持。你可以使用条件类型来指定单个 prop 的类型,但不能用于整个 props 对象的类型。

Props 解构默认值​

当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。可以通过使用响应式 Props 解构解决这个问题。

interface Props {msg?: stringlabels?: string[]
}const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()

复杂的 prop 类型​

通过基于类型的声明,一个 prop 可以像使用其他任何类型一样使用一个复杂类型:

<script setup lang="ts">
interface Book {title: stringauthor: stringyear: number
}const props = defineProps<{book: Book
}>()
</script>

对于运行时声明,我们可以使用 PropType 工具类型:

import type { PropType } from 'vue'const props = defineProps({book: Object as PropType<Book>
})

其工作方式与直接指定 props 选项基本相同:

import { defineComponent } from 'vue'
import type { PropType } from 'vue'export default defineComponent({props: {book: Object as PropType<Book>}
})

为组件的 emits 标注类型​

在 <script setup> 中,emit 函数的类型标注也可以通过运行时声明或是类型声明进行:

<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])// 基于选项
const emit = defineEmits({change: (id: number) => {// 返回 `true` 或 `false`// 表明验证通过或失败
  },update: (value: string) => {// 返回 `true` 或 `false`// 表明验证通过或失败
  }
})// 基于类型
const emit = defineEmits<{(e: 'change', id: number): void(e: 'update', value: string): void
}>()// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{change: [id: number]update: [value: string]
}>()
</script>

为 ref() 标注类型​

ref 会根据初始化时的值推导其类型:

import { ref } from 'vue'// 推导出的类型:Ref<number>
const year = ref(2020)// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'import { ref } from 'vue'
import type { Ref } from 'vue'const year: Ref<string | number> = ref('2020')year.value = 2020 // 成功!// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')year.value = 2020 // 成功!
// 推导得到的类型:Ref<number | undefined> const n = ref<number>()

为 reactive() 标注类型​

reactive() 也会隐式地从它的参数中推导类型:

import { reactive } from 'vue'interface Book {title: stringyear?: number
}const book: Book = reactive({ title: 'Vue 3 指引' })

为 computed() 标注类型​

computed() 会自动从其计算函数的返回值上推导出类型:

import { ref, computed } from 'vue'const count = ref(0)// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')const double = computed<number>(() => {// 若返回值不是 number 类型则会报错
})

为事件处理函数标注类型​

在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。让我们看一下这个例子:

<script setup lang="ts">
function handleChange(event) {// `event` 隐式地标注为 `any` 类型
  console.log(event.target.value)
}
</script><template><input type="text" @change="handleChange" />
</template>
没有类型标注时,这个 event 参数会隐式地标注为 any 类型。这也会在 tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 时报出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你在访问 event 上的属性时可能需要使用类型断言:
function handleChange(event: Event) {console.log((event.target as HTMLInputElement).value)
}

为 provide / inject 标注类型​

provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:

import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'const key = Symbol() as InjectionKey<string>provide(key, 'foo') // 若提供的是非字符串值会导致错误

const foo = inject(key) // foo 的类型:string | undefined

为模板引用标注类型​

在 Vue 3.5 和 @vue/language-tools 2.1 (为 IDE 语言服务和 vue-tsc 提供支持) 中,在单文件组件中由 useTemplateRef() 创建的 ref 类型可以基于匹配的 ref attribute 所在的元素自动推断为静态类型。

在无法自动推断的情况下,仍然可以通过泛型参数将模板 ref 转换为显式类型。

const el = useTemplateRef<HTMLInputElement>('el')

为组件模板引用标注类型​

在 Vue 3.5 和 @vue/language-tools 2.1 (为 IDE 语言服务和 vue-tsc 提供支持) 中,在单文件组件中由 useTemplateRef() 创建的 ref 类型可以基于匹配的 ref attribute 所在的元素自动推断为静态类型。

在无法自动推断的情况下 (如非单文件组件使用或动态组件),仍然可以通过泛型参数将模板 ref 强制转换为显式类型。

为了获取导入组件的实例类型,我们需要先通过 typeof 获取其类型,然后使用 TypeScript 的内置 InstanceType 工具提取其实例类型:

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'type FooType = InstanceType<typeof Foo>
type BarType = InstanceType<typeof Bar>const compRef = useTemplateRef<FooType | BarType>('comp')
</script><template><component :is="Math.random() > 0.5 ? Foo : Bar" ref="comp" />
</template>

如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 ComponentPublicInstance。这只会包含所有组件都共享的属性,比如 $el

import { useTemplateRef } from 'vue'
import type { ComponentPublicInstance } from 'vue'const child = useTemplateRef<ComponentPublicInstance>('child')

如果引用的组件是一个泛型组件,例如 MyGenericModal

<script setup lang="ts" generic="ContentType extends string | number">
import { ref } from 'vue'const content = ref<ContentType | null>(null)const open = (newContent: ContentType) => (content.value = newContent)defineExpose({open
})
</script>

则需要使用 vue-component-type-helpers 库中的 ComponentExposed 来引用组件类型,因为 InstanceType 在这种场景下不起作用。

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MyGenericModal from './MyGenericModal.vue'
import type { ComponentExposed } from 'vue-component-type-helpers'const modal =useTemplateRef<ComponentExposed<typeof MyGenericModal>>('modal')const openModal = () => {modal.value?.open('newValue')
}
</script>

为自定义全局指令添加类型​

可以通过扩展 ComponentCustomProperties 来为使用 app.directive() 声明的全局自定义指令获取类型提示和类型检查

import type { Directive } from 'vue'export type HighlightDirective = Directive<HTMLElement, string>declare module 'vue' {export interface ComponentCustomProperties {// 使用 v 作为前缀 (v-highlight)
    vHighlight: HighlightDirective}
}export default {mounted: (el, binding) => {el.style.backgroundColor = binding.value}
} satisfies HighlightDirective


import highlight from './directives/highlight'
// ...其它代码
const app = createApp(App)
app.directive('highlight', highlight)

 

在组件中使用

<template>
<p v-highlight="'blue'">This sentence is important!</p>
</template>

 

 

http://www.jsqmd.com/news/366539/

相关文章:

  • 【计算机毕业设计案例】基于Java springboot网络书籍阅读写作系统基于springboot的网络阅读与写作(程序+文档+讲解+定制)
  • 2026年GEO服务商综合选择指南:AI时代品牌认知争夺战的技术伙伴 - 品牌策略主理人
  • 手把手教你学Simulink--基于多能互补微电网系统的建模与优化场景实例:光储氢一体化微电网能量管理与调度仿真
  • 2026医院标识/发光字/沙盘模型设计权威推荐 老牌实力企业榜单 - 深度智识库
  • 真的太省时间!千笔·专业论文写作工具,口碑爆棚的AI论文软件
  • 2026年天虹购物卡回收平台实测!闲置卡变现不亏攻略 - 京回收小程序
  • 【计算机毕业设计案例】基于springboot的在线拍卖商品拍卖、订单处理系统的设计与实现(程序+文档+讲解+定制)
  • 电机nvh分析电磁仿真Maxwell电机电磁振动噪声NVH分析 包括Maxwell仿真基础 电...
  • 2026 年 02 月 10 日 AI 前沿、通信和安全行业日报
  • 供应链风险管理方法
  • 【计算机毕业设计案例】基于springboot的中药科普知识平台基于Vue + SpringBoot的中医药文化科普系统设计与实现(程序+文档+讲解+定制)
  • 大模型小白/程序员必看:收藏这份智能体技术地图,轻松入门高效AI开发!
  • 2026年知名的ISO22000食品安全体系认证公司公司综合实力参考 - 品牌鉴赏师
  • 【粉丝福利社】豆包使用秘笈
  • 别再瞎找了!降AI率工具 千笔AI VS Checkjie,本科生专属高效之选
  • 如何判定受益所有人
  • 2026硫酸钡砂硫酸钡板厂家推荐:高纯度防护材料品牌实力测评 - 深度智识库
  • 摆脱论文困扰!全网爆红的AI论文平台 —— 千笔AI
  • 2026年信息与控制系统国际学术会议(ICS2026)2026 International Conference on Information and Control Systems中国
  • chatgpt团队会员一个月免费使用领取方法,有需要的用户速领! - Roxy指纹浏览器
  • 小时候的成长
  • 【Mysql】索引优化实战:从 320ms 到 130ms 的慢 SQL 改造
  • powershell无法运行命令
  • 2026年智能体公司推荐,物流、政府、能源、文娱、科技互联网五大赛道精选推荐 - 品牌2025
  • 2026年必看!豆包GEO服务商深度推荐:帮你找到最合适的合作伙伴 - 品牌2025
  • obet处理ORA-704 ORA-604 ORA-1578故障
  • 破局南昌流量内卷|这家代运营公司,如何帮本地商户“躺赢”? - 野榜数据排行
  • 2026年知识库私有化部署推荐:优质厂商、服务商、方案商推荐 - 品牌2025
  • 2026年省时攻略,GEO服务商推荐,豆包GEO、DeepSeek GEO一键适配不费力 - 品牌2025
  • Java毕设项目推荐-基于springboot的中药科普知识平台的设计与实现中草药科普网站的设计与实现【附源码+文档,调试定制服务】