TypeScript 语法概述
1. TypeScript 是什么
TypeScript 提供了 JavaScript 的所有功能,并在这些功能之上添加了一层:TypeScript 的类型系统。
TypeScript 的主要好处是,它可以检查代码中的意外行为,从而降低出现错误的机会。
TypeScript 可以识别 JavaScript 语言,在许多情况下可以推断类型。例如,在创建变量并将其赋值给特定值时,TypeScript 将使用该值作为其类型。
const helloWorld = 'Hello World'
TypeScript 能通过上下文得知变量类型的能力,例如:
const a: any = 3
if (typeof a === 'number') {
console.log(a.toFixed())
}
这一点也叫做 类型收缩、类型守卫 或者 窄化(Narrowing)。
declare const a: number | undefined
if (a !== undefined) {
console.log(a.toFixed())
}
TypeScript 的一个核心原则是类型检查基于对象的属性和行为(type checking focuses on the shape that values have)。这有时被叫做“鸭子类型”或“结构类型”(structural typing)。
2. 定义类型
interface
用于定义接口,接口用于描述对象的结构,即接口约束了对象应该具有什么成员,而不约束对象不包含什么成员。
因此,TypeScript 的 interface
实际上是鸭子类型的,即只要对象的结构和接口一致,那么这个对象就是这个接口的实例。这一点区别于 Java 的 interface
。
interface Person {
name: string
age: number
}
而 type
用于定义类型别名,可以用于定义任何类型,任何可用 interface
定义的类型都可以用 type
定义。
type Person = {
name: string
age: number
}
通常情况下,项目中使用 interface
就足够了,有时我们需要更复杂的类型定义,这时候就需要使用 type
。
interface Person {
name: string
age: number
say: (words: string) => void
}
type PersonWithoutSay = Omit<Person, 'say'>
一些内置类型的定义如下:
类型 | 描述 |
---|---|
any | 类型是任意类型,任意类型都可以看做 any 类型 |
unknown | 类型是类型安全的 any 类型 |
void | 类型是没有返回值的类型,或者返回值是 undefined 的类型 |
never | 类型是永远不会有值的类型 |
null | 类型是 null 或 undefined 的类型 |
undefined | 类型是 undefined 的类型 |
object | 类型是非原始类型的类型 |
boolean | 类型是布尔类型,只有 true 和 false |
number | 类型是数字类型,包括整数和浮点数和 NaN |
string | 类型是字符串类型 |
bigint | 类型是大整数类型 |
symbol | 类型是符号类型 |
3. 组合类型
在 TypeScript 中,类型可以通过简单的组合产生复杂类型。复杂的类型的产生方式:
- 通过 联合类型(Union)产生
- 通过 交叉类型(Intersect)产生
- 通过 元组类型(Tuple)产生
- 通过 可空类型(Nullable)产生
- 通过 泛型(Generic)产生
常量(Literal)、内置(Built-in)类型或用户定义类型通过上述方式组合可以产生更多新的类型。
3.1 联合类型
联合类型使用 | 组合,表示可以是多个中的任意一个。
type LockStates = 'locked' | 'unlocked'
3.2 交叉类型
如果要满足两种以上的类型,可以使用 &
来交叉两种类型。交叉类型表示这个类型满足被交叉的所有类型。
type A = B & C
那么 A
同时具有 B
和 C
的结构。
例如:
type A = 1 | 2 | 3
type B = 1 | 3 | 4
type C = A & B
此时 C
的类型为 1 | 3
,因为 1 | 3
同时满足 A
和 B
。
但是 interface
的行为就有点不同了:
interface I1 {
x: number
}
interface I2 {
y: number
}
type I3 = I1 & I2
const a: I3 = {
x: 1,
y: 2
}
为了同时满足 I1
和 I2
,I3
必须同时具有这两个接口的所有属性。
3.3 元组类型
元组类型描述一个集合对象的特征,例如 [string, number]
也是一种类型。元组类型还包含一种一个或多个的语法,使用 ...
来表示可变参数(不定参数),例如:[...args: number]
,这在函数中很常见。
type A = [string, number]
type B = [string, ...number[]]
3.4 可空类型
可空类型在类型后面加上 ?
,表示 t | undefined
的含义,注意不是 t | null
。
interface A {
a: number
b?: string
}
b
的类型是 t | undefined
。可空类型的设计和 C# 的可空类型(Nullable
类型)类似,可能的原因是 TypeScript 的作者就是 C# 的作者吧!这使得两种语言的设计异常巧妙地吻合。
3.5 泛型
泛型编程是普遍的程序设计模式。泛型是类型的模板(也就是泛化的类型,一个类型可以表示很多类型),通过泛型可以随时创建灵活的类型。同样,TypeScript 的泛型和 C# 的泛型类似。
泛型通常可以理解为一个类型模板,即新的类型是通过这个模板复制出来的(实际上的行为和语言有关),<>
的内容为模板的参数,给定参数即产出一个新的类型。
在 TypeScript 中,参数可以有默认值,这意味着 <>
也不是必须的。
interface A<T> {
x: number | T
y: number
}
interface B<T = string> {
x: number | T
y: number
}
type A_string = A<string>
type B_string = B
一些例子:
number[]
数字型的数组(number | string)[]
数字或字符串数组(a: string, ...args: number) => string
以一个字符串和多个数字为参数的函数,返回字符串Promise<{ data: User[], message: string }>
一个Promise
对象A<B<string>>
嵌套的泛型类型
4. 常见工具
4.1 内置工具类型
类型 | 描述 | 发布版本 |
---|---|---|
Awaited<T> | 获取 Promise 的返回值 | 4.5 |
Partial<T> | 将类型 T 的所有属性设置为可选 | 2.1 |
Required<T> | 将类型 T 的所有属性设置为必选 | 2.8 |
Readonly<T> | 将类型 T 的所有属性设置为只读 | 2.1 |
Record<K, T> | 由 K 指定属性并由 T 指定类型的对象类型 | 2.1 |
Pick<T, K> | 从 T 中选择属性 K 的类型 | 2.1 |
Omit<T, K> | 从 T 中排除属性 K 的类型 | 3.5 |
Exclude<UnionType, ExcludedUnion> | 从 UnionType 中排除 ExcludedUnion | 2.8 |
Extract<UnionType, IncludedUnion> | 从 UnionType 中提取 IncludedUnion | 2.8 |
NonNullable<T> | 从 T 中排除 null 和 undefined | 2.8 |
Parameters<T> | 获取函数类型 T 的参数类型 | 3.1 |
ConstructorParameters<T> | 获取构造函数类型 T 的参数类型 | 3.1 |
ReturnType<T> | 获取函数类型 T 的返回值类型 | 2.8 |
InstanceType<T> | 获取构造函数类型 T 的实例类型 | 3.1 |
ThisParameterType<T> | 获取函数类型 T 的 this 类型 | 3.3 |
OmitThisParameter<T> | 从函数类型 T 中排除 this 类型 | 3.3 |
ThisType<T> | 用于指定 this 类型 | 2.3 |
常见的类型等价表示
/**
* 使 T 中的所有属性变为可选
*/
type Partial<T> = {
[P in keyof T]?: T[P]
}
/**
* 使 T 中的所有属性变为必选
*/
type Required<T> = {
[P in keyof T]-?: T[P]
}
/**
* 使 T 中的所有属性变为只读
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
/**
* 从 T 中选取一组属性,其键在联合类型 K 中
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
/**
* 构造一个具有 K 中所有属性且类型为 T 的类型
*/
type Record<K extends keyof any, T> = {
[P in K]: T
}
/**
* 从 T 中排除那些可赋值给 U 的类型
*/
type Exclude<T, U> = T extends U ? never : T
/**
* 从 T 中提取那些可赋值给 U 的类型
*/
type Extract<T, U> = T extends U ? T : never
/**
* 构造一个包含 T 中除了 K 类型的属性的类型
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
/**
* 从 T 中排除 null 和 undefined
*/
type NonNullable<T> = T & {}
/**
* 获取函数类型的参数类型组成的元组
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
/**
* 获取构造函数类型的参数类型组成的元组
*/
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never
/**
* 获取函数类型的返回类型
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
/**
* 获取构造函数类型的返回类型
*/
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any
另外由几个工具是用于字符串的:
Uppercase<StringType>
Lowercase<StringType>
Capitalize<StringType>
Uncapitalize<StringType>
这些工具难以使用 type
来定义,因而在 TypeScript 中是内置的,使用编译时转换来实现的。
type A = Uppercase<'hello'>
4.2 类型断言
类型断言是一种告诉编译器某个值的类型的方法,有几种比较常见的形式:
- 泛型语法:
<Type>value
- 别名语法:
as
,如value as Type
,通常用于类型强制转换 - 非空断言操作符:
var!
,用于告诉编译器某个值不会是null
或undefined
- 使用
satisfies
关键字约束的类型
interface User {
name: string
age: number
}
const user = <User>{
name: 'Alice',
age: 30
}
const a: any = 3
const b = (a as number).toFixed()
as
总是将指定对象视为另一种类型的对象,并且这两种类型是可转换的。如果类型无法转换的类型将报错。
- 可空类型和对应的原类型
a | null
转换为a
any
的其他任意类型,unknown
和其他任意类型
请参考 高级:TypeScript 中的类型关系控制 来了解哪些类型可以转换。如果明显不能转换的类型,需要强制转换时,可以借助 any
或 unknown
类型,连续两次使用 as
即可。
// 将 a 视为 string
const a = (3 as unknown) as string
satisfies
与 as
的功能类似。不同的是,它用于在确保某些表达式与某种类型匹配的同时,保留该表达式的最特定的类型以进行推理。例如:
type User = {
name: string
age: number | string
}
const a = {
name: 'Alice',
age: 30
} satisfies User
4.3 自定义类型守卫
通常使用 typeof
、instanceof
、in
、is
等操作符来定义自定义类型守卫,以帮助 TypeScript 更好地收紧类型。这些类型守卫本质上也是一种类型断言。
function isString(value: string | number): value is string {
return typeof value === 'string'
}
function example(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase())
} else {
console.log(value.toFixed())
}
}
在项目中合理地使用类型守卫和自定义守卫,可以帮助我们减少很多不必要的类型断言,同时改善代码的可读性。
还有一些技巧可以收窄类型,例如使用 as const
:
const obj1 = [{ name: 'Alice' }, { name: 'Bob' }]
type Obj1 = typeof obj1
const obj2 = [{ name: 'Alice' }, { name: 'Bob' }] as const
type Obj2 = typeof obj2
as const
可以将对象的属性变为只读(相当于加上 readonly
修饰),这样就可以更好地收窄类型。下面一节将介绍更多的修饰符。
4.4 常见修饰符
在 TypeScript 完全兼容并扩展 ES6 语法,修饰符是一种用于限制类、接口、属性和方法的访问权限的关键字。常见的修饰符有:
public
:默认修饰符,表示公共的,可以在任何地方访问。private
:表示私有的,只能在类内部访问。protected
:表示受保护的,只能在类内部和子类中访问。readonly
:表示只读的,只能在声明时或构造函数中赋值。static
:表示静态的,可以直接通过类名访问。abstract
:表示抽象的,只能用于抽象类和抽象方法。
abstract class Animal {
public name: string
private age: number
protected weight: number
readonly type: string
static count = 0
constructor(name: string, age: number, weight: number, type: string) {
this.name = name
this.age = age
this.weight = weight
this.type = type
Animal.count++
}
static getCount() {
return Animal.count
}
abstract say(): void
}
5. 高级特征
5.1 条件类型(Conditional Types)
- 定义:根据条件判断选择不同的类型,基于
extends
关键字和三目运算符。 - 语法:
T extends U ? X : Y
type IsString<T> = T extends string ? true : false
type A = IsString<"hello"> // true
type B = IsString<42> // false
// 内置工具类型示例:Exclude<T, U>
type C = Exclude<"a" | "b" | "c", "a">
使用 infer
关键字可以推断类型变量,相当于提供条件下类型的占位符:
// 推断函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
infer
关键字只能在条件类型中使用,通常用于函数返回值类型的推断。
type UnpackPromise<T> = T extends Promise<infer U> ? U : T
type Unpacked = UnpackPromise<Promise<string>>
5.2 映射类型(Mapped Types)
- 定义:遍历现有类型的属性,生成新类型,通常结合
keyof
和in
操作符。 - 语法:
{ [K in Keys]: Type }
interface Person {
name: string
age: number
}
// 将所有属性转换为 boolean 类型
type Flags<T> = { [K in keyof T]: boolean }
type PersonFlags = Flags<Person>
// 将对象所有属性变为可选
type Partial<T> = {
[K in keyof T]?: T[K]
}
type PartialPerson = Partial<Person>
可以通过添加修饰符来改变属性的特性,例如:
// 将所有属性变为只读
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
// 将所有属性变为可选和只读
type PartialReadonly<T> = {
readonly [K in keyof T]?: T[K]
}
// 将所有属性变为必选
type Required<T> = {
[K in keyof T]-?: T[K]
}
// 将所有属性变为可变
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
使用 as
关键字可以重新映射属性名的类型:
// 给每个选中的属性添加后缀 "_list",然后设置为数组类型
type MappedList<T, K extends keyof T & string> = {
[P in K as `${P}_list`]: T[P][]
}
interface Person {
name: string
age: number
height: number
}
type PrefixedPerson = MappedList<Person, 'name' | 'age'>
例如,选择仅由 _id
结尾的属性:
type GetIdKeys<T> = {
[K in keyof T as K extends `${string}_id` ? K : never]: T[K]
}
interface User {
id: number
name: string
age: number
created_at: Date
role_id: number
tag_id: number
}
type IdKeys = GetIdKeys<User>
上述例子中,使用 never
类型可以过滤掉不符合条件的属性。
如果在映射值上使用 never
类型,则将构造不允许某些属性的一种类型:
type GetIdKeys<T> = {
[K in keyof T]: K extends `${string}_id` ? T[K] : never
}
type IdKeys = GetIdKeys<User>
const user: IdKeys = {
role_id: 1,
tag_id: 2,
created_at: new Date()}
5.3 索引类型(Index Types)
- 定义:通过
keyof
和索引访问符T[K]
动态操作对象类型- 。
interface Person {
name: string
age: number
}
// 获取 Person 的键的联合类型
type Keys = keyof Person
// 获取某个键对应的类型
type NameType = Person["name"]
// 泛型函数示例:获取对象属性值
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
5.4 可辨识联合类型(Discriminated Unions)
- 定义:通过公共的“标签字段”(如
kind
)区分联合类型中的不同成员- 。
interface Circle {
kind: "circle"
radius: number
}
interface Square {
kind: "square"
sideLength: number
}
type Shape = Circle | Square
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2
case "square": return shape.sideLength ** 2
}
}
5.5 枚举类型(Enums)
- 定义:定义一组命名的常量值,分为数字枚举和字符串枚举。
- 语法:
enum EnumName { ... }
// 数字枚举
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
// 使用示例
const color: Color = Color.Red
// 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN"
}
// 使用示例
const dir: Direction = Direction.Up
5.6 Symbol 类型(Symbol Types)
- 定义:通过
symbol
或unique symbol
声明唯一的标识符类型。
// 普通 symbol
const key1 = Symbol("key")
type KeyType = typeof key1 // symbol
// unique symbol(仅允许声明时初始化)
const key2: unique symbol = Symbol("key")
interface Obj {
[key2]: string // 使用 unique symbol 作为键
}
// 示例对象
const obj: Obj = { [key2]: "secret" }