# 笔记

# 2022 年 5 月 1 日

# typeof

typescript 中,typeof 可以用来获取一个变量声明或对象的类型

interface IPerson {
  name: string
  age: number
}
const someone: IPerson = { name: "张三", age: 21 }
type IMan = typeof someone // -> IPerson

也可以用来获取函数对象的类型

function getString(str: number): string {
  return str + ""
}

type Func = typeof getString // -> (str: number) => string

# 2022 年 5 月 2 日

# keyof

keyof 操作符可以捕获一个类型的键,如捕获变量的键的名称,用法如下:

const colors = {
  red: "red",
  blue: "blue"
}

// 首先通过 typeof 操作符获取 colors 变量的类型,然后通过 keyof 操作符获取该类型的所有键:'red' | 'blue'
type Colors = keyof typeof colors
let color: Colors
color = "red" // Ok
color = "blue" // Ok
color = "anythingElse" // Error

案例来源 (opens new window)

# 2022 年 5 月 3 日

# 泛型和泛型约束案例之一

function prop(obj: object, key: string) {
  return obj[key]
}

在上面代码中,为了避免调用 prop 函数时传入错误的参数类型,我们为 objkey 参数设置了类型,分别为 {}string 类型。但实际上并没有那么简单,以上代码经过 ts 编译后会报以下错误

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'

元素隐式地拥有 any 类型,因为 string 类型不能被用于索引 {} 类型。要解决这个问题,你可以使用以下非常暴力的方案:

function prop(obj: object, key: string) {
  return (obj as any)[key]
}

很明显该方案并不是一个好的方案,我们来回顾一下 prop 函数的作用,该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用 keyof 操作符:

function prop<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

首先定义了 T 类型并使用 extends 关键字约束该类型必须是 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型。

案例来源 (opens new window)

# 2022 年 5 月 4 日

# 内置类型别名之 Partial && Required

// Make all properties in T optional
type Partial<T> = {
  [P in keyof T]?: T[P]
}

// Make all properties in T required
type Required<T> = {
  [P in keyof T]-?: T[P]
}

如上代码所示,Partial<T> 的作用是将某个类型中的所有属性都变成可选属性;而 Required<T> 的作用则是将某个类型中的可选属性都变成必选属性,其中 -? 表示移除可选项 ?

示例:

export interface IResult {
  code?: number
  message: string
  data?: string
}

type IResultPartial = Partial<IResult>
type IResultRequired = Required<IResult>

// 结果
// type IResultPartial = {
//     code?: number | undefined;
//     message?: string | undefined;
//     data?: string | undefined;
// }

// type IResultRequired = {
//     code: number;
//     message: string;
//     data: string;
// }

# 2022 年 5 月 5 日

# 内置类型别名之 Exclude && Extract

// Exclude from T those types that are assignable to U
type Exclude<T, U> = T extends U ? never : T

// Extract from T those types that are assignable to U
type Extract<T, U> = T extends U ? T : never

如上代码所示,Exclude<T, U> 的作用是将 T 类型中属于 U 类型的属性移除掉;而 Extract<T, U> 则是从 T 中提取出 U

注意以上 extends 的作用为类型约束,当 TU 的子类型时,T extends U 返回的是 true,否则返回 false

示例:

interface T1 {
  name: string
  age: number
  email: string
  phone: number
}
interface T2 {
  email: string
  phone: number
}

type T3 = string | number | number[]
type T4 = string

// 需要区分联合类型与接口的区别
type T5 = Exclude<keyof T1, keyof T2> // -> type T5 = "name" | "age"
type T6 = Exclude<T3, T4> // -> type T6 = number | number[]

type T7 = Extract<keyof T1, keyof T2> // -> type T7 = "email" | "phone"
type T8 = Extract<T3, T4> // -> type T8 = string

# 2022 年 5 月 6 日

# 内置类型别名之 Pick && Omit

// From T, pick a set of properties whose keys are in the union K
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

// Construct a type with the properties of T except for those in type K.
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

如上代码所示,Pick<T, K extends keyof T> 的作用是将 T 中的子类型 K 提取出来;Omit<T, K extends keyof any> 的作用则是将 T 中除 K 类型以外的所有属性

示例:

interface T1 {
  name: string
  age: number
  email: string
  phone: number
}

interface T2 {
  email: string
  phone: number
}

type T3 = "email" | "phone"

type T5 = Pick<T1, keyof T2> // -> type T5 = { email: string; phone: number; }
type T6 = Pick<T1, T3> // -> type T6 = { email: string; phone: number; }

type T7 = Omit<T1, keyof T2> // -> type T7 = { name: string; age: number; }
type T8 = Omit<T1, T3> // -> type T8 = { name: string; age: number; }

# 2022 年 5 月 7 日

# TS 中的 extends 关键字

参考文档地址 (opens new window)

# 2022 年 5 月 8 日

# const 断言

# 2022 年 5 月 9 日

# 协变与逆变

# 2022 年 5 月 10 日

# readonly & const

参考文档:

# 2022 年 5 月 11 日

# 泛型、泛型接口、泛型类、非泛型函数签名?

# 2022 年 5 月 12 日

# ts 中的 infer 关键字

infer 关键字是在 2.8 版本中新增的,在有条件类型的 extends 子语句中,允许出现 infer 声明,它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的 true 分支中被引用。 允许出现多个同类型变量的 infer

如下代码会提取函数类型的返回值类型:

// 例1:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
// infer R 为 待推断的返回类型

// 例2:
type ParamType<T> = T extends (param: infer R) => any ? R : T

大白话理解即为:如果例 1 中的 T 能赋值于 (...args: any[]) => infer R,那么 infer R 就是 T 的返回类型;如果例 2 中的 T 能赋值于 (param: infer R) => any 那么 infer R 就是参数 param 的类型

加深理解:

type Unpacked<T> = T extends (infer U)[] // 指待推断的数组类型
  ? U
  : T extends (...args: any[]) => infer U // 指待推断的函数返回值类型
  ? U
  : T extends Promise<infer U> // 指待推断的参数类型
  ? U
  : T

type T0 = Unpacked<string> // string
type T1 = Unpacked<string[]> // string
type T2 = Unpacked<() => string> // string
type T3 = Unpacked<Promise<string>> // string
type T4 = Unpacked<Promise<string>[]> // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>> // string

参考文档:

# 2022 年 5 月 13 日

# 内置类型别名之:ReturnType

// Obtain the return type of a function type
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

如上代码所示,ReturnType<T> 主要用于获取函数返回值类型

更多示例:

function f1(s: string) {
  return { a: 1, b: s }
}

type T1 = ReturnType<() => string> // string
type T2 = ReturnType<(s: string) => void> // void
type T3 = ReturnType<<T>() => T> // unknown ? {}
type T4 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T5 = ReturnType<typeof f1> // { a: number, b: string }
type T6 = ReturnType<any> // any
type T7 = ReturnType<never> // never?any
type T8 = ReturnType<string> // Error
type T9 = ReturnType<Function> // Error

# 2022 年 5 月 24 日

# Object.keys 遍历赋值

如下栗子,在 js 中我们经常用 Object.keys 获取一个对象的属性进行遍历赋值,但是在 ts 中使用会提示异常

type IObj = {
  a: number
  b: string
  c: number[]
}
const obj: IObj = {
  a: 1,
  b: "2",
  c: [1, 2, 3]
}

const data = {
  a: 10,
  b: "string",
  c: [1, 2, 3, 4, 5, 6, 7],
  d: true
}

Object.keys(obj).forEach(key => {
  // 报错
  obj[key] = data[key]
})
// 异常信息 ->
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IObj'.
// No index signature with a parameter of type 'string' was found on type 'IObj'.

// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: string; c: number[]; d: boolean; }'.
// No index signature with a parameter of type 'string' was found on type '{ a: number; b: string; c: number[]; d: boolean; }'.

Object.keys 获取到的属性 list 其实是 string[],而非 (keyof IObj)[],因此需要再使用类型断言

Object.keys(obj).forEach(key => {
  obj[key as keyof IObj] = data[key]
})

// 可能在playground仍然会报下面这个异常,这时只需要给obj加上 as any 断言即可
// Type 'any' is not assignable to type 'never'

Object.keys(obj).forEach(key => {
  ;(obj as any)[key as keyof IObj] = data[key]
})

写成方法则如下:

function traversalAssign<T extends object, Q extends object>(obj1: T, obj2: Q) {
  Object.keys(obj1).forEach(key => {
    obj1[key as keyof T] = (obj2 as any)[key]
  })
}

// 或

function traversalAssign<T, Q extends object>(obj1: T, obj2: Q) {
  let item: keyof T
  for (item in obj1) {
    obj1[item] = (obj2 as any)[item]
  }
}
Last Updated: 7/22/2022, 10:08:22 AM