xxxxxxxxxx
let isHandsome: boolean = true
xxxxxxxxxxlet age: number = 18
string
xxxxxxxxxxlet realName: string = 'lin'
let fullName: string = `A ${realName}` // 支持模板字符串 xxxxxxxxxxlet u:undefined = undefined // undefined 类型let n:null = null // null 类型默认情况下 null 和 undefined 是所有类型的子类型。可以把 null 和 undefined 赋值给 number 类型的变量。
但是如果指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自,不然会报错。
any
不清楚用什么类型,可以使用 any 类型。
xxxxxxxxxxlet notSure: any = 4notSure = "maybe a string" // 可以是 string 类型notSure = false // 也可以是 boolean 类型
notSure.name // 可以随便调用属性和方法notSure.getName()不建议使用 any,不然就丧失了 TS 的意义。
unknown 类型
不建议使用 any,当我不知道一个类型具体是什么时,可以使用 unknown 类型,unknown 类型代表任何类型,它的定义和 any 定义很像,但是它是一个安全类型,使用 unknown 做任何事情都是不合法的。
比如,这样一个 divide 函数,把 param 定义为 unknown 类型 ,TS 编译器就能拦住潜在风险,如下图,
xxxxxxxxxxfunction divide(param: unknown) { return param / 2;}因为不知道 param 的类型,使用运算符 /,导致报错。再配合类型断言,即可解决这个问题
类型断言(Type Assertion)可以用来手动指定一个值的类型。
断言语法:
<类型>值或值 as 类型
xxxxxxxxxxfunction divide(param: unknown) { return param as number / 2;}void
void类型与 any 类型相反,它表示没有任何类型。比如函数没有明确返回值,默认返回 Void 类型
xxxxxxxxxxfunction welcome(): void { console.log('hello')}never类型表示的是那些永不存在的值的类型
有些情况下值会永不存在,比如:
如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值
函数中执行无限循环的代码
xxxxxxxxxx// 异常function fn(msg: string): never { throw new Error(msg)}
// 死循环 千万别这么写,会内存溢出function fn(): never { while (true) {}}复制代码never 类型是任何类型的子类型,也可以赋值给任何类型。
没有类型是 never 的子类型,没有类型可以赋值给 never 类型
xxxxxxxxxxlet list: number[] = [1, 2, 3]list.push(4) // 可以调用数组上的方法数组中元素的数据类型都一般是相同的(any[] 类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组。
声明一个元组并初始化:
xxxxxxxxxxvar mytuple = [10,"Runoob"];
或者我们可以先声明一个空元组,然后再初始化:
xxxxxxxxxxvar mytuple = [];mytuple[0] = 120mytuple[1] = 234
元组中元素使用索引来访问,语法格式如下:
xxxxxxxxxxmytuple[index]
可以对元组使用数组的方法,比如使用 push 时,不会有越界报错
xxxxxxxxxxlet tuple: [number, string] = [18, 'lin']tuple.push(100) // 但是只能 push 定义的 number 或者 string 类型TS 定义函数类型需要定义输入参数类型和输出类型。
输出类型也可以忽略,因为 TS 能够根据返回语句自动推断出返回值类型。
xxxxxxxxxxfunction add(x:number, y:number):number { return x + y}add(1,2)函数没有明确返回值,默认返回 Void 类型
xxxxxxxxxxfunction welcome(): void { console.log('hello');}参数后加个问号,代表这个参数是可选的,注意可选参数要放在函数入参的最后面,不然会导致编译错误。
xxxxxxxxxxfunction add(x:number, y:number, z?:number):number { return x + y}
add(1,2,3)add(1,2)xxxxxxxxxxfunction add(x:number, y:number = 100):number { return x + y}
add(100) // 200跟 JS 的写法一样,在入参里定义初始值。
JS 中变量随便赋值没问题,但在 TS 中函数不能随便赋值,会报错的。
也可以用下面这种方式定义一个函数 add3,把 add2 赋值给 add3
xxxxxxxxxxlet add2 = (x: number, y: number): number => { return x + y}
const add3:(x: number, y: number) => number = add2有点像 es6 中的箭头函数,但不是箭头函数,TS 遇到 : 就知道后面的代码是类型。
函数重载是指两个函数名称相同,但是参数个数或参数类型不同
比如实现一个 add 函数,如果传入参数都是数字,就返回数字相加,如果传入参数都是字符串,就返回字符串拼接。
xxxxxxxxxxfunction add(x: number[]): numberfunction add(x: string[]): stringfunction add(x: any[]): any { if (typeof x[0] === 'string') { return x.join() } if (typeof x[0] === 'number') { return x.reduce((acc, cur) => acc + cur) }}
在 TS 中,实现函数重载,需要多次声明这个函数,前几次是函数定义,列出所有的情况,最后一次是函数实现,需要比较宽泛的类型,比如上面的例子就用到了 any。
interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
定义 interface 一般首字母大写,代码如下:
xxxxxxxxxxinterface Person { name: string age: number}
const p1: Person = { name: 'lin', age: 18}注意:interface 不是 JS 中的关键字,所以 TS 编译成 JS 之后,interface都会被删除掉,interface 只是在 TS 中用来做静态检查。
跟函数的可选参数是类似的,在属性上加个 ?,这个属性就是可选的,比如下面的 age 属性
xxxxxxxxxxinterface Person { name: string age?: number}
const p1: Person = { name: 'lin',}如果希望某个属性不被改变,可以这么写:
xxxxxxxxxxinterface Person { readonly id: number name: string}interface 也可以用来描述函数类型,代码如下:
xxxxxxxxxxinterface ISum { (x:number,y:number):number}
const add:ISum = (num1, num2) => { return num1 + num2}interface 还有一个响亮的名称: duck typing(鸭子类型)。
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
举个例子:
xxxxxxxxxxinterface FunctionWithProps { (x: number): number fnName: string}FunctionWithProps 接口描述了一个函数类型,还向这个函数类型添加了 name 属性,这看上去完全是四不像,但是这个定义是完全可以工作的。React 的 FunctionComponent(函数式组件) 就是这么写的
xxxxxxxxxxconst fn: FunctionWithProps = (x) => { return x}
fn.fnName = 'hello world'定义一个 Person 类,有属性 name 和 方法 speak
xxxxxxxxxxclass Person { name: string constructor(name: string) { this.name = name } speak() { console.log(`${this.name} is speaking`) }}
const p1 = new Person('lin') // 新建实例
p1.name // 访问属性和方法p1.speak()使用 extends 关键字实现继承,定义一个 Student 类继承自 Person 类。
xxxxxxxxxxclass Student extends Person { study() { console.log(`${this.name} needs study`) }}
const s1 = new Student('lin')s1.study()注意,上例中 Student 类没有定义自己的属性,可以不写 super ,但是如果 Student 类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。比如,Student 类新增一个 grade(成绩) 属性,就要这么写:
xxxxxxxxxxclass Student extends Person { grade: number constructor(name: string,grade:number) { super(name) this.grade = grade }}
const s1 = new Student('lin', 100)public,公有的,一个类里默认所有的方法和属性都是 public。
private,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。
protected 受保护的,继承它的子类可以访问,实例不能访问。
将 Person 类的 name 属性改为 protected。
xxxxxxxxxxclass Person { protected name: string public constructor(name: string) { this.name = name } public speak() { console.log(`${this.name} is speaking`) }}实例访问 name 属性,会报错:类可以访问。
xxxxxxxxxxclass Studeng extends Person { study() { console.log(`${this.name} needs study`) }}static 是静态属性,可以理解为是类上的一些常量,实例不能访问。
xxxxxxxxxxclass Circle { static pi = 3.14 public radius: number public constructor(radius: number) { this.radius = radius } public calcLength() { return Circle.pi * this.radius * 2 // 计算周长,直接访问 Circle.pi }}实例访问,会报错
所谓抽象类,是指只能被继承,但不能被实例化的类,抽象类用一个 abstract 关键字来定义
抽象类有两个特点:
抽象类不允许被实例化
抽象类中的抽象方法必须被子类实现
定义一个 Dog 类,继承自 Animal 类
xxxxxxxxxxabstract class Animal { constructor(name:string) { this.name = name } public name: string public abstract sayHi():void}
class Dog extends Animal { constructor(name:string) { super(name) } public sayHi() { console.log('wang') }}
多态指的是,父类定义一个抽象方法,在多个子类中有不同的实现,运行的时候不同的子类就对应不同的操作,比如:
xxxxxxxxxxabstract class Animal { constructor(name:string) { this.name = name } public name: string public abstract sayHi():void}
class Dog extends Animal { constructor(name:string) { super(name) } public sayHi() { console.log('wang') }}
class Cat extends Animal { constructor(name:string) { super(name) } public sayHi() { console.log('miao') }}
Dog 类和 Cat 类都继承自 Animal 类,Dog 类和 Cat 类都不同的实现了 sayHi 这个方法。
类的成员方法可以直接返回一个 this,这样就可以很方便地实现链式调用。
xxxxxxxxxxclass StudyStep { step1() { console.log('listen') return this } step2() { console.log('write') return this }}
const s = new StudyStep()s.step1().step2() // 链式调用在继承的时候,this 可以表示父类型,也可以表示子类型
xxxxxxxxxxclass StudyStep { step1() { console.log('listen') return this } step2() { console.log('write') return this }}
class MyStudyStep extends StudyStep { next() { console.log('before done, study next!') return this }}
const m = new MyStudyStep()
m.step1().next().step2().next() // 父类型和子类型上的方法都可随意调用这样就保持了父类和子类之间接口调用的连贯性。
interface 同样可以用来约束 class,要实现约束,需要用到 implements 关键字。
比如手机有播放音乐的功能,可以这么写:
xxxxxxxxxxinterface MusicInterface { playMusic(): void}
class Cellphone implements MusicInterface { playMusic() {}}复制代码定义了约束后,class 必须要满足接口上的所有条件。如果 Cellphone 类上不写 playMusic 方法,会报错。
不同的类有一些共同的属性和方法,使用继承很难完成。使用 implements,问题就会迎刃而解。
xxxxxxxxxxinterface MusicInterface { playMusic(): void}
class Car implements MusicInterface { playMusic() {}}
class Cellphone implements MusicInterface { playMusic() {}}这样 Car 类和 Cellphone 类都约束了播放音乐的功能。
TS 中我们使用 const 来声明常量,但是有些取值是在一定范围内的一系列常量。
比如一周有七天,比如方向分为上下左右四个方向。
这时就可以使用枚举(Enum)来定义。他有两个特点:
数字递增
反向映射
枚举成员会被赋值为从 0 开始递增的数字,
xxxxxxxxxxenum Direction { Up, Down, Left, Right}
console.log(Direction.Up) // 0console.log(Direction.Down) // 1console.log(Direction.Left) // 2console.log(Direction.Right) // 3枚举会对枚举值到枚举名进行反向映射,
xxxxxxxxxxconsole.log(Direction[0]) // Upconsole.log(Direction[1]) // Downconsole.log(Direction[2]) // Leftconsole.log(Direction[3]) // Right如果枚举第一个元素赋有初始值,就会从初始值开始递增,
xxxxxxxxxxenum Direction { Up = 6, Down, Left, Right}
console.log(Direction.Up) // 6console.log(Direction.Down) // 7console.log(Direction.Left) // 8console.log(Direction.Right) // 9但有时候后端给你返回的数据状态是乱的,就需要我们手动赋值。比如后端说 Buy 是 100,Send 是 20,Receive 是 1,就可以这么写,
xxxxxxxxxxenum ItemStatus { Buy = 100, Send = 20, Receive = 1}
console.log(ItemStatus['Buy']) // 100console.log(ItemStatus['Send']) // 20console.log(ItemStatus['Receive']) // 1实际开发中经常会有这种情况发生。
枚举中的成员可以被计算,比如经典的使用位运算合并权限,可以这么写,
xxxxxxxxxxenum FileAccess { Read = 1 << 1, Write = 1 << 2, ReadWrite = Read | Write,}
console.log(FileAccess.Read) // 2 -> 010console.log(FileAccess.Write) // 4 -> 100console.log(FileAccess.ReadWrite) // 6 -> 110使用 const 来定义一个常量枚举
xxxxxxxxxxconst enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT",}
const value = 'UP'if (value === Direction.Up) { // do something}常量枚举不允许包含计算成员 常量枚举可以避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问。
xxxxxxxxxxlet aa = 18a = 'lin'定义时不赋值,就会被 TS 自动推导成 any 类型,之后随便怎么赋值都不会报错。
xxxxxxxxxxlet userName = 'lin'因为赋值的时候赋的是一个字符串类型,所以 TS 自动推导出 userName 是 string 类型。
这个时候,再更改 userName 时,就必须是 string 类型,是其他类型就报错
函数设置默认参数时,也会有自动推导,比如定义一个打印年龄的函数,默认值是 18
xxxxxxxxxxfunction printAge(num = 18) { console.log(num) return num}那么 TS 会自动推导出 printAge 的入参类型,传错了类型会报错。
决定函数返回值时, TS 也会自动推导出返回值类型。比如一个函数不写返回值,
xxxxxxxxxxfunction welcome() { console.log('hello')}TS 自动推导出返回值是 void 类型
内置对象是指根据标准在全局作用域 global 上存在的对象,这里的标准指的是 ECMAcript 和其他环境(比如DOM)的标准。
xxxxxxxxxxlet name: string = "lin";let age: number = 18;let isHandsome: boolean = true;let u: undefined = undefined;let n: null = null;let obj: object = {name: 'lin', age: 18};let big: bigint = 100n;let sym: symbol = Symbol("lin"); 比如,Array、Date、Error 等,
xxxxxxxxxxconst nums: Array<number> = [1,2,3]const date: Date = new Date()const err: Error = new Error('Error!');const reg: RegExp = /abc/;Math.pow(2, 9)比如 HTMLElement、NodeList、MouseEvent 等
xxxxxxxxxxlet body: HTMLElement = document.body
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', (e: MouseEvent) => { e.preventDefault() // Do something});
如果希望一个变量可以支持多种类型,就可以用联合类型(union types)来定义。
一个变量既支持 number 类型,又支持 string 类型,就可以这么写:
xxxxxxxxxxlet num: number | stringnum = 8num = 'eight'如果直接访问 length 属性,string 类型上有,number 类型上没有,就报错了。
要对对象形状进行扩展,可以使用交叉类型 &。
比如 Person 有 name 和 age 的属性,而 Student 在 name 和 age 的基础上还有 grade 属性,就可以这么写,
xxxxxxxxxxinterface Person { name: string age: number}
type Student = Person & { grade: number }交叉类型和 interface 的 extends 非常类似,都是为了实现对象形状的组合和扩展。
类型别名(type aliase),听名字就很好理解,就是给类型起个别名。
类型别名的用法如下,
xxxxxxxxxxtype Name = string // 基本类型type arrItem = number | string // 联合类型
const arr: arrItem[] = [1,'2', 3]两者相同点:
都可以定义一个对象或函数
xxxxxxxxxxinterface Person {name: stringage: number}const person: Person = {name: 'lin',age: 18}type Person = {name: stringage: number}const person: Person = {name: 'lin',age: 18}// 这两种写法都可以定义函数类型type addType = (num1:number,num2:number) => numberinterface addType {(num1:number,num2:number):number}const add:addType = (num1, num2) => {return num1 + num2}
都允许继承,interface 使用 extends 实现继承, type 使用交叉类型实现继承
xxxxxxxxxx// interface 继承 interfaceinterface Person {name: string}interface Student extends Person {grade: number}// type 继承 typetype Person = {name: string}type Student = Person & { grade: number } // 用交叉类型
两者不同点:
interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。
type 可以声明基本类型、联合类型、交叉类型、元组,interface 不行
interface可以合并重复声明,type 不行
xxxxxxxxxxinterface Person {name: string}interface Person { // 重复声明 interface,就合并了age: number}const person: Person = {name: 'lin',age: 18}
使用 typeof 关键字判断变量的类型。之所以叫类型保护,就是为了能够在不同的分支条件中缩小范围,这样我们代码出错的几率就大大降低了。
如果有一个 getLength 函数,入参是联合类型 number | string,返回入参的 length,
xxxxxxxxxxfunction getLength(arg: number | string): number { return arg.length}这么写会报错,因为 number 类型上没有 length 属性。
我们把 getLength 方法改造一下,就可以精准地获取到 string 类型的 length 属性了,
xxxxxxxxxxfunction getLength(arg: number | string): number { if(typeof arg === 'string') { return arg.length } else { return arg.toString().length }}使用类型断言来告诉 TS,我(开发者)比你(编译器)更清楚这个参数是什么类型,你就别给我报错了
类型断言语法(tsx 语法):
xxxxxxxxxx值 as 类型xxxxxxxxxxfunction getLength(arg: number | string): number { const str = arg as string if (str.length) { return str.length } else { const number = arg as number return number.toString().length }}注意,类型断言不是类型转换,把一个类型断言成联合类型中不存在的类型会报错。
可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
泛型就出现了,它可以轻松解决输入输出要一致的问题。
泛型的语法是 <> 里写类型参数,一般可以用 T 来表示。
xxxxxxxxxxfunction print<T>(arg:T):T { console.log(arg) return arg}我们在函数名后添加了
<T>,其中T用来指代任意输入的类型,在后面的输入arg:T和输出T中即可使用了。
这样,我们就做到了输入和输出的类型统一,且可以输入输出任何类型。
泛型中的 T 就像一个占位符,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。
type 和 interface 都可以定义函数类型,也用泛型来写一下,type 这么写:
xxxxxxxxxxtype Print = <T>(arg: T) => Tconst printFn:Print = function print(arg) { console.log(arg) return arg}interface 这么写:
xxxxxxxxxxinterface Iprint<T> { (arg: T): T}function print<T>(arg:T) { console.log(arg) return arg}
const myPrint: Iprint<number> = print如果要给泛型加默认参数,可以这么写:
xxxxxxxxxxinterface Iprint<T = number> { (arg: T): T}
function print<T>(arg:T) { console.log(arg) return arg}
const myPrint: Iprint = print这样默认就是 number 类型了
假设现在有这么一个函数,打印传入参数的长度,我们这么写:
xxxxxxxxxxfunction printLength<T>(arg: T): T { console.log(arg.length) return arg}因为不确定 T 是否有 length 属性,会报错。
可以和 interface 结合,来约束类型。
xxxxxxxxxxinterface ILength { length: number}
function printLength<T extends ILength>(arg: T): T { console.log(arg.length) return arg}这其中的关键就是 <T extends ILength>,让这个泛型继承接口 ILength,这样就能约束泛型。
keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
xxxxxxxxxxinterface IPerson { name: string; age: number;}
type Test = keyof IPerson; // 'name' | 'age'上面的例子,Test 类型变成了一个字符串字面量。
T[K],表示接口 T 的属性 K 所代表的类型,
xxxxxxxxxxinterface IPerson { name: string; age: number;}
let type1: IPerson['name'] // stringlet type2: IPerson['age'] // numberT extends U,表示泛型变量可以通过继承某个类型,获得某些属性
xxxxxxxxxxinterface ILength { length: number}
function printLength<T extends ILength>(arg: T): T { console.log(arg.length) return arg}这样入参就一定要有 length 属性,比如 str、arr、obj 都可以, num 就不行。
xxxxxxxxxxconst str = printLength('lin')const arr = printLength([1,2,3])const obj = printLength({ length: 10 })
const num = printLength(10) // 报错
改造后,从对象中抽取一些属性的值,然后拼接成数组,可以这么写:
xxxxxxxxxxconst userInfo = { name: 'lin', age: '18',}
function getValues<T, K extends keyof T>(userInfo: T, keys: K[]): T[K][] { return keys.map(key => userInfo[key])}
Partial<T>将T的所有属性映射为可选的,使用 Partial 改造一下,就可以变成可选属性,
xxxxxxxxxxinterface IPerson { name: string age: number}
type IPartial = Partial<IPerson>
let p1: IPartial = {}
in操作符,用来对联合类型实现遍历
xxxxxxxxxxtype Person = "name" | "school" | "major"
type Obj = { [p in Person]: string}Partial 的实现用到了 in 和 keyof
xxxxxxxxxxtype Partial<T> = { [P in keyof T]?: T[P]}
[P in keyof T]遍历T上的所有属性
?:设置为属性为可选的
T[P]设置类型为原来的类型
Readonly<T>将T的所有属性映射为只读的,例如:
xxxxxxxxxxinterface IPerson { name: string age: number}
type IReadOnly = Readonly<IPerson>
let p1: IReadOnly = { name: 'lin', age: 18}和 Partial 原理几乎完全一样
xxxxxxxxxxtype Readonly<T> = { readonly [P in keyof T]: T[P]}[P in keyof T]遍历T上的所有属性
readonly设置为属性为可选的
T[P]设置类型为原来的类型
Pick用于抽取对象子集,挑选一组属性并组成一个新的类型
xxxxxxxxxxinterface IPerson { name: string age: number sex: string}
type IPick = Pick<IPerson, 'name' | 'age'>
let p1: IPick = { name: 'lin', age: 18}
这样就把 name 和 age 从 IPerson 中抽取出来。
上面三种映射类型官方称为同态,意思是只作用于 obj 属性而不会引入新的属性。
Record 是会创建新属性的非同态映射类型。
xxxxxxxxxxinterface IPerson { name: string}
type IRecord = Record<string, IPerson>
let personMap: IRecord = { person1: { name: 'lin' }, person2: { name: 'liu' } }T extends U ? X : Y
若类型 T 可被赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型
Exclude 意思是不包含,Exclude<T, U> 会返回 联合类型 T 中不包含 联合类型 U 的部分。
xxxxxxxxxxtype Test = Exclude<'a' | 'b' | 'c', 'a'>
Extract<T, U>提取联合类型 T 和联合类型 U 的所有交集。
xxxxxxxxxxtype Test = Extract<'key1' | 'key2', 'key1'>
Omit<T, U>从类型 T 中剔除 U 中的所有属性。
xxxxxxxxxxinterface IPerson { name: string age: number}
type IOmit = Omit<IPerson, 'age'>// type IOmit = {name: string;}Parameters 获取函数的参数类型,将每个参数类型放在一个元组中。
xxxxxxxxxxtype T1 = Parameters<() => string> // []
type T2 = Parameters<(arg: string) => void> // [string]
type T3 = Parameters<(arg1: string, arg2: number) => void> // [arg1: string, arg2: number]ReturnType 获取函数的返回值类型。
xxxxxxxxxxtype T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
当使用第三方库时,很多三方库不是用 TS 写的,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
比如,在 TS 中直接使用 Vue,就会报错,这时,我们可以使用 declare 关键字来定义 Vue 的类型
xxxxxxxxxxinterface VueOption { el: string, data: any}
declare class Vue { options: VueOption constructor(options: VueOption)}
const app = new Vue({ el: '#app', data: { message: 'Hello Vue!' }})这样就不会报错了,使用 declare 关键字,相当于告诉 TS 编译器,这个变量(Vue)的类型已经在其他地方定义了,你直接拿去用,别报错。
仅仅会用于编译时的检查,在编译结果中会被删除。
通常我们会把声明语句放到一个单独的文件(Vue.d.ts)中,这就是声明文件,以 .d.ts 为后缀。
// src/Vue.d.ts
xxxxxxxxxxinterface VueOption { el: string, data: any}
declare class Vue { options: VueOption constructor(options: VueOption)}// src/index.ts
xxxxxxxxxxconst app = new Vue({el: '#app',data: {message: 'Hello Vue!'}})
一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 Vue.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 Vue 的类型定义了。