详解在Vue中使用TypeScript的一些思考(实践)
Vue.extend or vue-class-ponent
使用 TypeScript 写 Vue 组件时,有两种推荐形式
Vue.extend()
使用基础 Vue 构造器,创建一个“子类”。此种写法与 Vue 单文件组件标准形式最为接近,唯一不同仅是组件选项需要被包裹在Vue.extend()
中。vue-class-ponent
通常与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 时,便能得到相关提示
,你必须以函数返回值的形式断言,并不能直接断言
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'.
实际上,我们可从
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 2.6 版本才可以用)
props: { testProp: Object as PropTypes<{ test: boolean }> }
vue-class-ponent
得益于 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-ponent 这会方便很多
// ExampleMixin.vue import Vue from 'vue' import Component from 'vue-class-ponent' @Component export class ExampleMixin extends Vue { public testValue = 'test' } // other.vue import Component, { mixins } from 'vue-class-ponent' import ExampleMixin from 'ExampleMixin.vue' @Component({ ponents: { ExampleMixin } }) export class MyComp extends mixins(ExampleMixin) { created () { console.log(this.testValue) // 编译通过 } }
也支持可以传入多个 mixins。
一些其它
做为 Vue 中最正统的方法(与标准形式最为接近),Vue.extends()
有着自己的优势,在 VScode Vetur 插件辅助下,它能正确提示子组件上的 Props:
而类做为 TypeScript 特殊的存在(它既可以作为类型,也可以作为值),当我们使用 vue-class-ponent
并通过 $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 }
,这引起了另一个问题,当我们导入一个并不存在的 .vue 文件时,也能通过编译
是的,这在情理之中。
当我尝试在 .vue 文件中导入已存在或者不存在的 .vue 文件时,却得到不同的结果
文件不存在时
文件存在时
文件不存在时,引用 Vue 的声明文件。文件存在时,引用正确的文件定义。
这让人很困惑,而这些都是 Vetur 的功劳。
在这个 下,我找到相关解答这个 PR 里,Vetur 提供解析其他 .vue 文件的功能,以便能获取正确的信息,当 .vue 文件不存在时,会读取 .d.ts 里的信息。
参考
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程