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 User4.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 : anyinfer 关键字只能在条件类型中使用,通常用于函数返回值类型的推断。
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" }