详解在Vue中使用TypeScript的一些思考(实践)

Vue.extend or vue-class-component

使用 TypeScript 写 Vue 组件时,有两种推荐形式:

  • Vue.extend():使用基础 Vue 构造器,创建一个“子类”。此种写法与 Vue 单文件组件标准形式最为接近,唯一不同仅是组件选项需要被包裹在 Vue.extend() 中。
  • vue-class-component:通常与 vue-property-decorator 一起使用,提供一系列装饰器,能让我们书写类风格的 Vue 组件。

两种形式输出结果一致,同是创建一个 Vue 子类,但在书写组件选项如 props,mixin 时,有些不同。特别是当你使用 Vue.extend() 时,为了让 TypeScript 正确推断类型,你将不得不做一些额外的处理。接下来,我们来聊一聊它们的细节差异。

Prop

由于组件实例的作用域是孤立的,当从父组件传递数据到子组件时,我们通常使用 Prop 选项。同时,为了确保 Prop 的类型安全,我们会给 Prop 添加指定类型验证,形式如下:

export default {
 props: {
  someProp: {
   type: Object,
   required: true,
   default: () => ({ message: 'test' })
  }
 }
}

我们定义了一个 someProp,它的类型是 Object。

使用 JavaScript 时,这并没有什么不对的地方,但当你使用 TypeScript 时,这有点不足,我们并不能得到有关于 someProp 更多有用的信息(比如它含有某些属性),甚至在 TypeScript 看来,这将会是一个 any 类型:

这意味着我们可以使用 someProp 上的任意属性(存在或者是不存在的)都可以通过编译。为了防止此种情况的发生,我们将会给 Prop 添加类型注释。

Vue.extend()

使用 Vue.extend() 方法添加类型注释时,需要给 type 断言:

import Vue from 'vue'
 
interface User {
 name: string,
 age: number
}
 
export default Vue.extend({
 props: {
  testProps: {
   type: Object as () => User
  }
 }
})

当组件内访问 testProps 时,便能得到相关提示:

前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。

然而,你必须以函数返回值的形式断言,并不能直接断言:

export default Vue.extend({
 props: {
  testProps: {
   type: Object as User
  }
 }
})

它会给出错误警告,User 接口并没有实现原生 Object 构造函数所执行的方法:
Type ‘ObjectConstructor’ cannot be converted to type ‘User’. Property ‘id’ is missing in type ‘ObjectConstructor’.

实际上,我们可从 Prop type declaration :

export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
 
export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];
 
export interface PropOptions<T=any> {
 type?: Prop<T> | Prop<T>[];
 required?: boolean;
 default?: T | null | undefined | (() => object);
 validator?(value: T): boolean;
}

可知 Prop type 可以以两种不同方式出现:

  • 含有一个调用签名的范型 type,该签名返回 T;
  • 一个范型构造函数签名,该函数创建指定类型 T 对象 (返回值 T & object 用于降低优先级,当两种方式同时满足时取第一种,其次它还可以用于标记构造函数不应该返回原始类型)。

当我们指定 type 类型为 String/Number/Boolean/Array/Object/Date/Function/Symbol 等原生构造函数时,Prop 会返回它们各自签名的返回值。

当 type 类型为 String 构造函数时,它的调用签名返回为 string:

// lib.es5.d.ts
 
interface StringConstructor {
 new(value?: any): String;
 (value?: any): string;
 readonly prototype: String;
 fromCharCode(...codes: number[]): string;
}

而这也是上文中,当指定 type 类型为 Object 构造函数时,经过 Vue 的声明文件处理,TypeScript 推断出为 any 类型的原因:

interface ObjectConstructor {
 new(value?: any): Object;
 (): any;
 (value: any): any;
 // 其它属性 ....
}

类似的,当我们使用关键字 as 断言 Object 为 () => User 时,它能推断出为 User 。

从 type 第二部分可知,除传入原生构造函数外,我们还可传入自定义类:

此外,这里有个 PR 暴露一个更直观的类型( Vue 2.6 版本才可以用):

props: {
 testProp: Object as PropTypes<{ test: boolean }>
}

vue-class-component

得益于 vue-propperty-decorator Prop 修饰器,当给 Prop 增加类型推断时,这些将变得简单:

import { Component, Vue, Prop } from 'vue-property-decorator'
 
@Component
export default class Test extends Vue {
 @Prop({ type: Object })
 private test: { value: string }
}

当我们在组件内访问 test 时,便能获取它正确的类型信息。

mixins

mixins 是一种分发 Vue 组件中可复用功能的一种方式。当在 TypeScript 中使用它时,我们希望得到有关于 mixins 的类型信息。

当你使用 Vue.extends() 时,这有点困难,它并不能推断出 mixins 里的类型:

// ExampleMixin.vue
export default Vue.extend({
 data () {
  return {
   testValue: 'test'
  }
 }
})
 
// other.vue
export default Vue.extend({
 mixins: [ExampleMixin],
 created () {
  this.testValue // error, testValue 不存在!
 }
})

前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。

我们需要稍作修改:

// other.vue
export default ExampleMixin.extend({
 mixins: [ExampleMixin],
 created () {
  this.testValue // 编译通过
 }
})

但这会存在一个问题,当使用多个 mixins 且推断出类型时,这将无法工作。而在这个 Issuse 中官方也明确表示,这无法被修改。

使用 vue-class-component 这会方便很多:

// ExampleMixin.vue
import Vue from 'vue'
import Component from 'vue-class-component'
 
@Component
export class ExampleMixin extends Vue {
 public testValue = 'test'
}
 
// other.vue
import Component, { mixins } from 'vue-class-component'
import ExampleMixin from 'ExampleMixin.vue'
 
@Component({
 components: {
  ExampleMixin
 }
})
export class MyComp extends mixins(ExampleMixin) {
 created () {
  console.log(this.testValue) // 编译通过
 }
}

也支持可以传入多个 mixins。

一些其它

做为 Vue 中最正统的方法(与标准形式最为接近),Vue.extends() 有着自己的优势,在 VScode Vetur 插件辅助下,它能正确提示子组件上的 Props:

而类做为 TypeScript 特殊的存在(它既可以作为类型,也可以作为值),当我们使用 vue-class-component 并通过 $refs 绑定为子类组件时,便能获取子组件上暴露的类型信息:

导入 .vue 时,为什么会报错?

当你在 Vue 中使用 TypeScript 时,所遇到的第一个问题即是在 ts 文件中找不到 .vue 文件,即使你所写的路径并没有问题:

在 TypeScript 中,它仅识别 js/ts/jsx/tsx 文件,为了让它识别 .vue 文件,我们需要显式告诉 TypeScript,vue 文件存在,并且指定导出 VueConstructor:

declare module '*.vue' {
 import Vue from 'vue'
 export default Vue
}

前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。

但是,这引起了另一个问题,当我们导入一个并不存在的 .vue 文件时,也能通过编译:

是的,这在情理之中。

当我尝试在 .vue 文件中导入已存在或者不存在的 .vue 文件时,却得到不同的结果:

文件不存在时:

文件存在时:

文件不存在时,引用 Vue 的声明文件。文件存在时,引用正确的文件定义。

这让人很困惑,而这些都是 Vetur 的功劳。

最后

为了帮助大家让学习变得轻松、高效,给大家免费分享一大批资料,帮助大家在成为全栈工程师,乃至架构师的路上披荆斩棘。在这里给大家推荐一个前端全栈学习交流圈:866109386.欢迎大家进群交流讨论,学习交流,共同进步。

当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。

但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有有效资源还是很有必要的。

最后祝福所有遇到瓶疾且不知道怎么办的前端程序员们,祝福大家在往后的工作与面试中一切顺利。

一些思考 一些帮助

09-08

2007年入职rn转眼已经过了一年了rn看着那些正在说着在大学煎熬的兄弟,心里面多的还是羡慕rn现在是开学的时候,无比的怀念我的大学rn也许给工作的人机会再读一次大学,80%的人都愿意.rn工作的一年,有开心,有难过,体会特别的多,也说下自己的一些体会,希望对正在读书,正在迷茫,正在消沉的兄弟姐妹们有点帮助,说实话,做程序员不容易啊-.-以下的每个小点只是临时想的,没有任何逻辑关系:)rnrn工作地点: 尽量去经济发达的,IT行业发达的地方(不管从环境还是机遇来说,这些地方都比较好,也许你会说这些地方竞争激烈,如果你真的这么想,我想告诉你,孩子,你该进敬老院了)rnrn工作单位: 如果是第一份工作,能进大公司固然是好,但是如果进不了,那么也要进一家能够让自己学到东西的公司,其实工资少点也没关系的,最主要的是你要相信自己,相信这些只是暂时的.在选择公司的时候,不要太相信别人说的,特别是在学校的时候,同学之间经常说这个公司好,那个公司不好,其实你要知道,他们也只是学生,他们对公司有多大的了解?这些时候你应该的是通过朋友,网络去了解你所感兴趣的公司,能够得到一些过来人的指导,其实是很好的,不过希望这个过来人有这个能力:).rnrnMFC: csdn经常会有问MFC是否过时这种问题,我想更多的是提问者想得到一种肯定,而非想知道MFC本身,如果是程序初学者,或者是在校的学生,不要管这些什么过不过时,更多的是应该提升自己对于程序的兴趣,人家用汇编都可以做你MFC做的事情.如果对于已经工作的人来说,我想应该慢慢的从MFC中走出来,从更高的层面去设计程序,不该受到MFC的约束.试着想想,有一天,突然有另外一个类库出现,全免费,功能齐全,简单易用,那么你还会用现在的MFC么? 自己大学的时候学习C++和MFC的时候就是什么都不管,网上找例子,自己修改例子,然后自己想例子,在这些不断的看别人的东西中,不断的有自己的东西产生的时候,对于编程产生了很好的兴趣,带着兴趣去学习,比什么都好,但是慢慢的,用多了,发现MFC有点束缚自己的思想了,慢慢的你就会发现,MFC不算什么,更多的是关注设计,关注OO.很多时候觉得,以前老师要我们学习C++的目的,更多是让我们了解面向对象编程思想.rnrn英语: 英语是个好东西,自己英语也不咋滴,但是知道这个东西很重要,在学校的学生应该每天用少打一局游戏的时间来多多学习点英语:)别到用的时候只会My name is~~~~;rnrn为人处事: 如果我是一个HR,那么我想人品和技术应该是对等考虑的,毕竟大家都是人,和你工作的同事也是人,有时间的时候多多审视一下自己,多多找找自身的问题,我们应该做一个细胞,懂得怎么去变异,怎么去更新:) rnrn圈子: 不要永远都想生活在自己的一个小小的圈子里面,多认识朋友,多交流,多沟通,别让社会把自己遗忘:)rnrn迷茫: 生活中经常会有迷茫,不知道自己该干什么,不知道自己走的路是否正确,不知道生活是为了什么,自己前途在哪里.心情很沮丧,每当这个时候,我会让自己尽量的放松,打打游戏,出去走走,唱唱歌,能放松就行,对于生活前途这些的思考,不应该是在迷茫的时候,更多的是应该在自己清醒的时候.rnrn工作: 负责是一个好事情,但是别学大哥一样把所有责任都归咎于自己,把所有开发任务都自己来扛,毕竟你是在团队里面开发,你还有你的伙伴,应该把责任平摊,这样让自己被这些事情烦心,也可以给自己的伙伴一些讯息,一些合作者的讯息:)这样整个团队才能和谐(时代的主题)....rnrn提醒: 有很多东西,上面的,关于生活关于程序的,在以前的生活中其实有人提醒过,但是有时候觉得不是那么重要,没有注意,善待提醒,好好的去想想别人的提醒,这个是生活中的一个避免出错的小窍门:)rnrn生活: 程序只是生活的一部分,很小,真的:)..别错过生活中其它美好的东西...rnrn另外由于个人原因,准备向上海发展了,想向兄弟姐妹们了解下上海有什么比较好点的公司?C++的...壮士一去兮不复还啊~~rn(GF看了写的过后说,你重点应该是最后一句吧,其实不是,很早就想写点这些东西了,因为在大学生活以及后面的工作中感受到,要是在生活中有人给自己一些指导,也许自己就不会走太多弯路了,自己随手写写文章,虽然力量很渺小,但是希望能够帮到需要帮助的人).

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试