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


Posted in Javascript onJuly 06, 2018

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 类型:

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

这意味着我们可以使用 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 时,便能得到相关提示:

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

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

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<T> 会返回它们各自签名的返回值。

当 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 第二部分可知,除传入原生构造函数外,我们还可传入自定义类:

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

此外,这里有个 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 不存在!
 }
})

我们需要稍作修改:

// 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:

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

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

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

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

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

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

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

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

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

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

是的,这在情理之中。

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

文件不存在时:

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

文件存在时:

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

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

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

在这个 PR 下,我找到相关解答:这个 PR 里,Vetur 提供解析其他 .vue 文件的功能,以便能获取正确的信息,当 .vue 文件不存在时,会读取 .d.ts 里的信息。

参考

https://github.com/vuejs/vue/pull/5887

https://github.com/vuejs/vue/issues/7211

https://github.com/vuejs/vue/pull/6856

https://github.com/vuejs/vue/pull/5887/files/1092efe6070da2052a8df97a802c9434436eef1e#diff-23d7799dcc9e9be419d28a15348b0d99

https://github.com/Microsoft/TypeScript/blob/8e47c18636da814117071a2640ccf87c5f16fcfd/src/compiler/types.ts#L3563-L3583

https://github.com/vuejs/vetur/pull/94

Javascript 相关文章推荐
在网页中屏蔽快捷键
Sep 06 Javascript
IE事件对象(The Internet Explorer Event Object)
Jun 27 Javascript
Jquery AJAX POST与GET之间的区别
Nov 14 Javascript
Javascript中的数组常用方法解析
Jun 17 Javascript
jQuery悬停文字提示框插件jquery.tooltipster.js用法示例【附demo源码下载】
Jul 19 Javascript
js事件冒泡、事件捕获和阻止默认事件详解
Aug 04 Javascript
用headjs来管理和加载js 提高网站加载速度
Nov 29 Javascript
jquery实现自适应banner焦点图
Feb 16 Javascript
详解Vue如何支持JSX语法
Nov 10 Javascript
jquery实现动态改变css样式的方法分析
May 27 jQuery
layer弹出层自定义提交取消按钮的例子
Sep 10 Javascript
js中实现继承的五种方法
Jan 25 Javascript
javascript显示动态时间的方法汇总
Jul 06 #Javascript
Django+Vue跨域环境配置详解
Jul 06 #Javascript
微信小程序画布圆形进度条显示效果
Nov 17 #Javascript
微信小程序实时聊天WebSocket
Jul 05 #Javascript
vue v-model实现自定义样式多选与单选功能
Jul 05 #Javascript
基于vue展开收起动画的示例代码
Jul 05 #Javascript
微信小程序实现星级评分和展示
Jul 05 #Javascript
You might like
php FPDF类库应用实现代码
2009/03/20 PHP
自编函数解决pathinfo()函数处理中文问题
2014/11/03 PHP
php使用glob函数快速查询指定目录文件的方法
2014/11/15 PHP
php使用GD创建保持宽高比缩略图的方法
2015/04/17 PHP
使用php-timeit估计php函数的执行时间
2015/09/06 PHP
PHP实现导出带样式的Excel
2016/08/28 PHP
PHP实现电商订单自动确认收货redis队列
2017/05/17 PHP
JQuery中html()方法使用不当带来的陷阱
2011/04/07 Javascript
读jQuery之五(取DOM元素)
2011/06/20 Javascript
JavaScript自定义DateDiff函数(兼容所有浏览器)
2012/03/01 Javascript
Asp.Net alert弹出提示信息的几种方法总结
2014/01/29 Javascript
jquery仅用6行代码实现滑动门效果
2015/09/07 Javascript
javascript原生ajax写法分享
2016/04/10 Javascript
Javascript 高性能之递归,迭代,查表法详解及实例
2017/01/08 Javascript
JavaScript实现定时页面跳转功能示例
2017/02/14 Javascript
微信小程序开发教程之增加mixin扩展
2017/08/09 Javascript
js中switch语句的学习笔记
2020/03/25 Javascript
小谈angular ng deploy的实现
2020/04/07 Javascript
vue+canvas实现移动端手写签名
2020/05/21 Javascript
python pickle 和 shelve模块的用法
2013/09/16 Python
Python中尝试多线程编程的一个简明例子
2015/04/07 Python
Python利用multiprocessing实现最简单的分布式作业调度系统实例
2017/11/14 Python
详解Python数据可视化编程 - 词云生成并保存(jieba+WordCloud)
2019/03/26 Python
Python处理mysql特殊字符的问题
2020/03/02 Python
python 连续不等式语法糖实例
2020/04/15 Python
Python描述数据结构学习之哈夫曼树篇
2020/09/07 Python
Prometheus开发中间件Exporter过程详解
2020/11/30 Python
升职自荐信
2013/11/28 职场文书
劳资专员岗位职责
2013/12/27 职场文书
八一建军节活动方案
2014/02/10 职场文书
群众路线教育实践活动实施方案
2014/10/31 职场文书
质量整改报告范文
2014/11/08 职场文书
财务人员个人工作总结
2015/02/27 职场文书
退休职工欢送会致辞
2015/08/01 职场文书
《鲁滨逊漂流记》之六读后感(4篇)
2019/09/29 职场文书
python基于opencv批量生成验证码的示例
2021/04/28 Python