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 这种类型完备的库应该在它们自己的类型定义中自动执行这些扩展。

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


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

相关内容

热门资讯

我来教大家“掼蛋大师辅助器ap... 亲,掼蛋大师这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,掼蛋大师的开挂软件,名...
我来教大家“开心联盟辅助器ap... 您好,开心联盟这款游戏可以开挂的,确实是有挂的,需要了解加微{7198902}很多玩家在这款游戏中打...
我来教大家“友愉棋牌辅助器ap... 您好,友愉棋牌这款游戏可以开挂的,确实是有挂的,需要了解加微{7198902}很多玩家在这款游戏中打...
我来教大家“全盛棋牌辅助器ap... 亲,全盛棋牌这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,全盛棋牌的开挂软件,名...
我来教大家“情怀三三麻将辅助器... 我来教大家“情怀三三麻将辅助器app官方下载最新版本更新”详细分享开挂一、情怀三三麻将AI软件牌型概...
我来教大家“无锡好运麻将辅助器... 亲,无锡好运麻将这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,无锡好运麻将的开挂...
我来教大家“推筒子辅助器app... 您好,推筒子这款游戏可以开挂的,确实是有挂的,需要了解加微{7198902}很多玩家在这款游戏中打牌...
我来教大家“比奇堡辅助器app... 我来教大家“比奇堡辅助器app官方下载最新版本更新”详细分享开挂1、让任何用户在无需AI插件第三方神...
我来教大家“新兴茶苑辅助器ap... 我来教大家“新兴茶苑辅助器app官方下载最新版本更新”详细分享开挂1、让任何用户在无需AI插件第三方...
我来教大家“欢乐互动辅助器ap... 我来教大家“欢乐互动辅助器app官方下载最新版本更新”详细分享开挂1、让任何用户在无需AI插件第三方...
我来教大家“东方互娱辅助器ap... 亲,东方互娱这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,东方互娱的开挂软件,名...
我来教大家“天天乐清麻将辅助器... 我来教大家“天天乐清麻将辅助器app官方下载最新版本更新”详细分享开挂一、天天乐清麻将AI软件牌型概...
我来教大家“唐三彩辅助器app... 我来教大家“唐三彩辅助器app官方下载最新版本更新”详细分享开挂一、唐三彩AI软件牌型概率发牌机制”...
我来教大家“Y0辅助器app官... 我来教大家“Y0辅助器app官方下载最新版本更新”详细分享开挂一、Y0AI软件牌型概率发牌机制”必胜...
我来教大家“天天互娱辅助器ap... 亲,天天互娱这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,天天互娱的开挂软件,名...
我来教大家“悟空竞技麻将辅助器... 亲,悟空竞技麻将这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,悟空竞技麻将的开挂...
我来教大家“情怀开化麻将辅助器... 我来教大家“情怀开化麻将辅助器app官方下载最新版本更新”详细分享开挂一、情怀开化麻将AI软件牌型概...
我来教大家“龙炎互娱辅助器ap... 我来教大家“龙炎互娱辅助器app官方下载最新版本更新”详细分享开挂一、龙炎互娱AI软件牌型概率发牌机...
我来教大家“闲逸碰胡辅助器ap... 亲,闲逸碰胡这款游戏可以开挂的,确实是有挂的,。但是开挂要下载第三方辅助软件,闲逸碰胡的开挂软件,名...
我来教大家“友聚麻将辅助器ap... 我来教大家“友聚麻将辅助器app官方下载最新版本更新”详细分享开挂一、友聚麻将AI软件牌型概率发牌机...