类型工具

TypeScript

  • JS 也有类型,但是类型数量比较少(number、string...)
  • TS 可以随意自定义类型,比如联合类型。TS 的类型是严格类型,可以对类型进行校验
  • TS 无限集合类型 type JC = string | number
  • TS 有限集合类型 type JC = 'hello' | 'world'

is

is 用于定义变量属于某个类型,下面判断时将出现类型错误提示:

const isString = (x: unknown): boolean => typeof x === 'string'

function hello(a: unknown) {
    if (isString(a)) {
        //error: 对象的类型为 "unknown"。
        a.toUpperCase()
    }
}
let a = 'ab'

hello(a)
1
2
3
4
5
6
7
8
9
10
11

现在重新定义 isString 函数,使用 is 来定义变量为某个类型。

x is string 表示如果函数返回值为 true,则 x 为 string 类型

const isString = (x: unknown): x is string => typeof x === 'string'

function hello(a: unknown) {
    if (isString(a)) {
        a.toUpperCase()
    }
}
let a = 'ab'

hello(a)
1
2
3
4
5
6
7
8
9
10

keyof

获取类、接口索引组成的联合类型

keyof 可用于基本数据类型、any、class、interface、enum 等

任何类型都可以使用 keyof

// keyof 基本数据类型 => 类型的属性方法
type AMADA = keyof string

let jc: AMADA = 'match'
1
2
3
4

索引类型使用 keyof 时,获取索引名

// type AMADA = 'name' | 'age'
type AMADA = keyof { name: string, age: number }

let jc: AMADA = 'name'
1
2
3
4

下面是获取对象的属性的函数类型定义

function getAttribute<T>(obj: T, key: keyof T): T[keyof T] {
    return obj[key]
}

const user = { name: 'jerry', age: 18 }
getAttribute(user, 'name')
// getAttribute('abc', 'includes')
1
2
3
4
5
6
7

我们也可以用泛型定义索引类型

function getAttribute<T, D extends keyof T>(obj: T, key: D): T[D] {
    return obj[key]
}

const user = { name: 'jerry', age: 18 }
getAttribute(user, 'name')
1
2
3
4
5
6

typeof

使用 typeof 可获取变量的类型,下面是获取字符串变量的类型

let hello = 'jerry'

// type AMADA = string
type AMADA = typeof hello
1
2
3
4

下面使用 typeof 获取对象的 (在 JS 当中 typeof {} === 'object'

let hello = { name: 'jerry', age: 18 }

/**
 type AMADA = {
  name: string;
  age: number;
 */
type AMADA = typeof hello
1
2
3
4
5
6
7
8

keyof 与 typeof 结合定义获取对象属性的函数

// typeof obj => { name: string; age: number }
// keyof => 'name' | 'age'
function getAttribute(obj: object, key: string) {
    return obj[key as keyof typeof obj]
}

const hello = { name: 'helloworld' }
getAttribute(hello, 'name')
1
2
3
4
5
6
7
8

in

in 用于遍历接口或联合类型的属性

K in keyof T 指 K 类型为 keyof T 获取的 T 类型索引组成的联合类型

type USER = { name: string, age: number }

type MEMBER<T> = {
    [K in keyof T]: T[K]
}

const hello: MEMBER<USER & { address: string }> = {
    age: 10, name: 'jerry', address: '上海',
}
1
2
3
4
5
6
7
8
9

extends

extends 在 TS 中拥有多个特性,下面我们来分别了解。

类型继承

extends 实现类型的继承

type GOLDENJADE = { name: string }

interface helloworld extends GOLDENJADE {
    age: number
}

const hello: helloworld = { age: 33, name: 'jerry' }
1
2
3
4
5
6
7

extends 可用于泛型的类型限定,下例中 T 必须包含 id、render 属性,即 T 类型可赋予 extends 右侧类型

function helloworld<T extends { id: number; render(n: number): number }>(arr: T[]) {
    arr.map((a) => a.render(a.id))
}

helloworld([{ id: 1, render(n) { return n } }])
1
2
3
4
5

类型条件判断

extends 用于条件判断来决定返回什么类型,A extends B ? true:false。如果 A(狭窄类型) 可以赋予 B(宽泛类型) 类型则为 true。

下例的 hello 变量值必须为 false,因为 AMADA 不包含 GOLDENJADE 类型

type GOLDENJADE = { name: string, age: number }

type AMADA = { name: string }
// 窄类型的限制更多,更细。false
type HEYTEA = AMADA extends GOLDENJADE ? true : false

const hello: HEYTEA = false
1
2
3
4
5
6
7

下面是联合类型的条件判断

type GOLDENJADE = string

type AMADA = string | number

const hello: AMADA extends GOLDENJADE ? string : boolean = false  // boolean
// 联合类型比单一类型更宽泛,可以被 extendss
const jc: GOLDENJADE extends AMADA ? string : boolean = 'jerry'  // string
1
2
3
4
5
6
7

✅ 根据索引名称过滤

根据联合类型过滤掉指定索引

type User = { name: string, age: number, get(): void };

// type K = 'name' | 'age' |  'get'
// Exclude<K, 'name'> => never | 'age' | 'get' => 'age' | 'get'
// as never => neve, never 作为 key 会被过滤掉
type FilterObjectProperty<T, U> = {
    [K in keyof T as Exclude<K, U>]: T[K]
}

type HK = FilterObjectProperty<User, 'name' | 'age'>
1
2
3
4
5
6
7
8
9
10

✅ 根据值类型过滤

过滤掉指定的类型,以下代码含有下面几个含义

  • 根据类型获取索引组合成的联合类型
  • 根据新的联合类型提取出指定的索引,组合成新的类型
type USER = { name: string, age: number, get(a: string): void }

type FilterProperty<T, U> = {
    [K in keyof T]: T[K] extends U ? never : K
}[keyof T]

// [keyof T] 获取到 key 的联合类型
// type ABC = FilterProperty<USER, number>;  'name' | 'get'
// type UserType = { name: string }
type UserType = Pick<USER, FilterProperty<USER, Function | number>>
1
2
3
4
5
6
7
8
9
10

💡 [keyof T] 解析:

// 在 JavaScript 当中,let a = { name: '33'},a['name']可以取值

type UU = { name: 'name'; age: never; get: 'get' };
type AB = UU[keyof UU];  // 'name' | 'get' | never => 'name' | 'age'
1
2
3
4

泛型条件分配

如果泛型是普通类型,则与上面一样也是判断左侧类型是否可赋予右侧类型

type GOLDENJADE = string

type HEYTEA<T> = T extends GOLDENJADE ? string : boolean

const hello: HEYTEA<string> = 'jerry' //string
1
2
3
4
5

如果 extends 是泛型类型,并且传入的类型是联合类型。则分别进行判断,最后得到联合类型。

type GOLDENJADE = string

type HEYTEA<T> = T extends GOLDENJADE ? string : boolean

const hello: HEYTEA<string | number> = false  // string | boolean
1
2
3
4
5

条件判断也可以嵌套使用

type GOLDENJADE = string

type AMADA = string | number

type HEYTEA<T> =
	T extends GOLDENJADE ? string :
	T extends AMADA ? symbol : boolean

const hello: HEYTEA<string | number> = 'jerry'
1
2
3
4
5
6
7
8
9

✅ 使用 [ ] 包裹类型,表示使用泛型的整体进行比较

type GOLDENJADE = string | number

type AMADA = string | number

type HEYTEA<T> = [T] extends [GOLDENJADE] ? string : boolean

const hello: HEYTEA<string | number> = 'jerry'  // string
1
2
3
4
5
6
7

Exclude

我们利用上面的泛型类型的条件分配,可以创建一个类型用于进行类型的过滤。

  • 从 T 泛型类型 中过滤掉 U 的类型
  • never 是任何类型的子类型,可以赋值给任何类型,没有类型是 never 的子类型或可以赋值给 never 类型(never 本身除外)
type EXCLUDE<T, U> = T extends U ? never : T

type GOLDENJADE = string

type AMADA = string | number

const hello: EXCLUDE<AMADA, GOLDENJADE> = 100;  // number
1
2
3
4
5
6
7

事实上 typescript 已经提供了 Exclude 关键字用于完成上面的工作,所以我们不需要单独定义 Exclude 类型了。

type GOLDENJADE = string

type AMADA = string | number

const hello: Exclude<AMADA, GOLDENJADE> = 100;
1
2
3
4
5

Extract

Extract 与 Exclude 相反,用于获取相交的类型。(v. 提炼)

type EXTRACT<T, U> = T extends U ? T : never;

type AMADA = string | number | boolean

const hello: EXTRACT<AMADA, string | number> = 'jerry';
1
2
3
4
5

下面是取两个类型相同的属性名

type AMADA = string | number | boolean

const hello: Extract<AMADA, string | number> = 'jerry';
1
2
3

Pick

pick 可以用于从属性中挑选出一组属性,组成新的类型。

下面定义 pick 类型用于从 AMADA 类型中挑选出 name 与 age 类型。

type AMADA = { name: string, age: number, skill: string }
// keyof T: name | age | skill
type PICK<T, U extends keyof T> = {
    [P in U]: T[P]
}

// type HK = { name: string; age: number; }
type HK = PICK<AMADA, 'name' | 'age'>
const jc: HK = { name: 'jerry', age: 33 }
1
2
3
4
5
6
7
8
9

同样 typescript 已经原生提供了 Pick 类型,所以我们不用像上面那样自己定义了

type AMADA = { name: string, age: number, skill: string }

type HK = Pick<AMADA, 'name' | 'age'>
const jc: HK = { name: 'jerry', age: 33 }
1
2
3
4

Omit

从类型中过滤掉指定属性,这与 Pick 类型工具功能相反

type HK = { name: string, age: number, city: string }

type MyOmit<T, U> = Pick<T, {
    [K in keyof T]: K extends U ? never : K
}[keyof T]>

// { [K in keyof T]: K extends U ? never : K }
// { name: never ; age: never; city: 'city' }
// 最后通过 [keyof T] 提取出值的联合类型:never | never | 'city' => 'city'
// 结果为:Pick<HK, 'city'>
type XJ = MyOmit<HK, 'name' | 'age'>  // {city:string}
1
2
3
4
5
6
7
8
9
10
11

可以将上面代码使用 Exclude 优化

type HK = { name: string, age: number, city: string }

type MyOmit<T, U> = Pick<T, Exclude<keyof T, U>>

type XJ = MyOmit<HK, 'name' | 'age'>  // {city:string}
1
2
3
4
5

typescript 已经提供了类型工具 Omit

type HK = { name: string, age: number, city: string }

type XJ = Omit<HK, 'name' | 'age'>  // {city:string}
1
2
3

Partial

下面定义 Partial 类型,用于将全部属性设置为可选

type GOLDENJADE = { name: string, age: number }

type PARTIAL<T> = {
    [P in keyof T]?: T[P]
}

const hello: PARTIAL<GOLDENJADE> = { name: '小强' } // {name?:string,age?:number}
1
2
3
4
5
6
7

Typescript 原生提供了 Partial 的支持,所以我们不用自己定义了

type GOLDENJADE = { name: string, age: number }

const hello: Partial<GOLDENJADE> = { name: '小强' }
1
2
3

Record

✅ Record 常用于快速定义对象类型使用

下面我们来手动实现一个 Record,RECORD 类型的第一个参数为索引,第二个为类型

type RECORD<K extends string | number | symbol, V> = {
  [P in K]: V
}

// type HK = { name: string | number; age: string | number }
type HK = RECORD<'name' | 'age', string | number>

type MN = Record<string, string | number>

const jc: HK = { name: "jerry", age: 18 }
1
2
3
4
5
6
7
8
9
10

typescript 原生已经提供了 Record 类型,下面定义 MEMBER 类型,索引为字符串,值为任何类型

type HK = Record<'name' | 'age', any>

const jc: HK = { name: "jerry", age: 18 }
1
2
3

索引签名

type HELLO = {
  [x: `${string}Jc`]: string | number;
  city: string;
  age: number;
}

let jc = HELLO = {
  nameJc: 'Jerry',
  city: 'XIAMEN',
  age: 25
}

// type ABC = string | number | symbol
type ABC = keyof any
1
2
3
4
5
6
7
8
9
10
11
12
13
14

交叉类型

interface A { name: string }
type B = { age: number }
let c: A & B = { name: 'Jerry', age: 25 }

// 类型不一致无法交叉,结果就是没有类型:never
type JC = string & number  // never
type JC = 'a' & 'b'  // never
1
2
3
4
5
6
7

类型不一致报错:

type A = { name: string }
type B = { age: number, name: boolean }

// type AB = A & B
// name 报错,不能将类型“string”分配给类型“never”
type AB = A & Pick<B, 'age'>
let hi: AB = { age: 25, name: 'Jerry'}
1
2
3
4
5
6
7

交叉类型的实际应用:(类型拓展)

type User = { name: string, age: number }
type Member = { avatar: string} & User
1
2

merge 方法

function merge<T, U>(a: T & U, b: U): T & U {
  for(const key in b) {
    // a[key as Extract<keyof U, 'string'>] = b[key] as any
    a[key] = b[key] as any
  }
  return a
}
1
2
3
4
5
6
7

联合类型

tyoe HK = ('a' | 'b') & ('a') // a | never => a

// 'b'是单个字符串类型,是字符串的子类, 'b' & string => 'b'
tyoe HK = ('a' | 'b') & ('a' | string) // a | b
1
2
3
4

infer

infer

  • infer 只能在 extends 中使用
  • infer 的类型变量,只能在 extends 条件的 true 中使用 🚨

下面使用 infer 推断属性值类型

type HK = { name: string, age: number }

// type AttrType<T> = T extends { name: infer M, age: infer N } ? [M, N] : T
type AttrType<T> = T extends { name: infer M, age: infer M } ? M : T

type valueType = AttrType<HK>  // string | number
1
2
3
4
5
6

下面使用 infer 获取值类型

type USER = { name: string, age: number, get(a: string): void }

type GetType<T> = {
  	// infer 会动态改变:string -> number -> ((a: string) => void)
    [K in keyof T]: T[K] extends (infer U) ? U : K
}[keyof T]

// 最后通过 [keyof T] 获取到值联合类型:string | number | ((a: string) => void)
type valueType = GetType<USER>;
1
2
3
4
5
6
7
8
9

下面是获取函数返回值类型

type HK = (n: string) => number[]

type GetFunctionReturnValue<T> = T extends ((...args: any) => (infer U)[]) ? U : T

// number
type valueType = GetFunctionReturnValue<HK>;

// number[]
// type GetFunctionReturnValue<T> = T extends ((...args: any) => (infer U)) ? U : T
1
2
3
4
5
6
7
8
9
上次更新: 2022/8/20 17:29:12
贡献者: Jerry Chen, Jinrui Chen