TypeScript

环境搭建

  • yarn create vite 选择 vanilla-ts

ts 类型声明空间与变量声明空间

  • ts 中的类型声明空间与变量声明空间是分开的,这是因为 ts 是静态类型语言,所以在编译阶段就需要知道变量的类型,而不是在运行时才知道变量的类型。
// 变量声明空间
let a: number = 1
a = '1' // error

// 类型声明空间
type A = number
let a: A = 1
a = '1' // error

// 类在ts 中既是变量声明空间,也是类型声明空间
class Foo {}
type A = number
let a = Foo

类型注解与类型推断

  • 类型注解:我们来告诉 ts 变量是什么类型
// 类型注解: Number类型
let a: number
a = 1
a = '1' // error
  • 类型推断:ts 会自动的去尝试分析变量的类型
// ts 自动推断 a 的类型为 number
let a = 1
a = '1' // error

类型别名

  • 类型别名用来给一个类型起个新名字
  • 基本类型:stringnumberbooleansymbolundefined, nullbigint,可以直接作为类型别名
  • 对象类型:[]{}classfunction,可以直接作为类型别名
  • TS新增类型: neveranyunknownvoidenum, 可以直接作为类型别名
// 类型别名 字符串
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 EventNames = 'click' | 'scroll' | 'mousemove'

function handleEvent(ele: Element, event: EventNames) {
  // do something
}

handleEvent(document.getElementById('hello'), 'scroll') // 没问题
handleEvent(document.getElementById('world'), 'dbclick') // 报错,event 不能为 'dbclick'
  • 交叉类型
interface DogInterface {
  run(): void
}

interface CatInterface {
  jump(): void
}

let pet: DogInterface & CatInterface = {
  run() {},
  jump() {},
}

never 类型 any 类型 unknown 类型

  • never 类型表示的是那些永不存在的值的类型
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message)
}
// 返回never
function fail(): never {
  return error('Something failed')
}
// 返回never
function infiniteLoop(): never {
  while (true) {}
}
  • any 类型表示的是允许赋值为任意类型
let a: any = 1
a = '1'
a = true
  • unknown 类型表示的是未知类型的值,但是在使用之前需要进行类型判断
let a: unknown = 1
a = '1'
a = true
let b = a // error
// 使用前进行类型判断
if (typeof a === 'string') {
  let c = a // ok
}

数组与元组

  • 数组(Array)合并了相同类型的对象
// 数组
let fibonacci: number[] = [1, 1, 2, 3, 5]
console.log(fibonacci[0]) // 1

// 数组泛型 另外一种声明方式
let fibonacci: Array<number> = [1, 1, 2, 3, 5]
console.log(fibonacci[0]) // 1
  • 元组(Tuple)合并了不同类型的对象
let tom: [string, number] = ['Tom', 25]
console.log(tom[0]) // Tom

枚举与 const 枚举

  • 枚举(Enum)类型用于取值被限定在一定范围内的场景
enum Days {
  Sun,
  Mon,
  Tue,
  Wed,
  Thu,
  Fri,
  Sat,
}

console.log(Days['Sun'] === 0) // true
console.log(Days['Mon'] === 1) // true
console.log(Days['Tue'] === 2) // true
console.log(Days['Sat'] === 6) // true

// 双向映射
console.log(Days[0] === 'Sun') // true
console.log(Days[1] === 'Mon') // true
console.log(Days[2] === 'Tue') // true
console.log(Days[6] === 'Sat') // true
  • const 枚举
// const 枚举
const enum Directions {
  Up,
  Down,
  Left,
  Right,
}

let directions = [
  Directions.Up,
  Directions.Down,
  Directions.Left,
  Directions.Right,
]

console.log(directions) // [0, 1, 2, 3]

// const 枚举 字符串
const enum Directions {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

let directions = [
  Directions.Up,
  Directions.Down,
  Directions.Left,
  Directions.Right,
]

console.log(directions) // ["UP", "DOWN", "LEFT", "RIGHT"]

对象类型与索引签名

  • 对象类型
// 对象类型
interface Person {
  name: string
  age: number
}
let tom: Person = {
  name: 'Tom',
  age: 25,
}
  • 索引签名: 索引签名描述了对象索引的类型,有两种索引签名:字符串和数字
interface Person {
  name: string
  age: number
  // 索引签名: 可以添加额外的属性
  [propName: string]: any
}

let tom: Person = {
  name: 'Tom',
  age: 25,
  gender: 'female',
}

类型断言与非空断言

  • 类型断言: 用来手动指定一个值的类型, 语法为:<类型>值值 as 类型
let someValue: any = 'this is a string'
// as 告诉编译器,我知道这个值是 string 类型
let strLength: number = (someValue as string).length
  • 非空断言: 用来告诉编译器变量不会是 null 或 undefined, 语法为:变量名!
function getLength(something: string | null): number {
  if (something === null) {
    return 0
  } else {
    // 非空断言
    return something!.length
  }
}

函数类型与 void 类型

  • 函数类型
// 声明函数类型
interface SearchFunc {
  (source: string, subString: string): boolean
}
// 函数类型变量
let mySearch: SearchFunc = function (source: string, subString: string) {
  return source.search(subString) !== -1
}
// 调用函数
mySearch('hello', 'll') // true
  • void 类型: 用来表示没有任何返回值的函数
function alertName(): void {
  alert('My name is Tom')
}

函数重载与可调用注解

  • 函数重载: 为同一个函数提供多个函数类型定义来进行函数重载
function reverse(x: number): number
function reverse(x: string): string

function reverse(x: number | string): number | string {
  if (typeof x === 'number') {
    // 数字
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    // 字符串
    return x.split('').reverse().join('')
  }
}
  • 可调用注解: 用来约束函数的类型, 对函数重载有很好的支持
interface SearchFunc {
  (source: string, subString: string): boolean
  (source: number, subString: number): boolean
}
// 函数类型变量,SearchFunc 为可调用注解,来约束函数的类型
let mySearch: SearchFunc = function (source: string, subString: string) {
  if (typeof source === 'string' && typeof subString === 'string') {
    return source.search(subString) !== -1
  } else if (typeof source === 'number' && typeof subString === 'number') {
    return source.toString().search(subString.toString()) !== -1
  }
}
// 调用函数
mySearch('hello', 'll') // true
mySearch(123456, 456) // true

接口与类型别名区别

  • 区别

    1. 类型别名可以直接赋值给一个变量, 接口只能赋值对象类型。
    // 类型别名可以直接赋值给一个变量或者对象
    type Name = string
    let tom: Name = 'Tom'
    
    // 接口只能是对象
    interface Person {
      name: string
    }
    let tom: Person = {
      name: 'Tom',
    }
    
    1. 类型别名不能被 extendsimplements, 而接口可以
    // 类型别名不能被 extends 和 implements
    type Person = {
      name: string
    }
    interface Teacher extends Person {} // error
    
    // 接口可以被 extends 和 implements
    interface Person {
      name: string
    }
    interface Teacher extends Person {}
    class User implements Person {}
    
    1. 类型别名不能合并, 而接口可以合并
    // 类型别名不能合并
    type Person = {
      name: string
    }
    type Person = {
      age: number
    } // error
    
    // 接口可以合并
    interface Person {
      name: string
    }
    interface Person {
      age: number
    }
    
    1. 类型别名映射类型写法, 而接口不行
    // 类型别名映射类型写法
    type Person = {
      name: string
      age: number
    }
    // 通过映射类型,将 Person 的所有属性变为只读
    type ReadonlyPerson = {
      readonly [P in keyof Person]: Person[P]
    }
    
    // 接口不行
    interface Person {
      name: string
      age: number
    }
    interface ReadonlyPerson {
      readonly [P in keyof Person]: Person[P]
    } // error
    
  • 接口

interface Person {
  name: string
  age: number
}

let tom: Person = {
  name: 'Tom',
  age: 25,
}
  • 类型别名
type Person = {
  name: string
  age: number
}

let tom: Person = {
  name: 'Tom',
  age: 25,
}

字面量类型和 keyof 关键字

  • keyof 关键字
interface Person {
  name: string
  age: number
}
// keyof Person 表示取出 Person 的所有属性名组成的联合类型
type PersonKeys = keyof Person // 'name' | 'age'
  • 字面量类型: 用来约束变量的取值只能是某几个值中的一个
type Directions = 'Up' | 'Down' | 'Left' | 'Right'

function move(direction: Directions) {
  // ...
}

move('Up') // ok
move('Left') // ok
move('A') // error

泛型和泛型常见操作

  • 泛型: 一种类型变量,用来表示类型,而不是具体的值
// 泛型函数
function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
// 输出
createArray<string>(3, 'x') // ['x', 'x', 'x']
createArray<number>(3, 1) // [1, 1, 1]
createArray<boolean>(3, true) // [true, true, true]
  • 泛型常见操作
// 泛型约束
interface Lengthwise {
  length: number
}
// 泛型函数
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length) // Now we know it has a .length property, so no more error
  return arg
}
// 错误调用
loggingIdentity(3) // error
// 正确调用
loggingIdentity({ length: 10, value: 3 }) // ok

// 泛型接口
interface CreateArrayFunc {
  <T>(length: number, value: T): Array<T>
}
// 泛型函数,约束泛型 T
let createArray: CreateArrayFunc = function <T>(
  length: number,
  value: T
): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
// 调用
createArray<string>(3, 'x') // ['x', 'x', 'x']
createArray<number>(3, 1) // [1, 1, 1]
createArray<boolean>(3, true) // [true, true, true]

// 泛型类
class GenericNumber<T> {
  zeroValue: T
  add: (x: T, y: T) => T
}

let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function (x, y) {
  return x + y
}
// 输出 add 方法的返回值 数字
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 1)) // 1

let stringNumeric = new GenericNumber<string>()

stringNumeric.zeroValue = ''
stringNumeric.add = function (x, y) {
  return x + y
}
// 输出 add 方法的返回值 字符串
console.log(stringNumeric.add(stringNumeric.zeroValue, 'test')) // test

类型兼容性

  • 类型兼容性: 一个类型 X 可以被赋值给另一个类型 Y,那么我们就可以说类型 X 兼容类型 Y
interface Named {
  name: string
}
class Person {
  name: string
}
let p: Named
p = new Person() // ok

let x: Named
let y = { name: 'Alice', location: 'Seattle' }
x = y // ok

类型保护

  • 类型保护: 用来确保变量在某个作用域内的类型,可以在此作用域内放心的引用此类型的属性,或者调用此类型的方法
// typeof 类型保护
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value
  }
  if (typeof padding === 'string') {
    return padding + value
  }
  throw new Error(`Expected string or number, got '${padding}'.`)
}

// instanceof 类型保护
interface Padder {
  getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(' ')
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value
  }
}

function getRandomPadder() {
  return Math.random() < 0.5
    ? new SpaceRepeatingPadder(4)
    : new StringPadder('  ')
}

// 类型为 'SpaceRepeatingPadder' | 'StringPadder'
let padder: Padder = getRandomPadder()

if (padder instanceof SpaceRepeatingPadder) {
  padder // 类型细化为 'SpaceRepeatingPadder'
}

if (padder instanceof StringPadder) {
  padder // 类型细化为 'StringPadder'
}

// in 类型保护
interface Xiaoming {
  usename: string
}

interface Xiaohong {
  age: number
}

function getSmallPet(n: Xiaoming | Xiaohong): string | number {
  if ('usename' in n) {
    return n.usename
  }
  if ('age' in n) {
    return n.age
  }
}

// 调用
getSmallPet({ usename: 'xiaoming' }) // xiaoming

// null 类型保护
function f(sn: string | null): string {
  if (sn == null) {
    return 'default'
  } else {
    return sn
  }
}

// 可辨识联合
interface Square {
  kind: 'square'
  size: number
}
interface Rectangle {
  kind: 'rectangle'
  width: number
  height: number
}

interface Circle {
  kind: 'circle'
  radius: number
}

type Shape = Square | Rectangle | Circle
//  通过类型保护,可以访问联合类型的所有类型的属性
function area(s: Shape) {
  switch (s.kind) {
    case 'square':
      return s.size * s.size
    case 'rectangle':
      return s.height * s.width
    case 'circle':
      return Math.PI * s.radius ** 2
  }
}

// 用户自定义的类型保护
interface Bird {
  fly(): void
  layEggs(): void
}

interface Fish {
  swim(): void
  layEggs(): void
}

function getSmallPet(): Fish | Bird {
  // ...
}

let pet = getSmallPet()
//  as 类型断言
if ((pet as Fish).swim) {
  ;(pet as Fish).swim()
} else if ((pet as Bird).fly) {
  ;(pet as Bird).fly()
}

映射类型和内置工具类型

  • 映射类型
// 映射类型

type A = {
  name: string
  age: number
}
console.log('A', A) // { name: string; age: number; }
type B = {
  readonly [K in keyof A]: number
}
console.log('B', B) // { readonly name: number; readonly age: number; }

type C = {
  -readonly [K in keyof A]: number
}
console.log('C', C) // { name: number; age: number; }
  • 内置工具类型
// Readonly<T>: 将所有属性设置为只读
interface Todo {
  title: string
}

const todo: Readonly<Todo> = {
  title: 'Delete inactive users',
}
// todo.title 自读属性,不能修改
todo.title = 'Hello' // error

// Partial<T>: 将所有属性设置为可选
interface Todo {
  title: string
  description: string
}
console.log('Todo', Todo) // { title: string; description: string; }
console.log('Partial<Todo>', Partial<Todo>) // { title?: string | undefined; description?: string | undefined; }
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate }
}

const todo1 = {
  title: 'organize desk',
  description: 'clear clutter',
}
const todo2 = updateTodo(todo1, {
  description: 'throw out trash',
})

// Record<K, T>: 用于将类型K中的每个属性的值转换为类型T
interface PageInfo {
  title: string
}

type Page = 'home' | 'about' | 'contact'

const x: Record<Page, PageInfo> = {
  about: { title: 'about' },
  contact: { title: 'contact' },
  home: { title: 'home' },
}
console.log('Record<Page, PageInfo>', Record<Page, PageInfo>) // { about: PageInfo; contact: PageInfo; home: PageInfo; }

// Pick<T, K>: 从T中选择一系列属性K

interface Todo {
  title: string
  description: string
  completed: boolean
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>
console.log('TodoPreview', TodoPreview) // { title: string; completed: boolean; }
const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
}

// Omit<T, K>: 从T中删除一系列属性K

type TodoPreview = Omit<Todo, 'description'>
const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
}
console.log('TodoPreview', TodoPreview) // { title: string; completed: boolean; }

// Exclude<T, U>: 从T中剔除可以赋值给U的类型

type T0 = Exclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // "c"
type T2 = Exclude<string | number | (() => void), Function> // string | number

// Extract<T, U>: 提取 T 中可以赋值给 U 的类型
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // "a"
type T1 = Extract<string | number | (() => void), Function> // () => void

// NonNullable<T>: 从T中剔除null和undefined
type T0 = NonNullable<string | number | undefined> // string | number
type T1 = NonNullable<string[] | null | undefined> // string[]

// ReturnType<T>: 获取函数返回值类型
declare function f1(): { a: number; b: string }
type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
type T2 = ReturnType<<T>() => T> // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T4 = ReturnType<typeof f1> // { a: number, b: string }
type T5 = ReturnType<any> // any
type T6 = ReturnType<never> // any
type T7 = ReturnType<string> // Error
type T8 = ReturnType<Function> // Error

// InstanceType<T>: 获取构造函数类型的实例类型
class C {
  x = 0
  y = 0
}
type T0 = InstanceType<typeof C> // C
type T1 = InstanceType<any> // any
type T2 = InstanceType<never> // any
type T3 = InstanceType<string> // Error
type T4 = InstanceType<Function> // Error

// Required<T>: 将所有属性设置为必选
interface Props {
  a?: number
  b?: string
}
const obj: Props = { a: 5 } // OK
const obj2: Required<Props> = { a: 5 } // Error: property 'b' missing

// ThisParameterType<T>: 获取函数类型的 this 参数类型
interface Options {
  method: 'GET' | 'POST'
  data?: string
}

function makeRequest(url: string, options: Options) {
  /* ... */
}

function makeRequest(
  url: string,
  options: Options & ThisParameterType<typeof makeRequest>
) {
  /* ... */
}

makeRequest('/api', {
  method: 'GET',
  data: 'hello',
  onSuccess(data) {
    this // 类型为 Options & { onSuccess(data: string): void }
  },
  onError(error) {
    this // 类型为 Options & { onError(error: Error): void }
  },
})

// OmitThisParameter<T>: 从函数类型中移除 this 参数

interface Options {
  method: 'GET' | 'POST'
  data?: string
}

function makeRequest(url: string, options: Options) {
  /* ... */
}

function makeRequest(url: string, options: OmitThisParameter<Options>) {
  /* ... */
}

makeRequest('/api', {
  method: 'GET',
  data: 'hello',
  onSuccess(data) {
    this // 类型为 Options
  },
  onError(error) {
    this // 类型为 Options
  },
})

// Parameters<T>: 获取函数类型的参数类型
function f1(a: number, b: string) {
  return b
}

type T0 = Parameters<() => string> // []
type T1 = Parameters<(s: string) => void> // [s: string]
type T2 = Parameters<<T>(arg: T) => T> // [arg: unknown]
type T3 = Parameters<typeof f1> // [a: number, b: string]
type T4 = Parameters<any> // unknown[]
type T5 = Parameters<never> // never[]

// ConstructorParameters<T>: 获取构造函数类型的参数类型
class C {
  x = 0
  y = 0
}

type T0 = ConstructorParameters<ErrorConstructor> // [message?: string]
type T1 = ConstructorParameters<FunctionConstructor> // string[]
type T2 = ConstructorParameters<RegExpConstructor> // [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<typeof C> // []
type T4 = ConstructorParameters<any> // unknown[]
type T5 = ConstructorParameters<never> // never[]

条件类型 和 infer 关键字

  • 条件类型: 初始状态并不直接确定具体类型,而是根据条件判断后确定具体类型
// T extends U ? X : Y
type TypeName<T> = T extends string
  ? 'string'
  : T extends number
  ? 'number'
  : T extends boolean
  ? 'boolean'
  : T extends undefined
  ? 'undefined'
  : T extends Function
  ? 'function'
  : 'object'

type T0 = TypeName<string> // "string"
type T1 = TypeName<'a'> // "string"
type T2 = TypeName<() => void> // "function"

// 分布式条件类型
type T10 = TypeName<string | (() => void)> // "string" | "function"
type T11 = TypeName<string | string[] | undefined> // "string" | "object" | "undefined"
type T12 = TypeName<string[] | number[]> // "object"

// 条件类型的分布式特性
type Diff<T, U> = T extends U ? never : T // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never // Remove types from T that are not assignable to U

type T20 = Diff<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // "b" | "d"
type T21 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // "a" | "c"
type T22 = Diff<string | number | (() => void), Function> // string | number
type T23 = Filter<string | number | (() => void), Function> // () => void
type T24 = Diff<string | string[] | null | undefined, string[]> // string | null | undefined
type T25 = Filter<string | string[] | null | undefined, string[]> // string[]
type T26 = Diff<undefined, null> // undefined
type T27 = Filter<undefined, null> // never

// 条件类型的约束
type NonNullable<T> = T extends null | undefined ? never : T

type T30 = NonNullable<string | number | undefined> // string | number
type T31 = NonNullable<string[] | null | undefined> // string[]

// 条件类型的类型推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any

type T40 = ReturnType<() => string> // string
type T41 = ReturnType<(s: string) => void> // void
type T42 = ReturnType<<T>() => T> // {}
type T43 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T44 = ReturnType<typeof f1> // { a: number, b: string }

// 条件类型的类型推断
type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T
type T50 = Unpacked<string> // string
  • infer: 用于在条件类型中推断类型,(定义类型)
// infer 关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
type T40 = ReturnType<() => string> // string
type T41 = ReturnType<(s: string) => void> // void
type T42 = ReturnType<<T>() => T> // {}
type T43 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T44 = ReturnType<typeof f1> // { a: number, b: string }

// infer 关键字的使用
type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T

type T50 = Unpacked<string> // string
type T51 = Unpacked<string[]> // string
type T52 = Unpacked<() => string> // string
type T53 = Unpacked<Promise<string>> // string
type T54 = Unpacked<Promise<string>[]> // Promise<string>
type T55 = Unpacked<Unpacked<Promise<string>[]>> // string

类中使用类型

// 类中使用类型

class C {
  x = 0
  y = 0
}
// 类当做类型使用
type T00 = C['x'] // number
type T01 = C['x'][] // number[]
type T02 = C['x' | 'y'] // number
type T03 = C['x' | 'z'] // number | undefined
type T04 = C extends { x: infer U } ? U : never // number
type T05 = C extends { x: (args: infer U) => void } ? U : never // never
type T06 = ReturnType<C['getX']> // string

// 类中实现接口
interface I {
  x: number
  y: number
}

class C implements I {
  x = 0
  y = 0
}

type T10 = C['x'] // number
type T11 = C['x'][] // number[]

// 类使用泛型
class C<T> {
  x: T
  y: T
}

class D extends C<string> {
  z = 0
}

type T20 = D['x'] // string
type T21 = D['x'][] // string[]
type T22 = D['x' | 'y'] // string
type T23 = D['x' | 'z'] // string | number

参考资料