前言
TypeScript 是由 Microsoft 开发的一种开放源代码语言。 它是 JavaScript 的一个超集,这意味着你可以在 TypeScript 中使用 JS 已存在的所有语法,并且所有 JavaScript 脚本都可以当作 TypeScript 脚本,此外它还增加了一些自己的语法。TypeScript 对 JavaScript 添加的最的核心功能,就是它的类型系统。
1. 类型系统
在 TypeScript 中,可以使用类型提示来识别变量或参数的数据类型。 使用类型提示,可以描述对象的形状,这样不仅可以提供更好的文档,还能使 TypeScript 能够更好的验证代码是否正常工作。
通过静态类型检查,TypeScript 在开发初期就能发现代码问题,而 JavaScript 往往只能够在浏览器中运行时才会发现。 类型还让你可以描述代码的用途, 如果你是在团队中工作,那么在多人协作开发时也能更简单、高效。
类型还可以为开发工具提供各种便捷和生产力优势,例如 IntelliSense(智能感知)、基于符号的导航、转到定义、查找所有引用、语句结束和代码重构。
2. 类型声明
基本类型声明
TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明。
let foo: string
上面示例中,变量 foo
的后面使用冒号,声明了它的类型为 string
。
类型声明的写法,一律为在标识符后面添加 “冒号 + 类型” 。函数参数和返回值,也是这样来声明类型,下面是 TypeScript 中常用的声明类型的方式:
let foo: stringlet age: numberlet bar: booleanlet b: nulllet c: undefinedlet d: bigintlet e: symbolfunction getVal(val: string): string {return val
}
同样的它们只能被赋值为该类型的值:
foo = '123'age = 123bar = trueb = nullc = undefinedd = 123456ne = Symbol()
object 类型
根据 Javascript 的设计,object
可以作为所有对象、数组和函数的类型:
let a: objecta = {}
a = []
a = () => {}
上面示例中,对象、数组、函数都属于 object
类型。虽然 TypeScript 这样的行为比较贴合 JavaScript 作为弱类型语言的表现形式,但是在实际开发应用中却很少使用 object
作为以上三者的类型声明。对象、数组、函数的类型都有更为具体、规范的定义方式:
interface Person {name: string
}type Fn = (args: any) => voidlet a: Personlet b: any[]let c: Fn
以上分别列举了最常见的用法,定义了对象、数组、函数的类型。而 interface
和 type
的强大之处绝不仅仅于此,在后续的篇幅中我们会详细讲解这两个类型。
undefined 和 null 类型
值得注意的是,undefined
和 null
是所有类型的子类型,在非严格模式下,undefined
和 null
这两种类型的变量可以赋值给其他类型,如下:
然而在严格模式下,上述的写法就会报错:
这里推荐开启 "strict": true
直接启用严格模式开发,可以更严格地检测类型错误,以减少错误的发生。
3. interface「接口」
interface “接口”,我们前端很熟悉的就是跟后端同学一起联调接口,但却不能和 TypeScript 中的 “接口” 混为一谈。
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
如何描述对象的形状
TypeScript 中的接口是一个非常灵活的概念,除了可用于 对类的一部分行为进行抽象 以外,也常用于描述对象的形状。
interface Person {name: stringage: number
}let p: Personp = {name: '',age: 18
}
上述的代码就是 interface
的一个基本用法。我们都知道,在 JavaScript 中对象是可以随意的增加、删除、修改属性的,这样具备极大的灵活性,却也可能带来一定的开发风险。
而 TypeScript 只关心我们赋值给变量的 “值的结构”,它只关心它是否具有预期的属性。 只关心类型的结构和功能,这就是为什么 TypeScript 常常被称之为是一个 结构化类型 的类型系统。当编写以下代码后,TypeScript 会及时给出报错:
p.age = ''
// TS2322: Type string is not assignable to type number p.gender = 'male'
// TS2339: Property gender does not exist on type Person delete p.name
// TS2790: The operand of a delete operator must be optional.
灵活的 interface
那么可能有的同学可能就有疑惑了,我就是要这样写代码,TypeScript 这么不灵活,老子 TM 不学了 😡😡😡。其实不然,通过下述写法在保留可维护性、安全性的同时还能具备一定的灵活性:
interface Person {name?: string // 可选属性age: number | string // 联合类型[p: string]: any; // 任意属性
}let p: Personp = {name: '',age: 18
}p.age = ''p.gender = 'male'delete p.name
我们可以在属性名后面加一个 ?
标记表示这个属性是可选的。这样在 初始化对象的时候 就可以根据需要选择是否添加某个属性,并且在后续操作中也可以 选择删除或增加 这个属性。
通过任意属性,我们可以很灵活的给一个对象扩展新的属性。在某些时候,对象属性值的类型可能会有多种,我们则可以用 联合类型 来定义。
4. type「类型别名」
我们已经学会在类型注解里直接使用对象类型和联合类型,这很方便,但有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。
这就是类型别名(type alias)。所谓类型别名,顾名思义,一个可以指代任意类型的名字。类型别名的语法:
type Name = string;type NameResolver = () => string;type NameOrResolver = Name | NameResolver;function getName(n: NameOrResolver): Name {if (typeof n === 'string') {return n;} else {return n();}
}
也能用来定义对象和联合类型:
type Age = numbertype Name = stringtype Person = {name: Nameage: Age
}type Foo = Age | Name
跟定义 interface 很类似,将上述的示例代码改改就行:
type Age = number | stringtype Person = {name?: string // 可选属性age: Age // 联合类型[p: string]: any; // 任意属性
}let p: Personp = {name: '',age: 18
}p.age = ''p.gender = 'male'delete p.name
以上的代码可以直接运行,也是没有任何问题的。
5. typeof
JavaScript 中有一个操作符 typeof
可以用来获取一个值的类型,但是我们可能某些时候不会使用它,因为它不能准确分辨出对象和数组。在 TypeScript 中有一个同样的类型操作符 typeof
,它可以用来获取一个值 TS 类型,代码如下:
const info = {name: 'John',
}const list = [1, 2, 3]type Info = typeof infotype List = typeof list
类型 Info
此时等同于 {name: string}
,List
等同于 number[]
,我们来试一试加在变量上的表现:
通过使用 typeof
操作符可以获取任意值的 TS 类型,当某个复杂的对象已经写好了而我们又不太想手写它的类型时,typeof
就变得尤为有用:
const person = {name: 'lison',age: 18,id: 1,school: '清华大学',address: '北京市',birthday: '2001-01-01',height: 170,weight: 120,hobby: ['sleep', 'eat', 'run'],score: {math: 100,chinese: 90,english: 80}
}function printPerson(p: typeof person) {console.log(p.name)
}
还有一个需要注意的点是,在 TypeScript 中,const
声明的变量具有字面量类型(literal types)。字面量类型是一种特殊的类型,用于表示一个固定的、不可变的值。这些类型包括字符串、数字、布尔值、对象、数组和元组等。当你使用 const
声明一个变量并给它赋一个具体的值时,TypeScript 会推断该变量的类型为字面量类型。例如:
const pi = 3.14159type PI = typeof pi // 3.14159
当类型 PI
被作为某个变量的类型时,它就只能被赋值为 3.14159
,否则就会报错:
6. 类型推导
如果没有明确的指定类型,那么 TypeScript 会依照类型推导(Type Inference)的规则推导出一个类型。
什么是类型推导
以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
事实上,它等价于:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript 会在没有明确的指定类型的时候会通过解析代码推测出一个类型,这就是类型推导。正因为有这样的功能,在日常开发中,对于一些简单的、不会更改的数据我们可以不用显式去定义类型。