TypeScript 是由 Microsoft 开发的一种开放源代码语言。 它是 JavaScript 的一个超集,这意味着你可以在 TypeScript 中使用 JS 已存在的所有语法,并且所有 JavaScript 脚本都可以当作 TypeScript 脚本,此外它还增加了一些自己的语法。TypeScript 对 JavaScript 添加的最的核心功能,就是它的类型系统。
在 TypeScript 中,可以使用类型提示来识别变量或参数的数据类型。 使用类型提示,可以描述对象的形状,这样不仅可以提供更好的文档,还能使 TypeScript 能够更好的验证代码是否正常工作。
通过静态类型检查,TypeScript 在开发初期就能发现代码问题,而 JavaScript 往往只能够在浏览器中运行时才会发现。 类型还让你可以描述代码的用途, 如果你是在团队中工作,那么在多人协作开发时也能更简单、高效。
类型还可以为开发工具提供各种便捷和生产力优势,例如 IntelliSense(智能感知)、基于符号的导航、转到定义、查找所有引用、语句结束和代码重构。
TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明。
let foo: string
上面示例中,变量 foo
的后面使用冒号,声明了它的类型为 string
。
类型声明的写法,一律为在标识符后面添加 “冒号 + 类型” 。函数参数和返回值,也是这样来声明类型,下面是 TypeScript 中常用的声明类型的方式:
let foo: string
let age: number
let bar: boolean
let b: null
let c: undefined
let d: bigint
let e: symbol
function getVal(val: string): string {
return val
}
同样的它们只能被赋值为该类型的值:
foo = '123'
age = 123
bar = true
b = null
c = undefined
d = 123456n
e = Symbol()
根据 Javascript 的设计,object
可以作为所有对象、数组和函数的类型:
let a: object
a = {}
a = []
a = () => {}
上面示例中,对象、数组、函数都属于 object
类型。虽然 TypeScript 这样的行为比较贴合 JavaScript 作为弱类型语言的表现形式,但是在实际开发应用中却很少使用 object
作为以上三者的类型声明。对象、数组、函数的类型都有更为具体、规范的定义方式:
interface Person {
name: string
}
type Fn = (args: any) => void
let a: Person
let b: any[]
let c: Fn
以上分别列举了最常见的用法,定义了对象、数组、函数的类型。而 interface
和 type
的强大之处绝不仅仅于此,在后续的篇幅中我们会详细讲解这两个类型。
值得注意的是,undefined
和 null
是所有类型的子类型,在非严格模式下,undefined
和 null
这两种类型的变量可以赋值给其他类型,如下:
然而在严格模式下,上述的写法就会报错:
这里推荐开启 "strict": true
直接启用严格模式开发,可以更严格地检测类型错误,以减少错误的发生。
interface “接口”,我们前端很熟悉的就是跟后端同学一起联调接口,但却不能和 TypeScript 中的 “接口” 混为一谈。
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于 对类的一部分行为进行抽象 以外,也常用于描述对象的形状。
interface Person {
name: string
age: number
}
let p: Person
p = {
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.
那么可能有的同学可能就有疑惑了,我就是要这样写代码,TypeScript 这么不灵活,老子 TM 不学了 。其实不然,通过下述写法在保留可维护性、安全性的同时还能具备一定的灵活性:
interface Person {
name?: string // 可选属性
age: number | string // 联合类型
[p: string]: any; // 任意属性
}
let p: Person
p = {
name: '',
age: 18
}
p.age = ''
p.gender = 'male'
delete p.name
我们可以在属性名后面加一个 ?
标记表示这个属性是可选的。这样在 初始化对象的时候 就可以根据需要选择是否添加某个属性,并且在后续操作中也可以 选择删除或增加 这个属性。
通过任意属性,我们可以很灵活的给一个对象扩展新的属性。在某些时候,对象属性值的类型可能会有多种,我们则可以用 联合类型 来定义。
我们已经学会在类型注解里直接使用对象类型和联合类型,这很方便,但有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。
这就是类型别名(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 = number
type Name = string
type Person = {
name: Name
age: Age
}
type Foo = Age | Name
跟定义 interface 很类似,将上述的示例代码改改就行:
type Age = number | string
type Person = {
name?: string // 可选属性
age: Age // 联合类型
[p: string]: any; // 任意属性
}
let p: Person
p = {
name: '',
age: 18
}
p.age = ''
p.gender = 'male'
delete p.name
以上的代码可以直接运行,也是没有任何问题的。
JavaScript 中有一个操作符 typeof
可以用来获取一个值的类型,但是我们可能某些时候不会使用它,因为它不能准确分辨出对象和数组。在 TypeScript 中有一个同样的类型操作符 typeof
,它可以用来获取一个值 TS 类型,代码如下:
const info = {
name: 'John',
}
const list = [1, 2, 3]
type Info = typeof info
type 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.14159
type PI = typeof pi // 3.14159
当类型 PI
被作为某个变量的类型时,它就只能被赋值为 3.14159
,否则就会报错:
如果没有明确的指定类型,那么 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 会在没有明确的指定类型的时候会通过解析代码推测出一个类型,这就是类型推导。正因为有这样的功能,在日常开发中,对于一些简单的、不会更改的数据我们可以不用显式去定义类型。