Vue 之 vue3 与 TS 的配合使用整理
admin
2024-02-07 23:28:24
0

目录

  • 说明
  • 前言
    • 配置 tsconfig.json
  • 正文
    • TypeScript 与组合式 API
      • 为组件的 props 标注类型
        • 使用 `

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

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

          
          

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

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

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

          
          

          语法限制

          为了生成正确的运行时代码,传给 defineProps() 的泛型参数必须是以下之一:

          • 一个类型字面量:
          defineProps<{ /*... */ }>()
          
          • 同一个文件中的一个接口或对象类型字面量的引用:
          interface Props {/* ... */}defineProps()
          

          接口或对象字面类型可以包含从其他文件导入的类型引用,但是,传递给 defineProps 的泛型参数本身不能是一个导入的类型:

          import { Props } from './other-file'// 不支持!
          defineProps()
          

          这是因为 Vue 组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。我们计划在未来的版本中解决这个限制。

          Props 解构默认值

          当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:

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

          这将被编译为等效的运行时 props default 选项。此外,withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。

          或者,你可以使用目前为实验性的响应性语法糖:

          
          

          这个行为目前需要显式地选择开启。

          这个类型参数应该是一个带调用签名的类型字面量。这个类型字面量的类型就是返回的 emit 函数的类型。我们可以看到,基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。

          若没有使用

          
          

          没有类型标注时,这个 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 InjectionKeyprovide(key, 'foo') // 若提供的是非字符串值会导致错误const foo = inject(key) // foo 的类型:string | undefined
          

          建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入。

          当使用字符串注入 key 时,注入值的类型是 unknown,需要通过泛型参数显式声明:

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

          注意注入的值仍然可以是 undefined,因为无法保证提供者一定会在运行时 provide 这个值。

          当提供了一个默认值后,这个 undefined 类型就可以被移除:

          const foo = inject('foo', 'bar') // 类型:string
          

          如果你确定该值将始终被提供,则还可以强制转换该值:

          const foo = inject('foo') as string
          

          为模板引用标注类型

          模板引用需要通过一个显式指定的泛型参数和一个初始值 null 来创建:

          
          
          
          

          注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。这是因为直到组件被挂载前,这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null。

          为组件模板引用标注类型

          有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 MyModal 子组件,它有一个打开模态框的方法:

          
          
          

          为了获取 MyModal 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型:

          
          
          

          注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用这种技巧,需要开启 Volar 的 Takeover 模式。


          TypeScript 与选项式 API

          为组件的 props 标注类型

          选项式 API 中对 props 的类型推导需要用 defineComponent() 来包装组件。有了它,Vue 才可以通过 props 以及一些额外的选项,比如 required: true 和 default 来推导出 props 的类型:

          import { defineComponent } from 'vue'export default defineComponent({// 启用了类型推导props: {name: String,id: [Number, String],msg: { type: String, required: true },metadata: null},mounted() {this.name // 类型:string | undefinedthis.id // 类型:number | string | undefinedthis.msg // 类型:stringthis.metadata // 类型:any}
          })
          

          然而,这种运行时 props 选项仅支持使用构造函数来作为一个 prop 的类型——没有办法指定多层级对象或函数签名之类的复杂类型。

          我们可以使用 PropType 这个工具类型来标记更复杂的 props 类型:

          import { defineComponent } from 'vue'
          import type { PropType } from 'vue'interface Book {title: stringauthor: stringyear: number
          }export default defineComponent({props: {book: {// 提供相对 `Object` 更确定的类型type: Object as PropType,required: true},// 也可以标记函数callback: Function as PropType<(id: number) => void>},mounted() {this.book.title // stringthis.book.year // number// TS Error: argument of type 'string' is not// assignable to parameter of type 'number'this.callback?.('123')}
          })
          

          注意事项 如果你的 TypeScript 版本低于 4.7,在使用函数作为 prop 的 validator 和 default
          选项值时需要格外小心——确保使用箭头函数:

          import { defineComponent } from 'vue'
          import type { PropType } from 'vue'interface Book {title: stringyear?: number
          }export default defineComponent({props: {bookA: {type: Object as PropType,// 如果你的 TypeScript 版本低于 4.7,确保使用箭头函数default: () => ({title: 'Arrow Function Expression'}),validator: (book: Book) => !!book.title}}
          })
          

          这会防止 TypeScript 将 this 根据函数内的环境作出不符合我们期望的类型推导。这是之前版本的一个设计限制,不过现在已经在
          TypeScript 4.7 中解决了。

          为组件的 emits 标注类型

          我们可以给 emits 选项提供一个对象来声明组件所触发的事件,以及这些事件所期望的参数类型。试图触发未声明的事件会抛出一个类型错误:

          import { defineComponent } from 'vue'export default defineComponent({emits: {addBook(payload: { bookName: string }) {// 执行运行时校验return payload.bookName.length > 0}},methods: {onSubmit() {this.$emit('addBook', {bookName: 123 // 类型错误})this.$emit('non-declared-event') // 类型错误}}
          })
          

          为计算属性标记类型

          计算属性会自动根据其返回值来推导其类型:

          import { defineComponent } from 'vue'export default defineComponent({data() {return {message: 'Hello!'}},computed: {greeting() {return this.message + '!'}},mounted() {this.greeting // 类型:string}
          })
          

          在某些场景中,你可能想要显式地标记出计算属性的类型以确保其实现是正确的:

          import { defineComponent } from 'vue'export default defineComponent({data() {return {message: 'Hello!'}},computed: {// 显式标注返回类型greeting(): string {return this.message + '!'},// 标注一个可写的计算属性greetingUppercased: {get(): string {return this.greeting.toUpperCase()},set(newValue: string) {this.message = newValue.toUpperCase()}}}
          })
          

          在某些 TypeScript 因循环引用而无法推导类型的情况下,可能必须进行显式的类型标注。

          为事件处理函数标注类型

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

          
          
          
          

          没有类型标注时,这个 event 参数会隐式地标注为 any 类型。这也会在 tsconfig.json 中配置了 “strict”: true 或 “noImplicitAny”: true 时抛出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你可能需要显式地强制转换 event 上的属性:

          import { defineComponent } from 'vue'export default defineComponent({methods: {handleChange(event: Event) {console.log((event.target as HTMLInputElement).value)}}
          })
          

          扩展全局属性

          某些插件会通过 app.config.globalProperties 为所有组件都安装全局可用的属性。举例来说,我们可能为了请求数据而安装了 this.$http,或者为了国际化而安装了 this.$translate。为了使 TypeScript 更好地支持这个行为,Vue 暴露了一个被设计为可以通过 TypeScript 模块扩展来扩展的 ComponentCustomProperties 接口:

          import axios from 'axios'declare module 'vue' {interface ComponentCustomProperties {$http: typeof axios$translate: (key: string) => string}
          }
          

          类型扩展的位置

          我们可以将这些类型扩展放在一个 .ts 文件,或是一个影响整个项目的 *.d.ts 文件中。无论哪一种,都应确保在 tsconfig.json 中包括了此文件。对于库或插件作者,这个文件应该在 package.json 的 types 属性中被列出。

          为了利用模块扩展的优势,你需要确保将扩展的模块放在 TypeScript 模块 中。 也就是说,该文件需要包含至少一个顶级的 import 或 export,即使它只是 export {}。如果扩展被放在模块之外,它将覆盖原始类型,而不是扩展!

          // 不工作,将覆盖原始类型。
          declare module 'vue' {interface ComponentCustomProperties {$translate: (key: string) => string}
          }
          
          // 正常工作。
          export {}declare module 'vue' {interface ComponentCustomProperties {$translate: (key: string) => string}
          }
          

          扩展自定义选项

          某些插件,比如 vue-router,提供了一些自定义的组件选项,比如 beforeRouteEnter:

          import { defineComponent } from 'vue'export default defineComponent({beforeRouteEnter(to, from, next) {// ...}
          })
          

          如果没有确切的类型标注,这个钩子函数的参数会隐式地标注为 any 类型。我们可以为 ComponentCustomOptions 接口扩展自定义的选项来支持:

          import { Route } from 'vue-router'declare module 'vue' {interface ComponentCustomOptions {beforeRouteEnter?(to: Route, from: Route, next: () => void): void}
          }
          

          现在这个 beforeRouteEnter 选项会被准确地标注类型。注意这只是一个例子——像 vue-router 这种类型完备的库应该在它们自己的类型定义中自动执行这些扩展。

          这种类型扩展和全局属性扩展受到相同的限制。


          如有不足,望大家多多指点! 谢谢!

相关内容

热门资讯

电视安卓系统哪个品牌好,哪家品... 你有没有想过,家里的电视是不是该升级换代了呢?现在市面上电视品牌琳琅满目,各种操作系统也是让人眼花缭...
安卓会员管理系统怎么用,提升服... 你有没有想过,手机里那些你爱不释手的APP,背后其实有个强大的会员管理系统在默默支持呢?没错,就是那...
安卓系统软件使用技巧,解锁软件... 你有没有发现,用安卓手机的时候,总有一些小技巧能让你玩得更溜?别小看了这些小细节,它们可是能让你的手...
安卓系统提示音替换 你知道吗?手机里那个时不时响起的提示音,有时候真的能让人心情大好,有时候又让人抓狂不已。今天,就让我...
安卓开机不了系统更新 手机突然开不了机,系统更新还卡在那里,这可真是让人头疼的问题啊!你是不是也遇到了这种情况?别急,今天...
安卓系统中微信视频,安卓系统下... 你有没有发现,现在用手机聊天,视频通话简直成了标配!尤其是咱们安卓系统的小伙伴们,微信视频功能更是用...
安卓系统是服务器,服务器端的智... 你知道吗?在科技的世界里,安卓系统可是个超级明星呢!它不仅仅是个手机操作系统,竟然还能成为服务器的得...
pc电脑安卓系统下载软件,轻松... 你有没有想过,你的PC电脑上安装了安卓系统,是不是瞬间觉得世界都大不一样了呢?没错,就是那种“一机在...
电影院购票系统安卓,便捷观影新... 你有没有想过,在繁忙的生活中,一部好电影就像是一剂强心针,能瞬间让你放松心情?而我今天要和你分享的,...
安卓系统可以写程序? 你有没有想过,安卓系统竟然也能写程序呢?没错,你没听错!这个我们日常使用的智能手机操作系统,竟然有着...
安卓系统架构书籍推荐,权威书籍... 你有没有想过,想要深入了解安卓系统架构,却不知道从何下手?别急,今天我就要给你推荐几本超级实用的书籍...
安卓系统看到的炸弹,技术解析与... 安卓系统看到的炸弹——揭秘手机中的隐形威胁在数字化时代,智能手机已经成为我们生活中不可或缺的一部分。...
鸿蒙系统有安卓文件,畅享多平台... 你知道吗?最近在科技圈里,有个大新闻可是闹得沸沸扬扬的,那就是鸿蒙系统竟然有了安卓文件!是不是觉得有...
宝马安卓车机系统切换,驾驭未来... 你有没有发现,现在的汽车越来越智能了?尤其是那些豪华品牌,比如宝马,它们的内饰里那个大屏幕,简直就像...
p30退回安卓系统 你有没有听说最近P30的用户们都在忙活一件大事?没错,就是他们的手机要退回安卓系统啦!这可不是一个简...
oppoa57安卓原生系统,原... 你有没有发现,最近OPPO A57这款手机在安卓原生系统上的表现真是让人眼前一亮呢?今天,就让我带你...
安卓系统输入法联想,安卓系统输... 你有没有发现,手机上的输入法真的是个神奇的小助手呢?尤其是安卓系统的输入法,简直就是智能生活的点睛之...
怎么进入安卓刷机系统,安卓刷机... 亲爱的手机控们,你是否曾对安卓手机的刷机系统充满好奇?想要解锁手机潜能,体验全新的系统魅力?别急,今...
安卓系统程序有病毒 你知道吗?在这个数字化时代,手机已经成了我们生活中不可或缺的好伙伴。但是,你知道吗?即使是安卓系统,...
奥迪中控安卓系统下载,畅享智能... 你有没有发现,现在汽车的中控系统越来越智能了?尤其是奥迪这种豪华品牌,他们的中控系统简直就是科技与艺...