TypeScript

218 阅读3分钟

交叉类型

交叉类型使用&

  • 父类型收敛
type A0 = 1 & number; // 1
type A1 = '1' & string; // '1'
type A2 = true & boolean; // true
type A3 = any & 1; // any;
type A4 = any & boolean; // any
type A5 = any & never; // never
interface X {
    c: string
    d: string
}
interface Y {
    c: number
    d: string
}
type XY = X & Y; // 此时c类型未never

如果对象相同的属性是字面量类型或字面量类型组合的联合类型,那么运算结果将是never

type Foo = {
  age: number
  name: string
}
type Bar = {
  age: number
  name: boolean // 可以看作是true|false
}
type Baz = Foo & Bar; // never
  • 应用
  1. 实现一个PartialByKeys工具类型,用于把对象中指定的key变成可选的
type PartialByKeys<T, K extends keyof T> = {
  [P in K]?: T[P];
} & Pick<T, Exclude<keyof T, K>>

type S1 = {
  name: string
  age: number
  address: string
}
type R1 = PartialByKeys<S1, 'address'>
const r1: R1 = {
  name: 'Mark',
  age: 12
}
  1. 实现一个RequiredByKeys工具类型,用于把对象中指定的key变成必选的
type RequiredByKeys<T, K extends keyof T> = {
  [P in K]: T[P];
} & Partial<Pick<T, Exclude<keyof T, K>>>

type S1 = {
  name: string
  age: number
  address: string
}
type R1 = RequiredByKeys<S1, 'address'>
const r1: R1 = {
  address: '1'
}

索引签名

  • 用法
{ [key: KeyType]: ValueTYpe }
// KeyType: string 、number、symbol、模板字面量
type T0 = {
  [key: number]: string
}
type T1 = keyof T0 // number

type T10 = {
  [key: string]: string
}
type T11 = keyof T10 // number | number, 当用作属性访问器中的键时,js会隐式的把数字转为字符串类型

// 模板字面量类型
type T20 = {
  [key: `${string}Changed`]: () => void
}
const o20: T20 = {
  'valueChanged': () => {}
}
  • 和内置类型Record的区别 索引类型键的类型:string 、number、symbol、模板字面量
    Record键的类型:可以是字面量类型或字面量类型的联合类型

模板字面量类型

type Direction = 'left' | 'right' | 'top' | 'bottom'

type CssPadding = `padding-${Direction}` // "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
type MarginPadding = `margin-${Direction}` // "margin-left" | "margin-right" | "margin-top" | "margin-bottom"
type GetterName<T extends string> = `get${Capitalize<T>}`
type U1 = GetterName<'foo'> // 'getFoo'
type Direction = 'left' | 'right' | 'top' | 'bottom'
type InferRoot<T> = T extends `${infer R}${Capitalize<Direction>}` ? R : T
type U2 = InferRoot<'marginLeft'> // margin
type Getters<T> = {
  // string & K 过滤非string类型的键
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

type User = {
  id: number
  name: string
}
type U3 = Getters<User>

协变和逆变

  • 协变 将子类型的变量赋值给父类型的变量就是协变
interface Animal {
  name: string
}
interface Cat {
  name: string
  miao(): void
}
const cat1: Cat = {
  name: '悠米',
  miao: () => {
    console.log('喵喵')
  }
}
const animal: Animal = cat1
console.log(animal.name)
  • 逆变
interface Animal {
  name: string
}
interface Cat {
  name: string
  miao(): void
}
let fun1: (x: Animal) => void = (x: Animal)=>{}
let fun2: (x: Cat) => void = (x: Cat)=>{}

fun1  = fun2 // error
fun2  = fun1 // ok

我们发现:参数逆变是可以的,协变却发生错误。为什么报错呢,因为这样会造成类型不安全的。
我们可以在tsconfig.json中设置"strictFunctionTypes": false,使之不报错,加上如下代码:

const cat1: Cat = {
  name: '悠米',
  miao: () => {
    console.log('喵喵')
  }
}
let animal: Animal = {
  name: '小A'
}
fun1(animal) // 运行时报错了

一些关键字

is

vue3源码中有这样一个函数

const isString = (val: any): val is string => typeof val === 'string'

所以需要使用is特性. ts可以根据 if 判断推断出当前的val为string类型
如果没有没有is

function isString(s: unknown): boolean {
  return typeof s === 'string'
}

function toUpperCase(x: unknown) {
  if(isString(x)) {
    x.toUpperCase() // Error, Object is of type 'unknown'
  }
}

keyof

type KEY = keyof any //即 string | number | symbol

type KEY2 = keyof unknown // 即 never
export function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

typeof

enum HttpMethod {
  Get,
  Post
}
type Method = keyof typeof HttpMethod // "Get" | "Post"

extends

type R1 = 1 extends 1 ? true : false // true
type R2 = 1 extends 2 ? true : false // false
type R3 = 'abc' extends 'a' ? true : false // false
type R4 = { a: true } extends { a: true } ? true : false // true
type R5 = { a: true; b: false } extends { a: true } ? true : false // true
type R6 = { a: true } extends { a: true; b: false } ? true : false // false
type R7 = 'a' | 'b' | 'c' extends 'a' ? true : false // false

在泛型中使用呢 TypeScript: Documentation - Conditional Types (typescriptlang.org)
根据文档可知即当条件类型作用于泛型类型时,联合类型会被拆分使用。

  • 分布式条件类型:裸类型参数:没有被T[][T]Promise<T>包裹
type MyExclude<T, U> = T extends U ? never : T
type R10 = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

为什么R10是'b' | 'c',因为 MyExclude<'a' | 'b' | 'c', 'a'>会被拆成'a' extends 'a''b' extends 'a''c' extends 'a'

infer

// 仅条件类型的"extends"子句中才允许"infer"什么声明
type UnpakcedArray<T> = T extends (infer U)[] ? U : T
type S1 = UnpakcedArray<number[]> // number

Unpakced

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

type S1 = Unpakced<number[]> // number
type S2 = Unpakced<string[]> // string
type S3 = Unpakced<() => boolean> // boolean
type S4 = Unpakced<Promise<string>> // string
type S5 = Unpakced<Promise<string>[]> // Promise<string>
type ProperType<T> = T extends { id: infer U; name: infer R } ? [U, R] : T;

type User = {
  id: number;
  name: string;
  age: number;
}

type U1 = ProperType<User> // [number, string]

在协变位置上,若同一个类型变量存在多个候选者,则最终类型被推断为联合类型

type ProperType<T> = T extends { id: infer U; name: infer U } ? U : T;

type User = {
  id: number;
  name: string;
  age: number;
}

type U1 = ProperType<User> // string | number

在逆变位置上,若同一个类型变量存在多个候选者,则最终类型被推断为交叉类型

type Foo<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never
type U1 = Foo<{ a: (x: string) => void, b: (x: number) => void }> // number & string => never
  • 4.7新特性 可以给infer type添加可选的extends子句
type FirstIfString<T> = T extends [infer S extends string, ...unknown[]] ? S : never

declare

  • 生命全局变量、全局函数、全局类、全局枚举的。
  • 注意,声明全局函数不用实现
  • declare module 语法,扩展已有类型 比如给组件实例增加$axios属性
    import { AxiosInstance } from 'axios'
    declare module "@vue/runtime-core" {
        interface ComponentCustomProperties {
            $axios: AxiosInstance
        }
    }
    
    main.ts
    import axios from 'axios'
    app.config.globalPropertie.$axios = axios
    

面试题

interface 和 type的区别

  • type可以做到,但interface不能做到的事情
    • 可以定义其他类型别名,如 type MyBoolean = boolean
    • 可以使用typeof操作符来定义,如 type myType = typeof obj1
    • 联合类型,如type unionType = type1 | type2
    • 元组类型,如type tupleType = [type1, type2]
  • interface可以做到,而type不能做到事
    • 声明合并
        interface test1 {
          name: string
        }
        interface test1 {
          age: number
        }
        // 跟下面的等价
        // interface test1 {
        //   name: string
        //   age: number
        // }
      
    • 如果是type的话,会报重复定义的错误

any和unknown 的区别

  • any: 我不在乎它的类型,然后可以进行任何操作
  • unknown: 我不知道它的类型,所以必须进行类型检查或类型断言后才能进行操作。
  • 任何类型都可以赋给any或unknown类型
  • unknown类型的变量只能赋值给any和unknown类型
type T0 = keyof any; // string | number | symbol
type T1 = keyof unknown; // never
type T10<T> = {
  [P in keyof T]: number
}
type T11 = T10<any>; // { [x: string]: number }
type T12 = T10<unknown>; // {}

内置的类型

Partial<T>

 将类型 T 的所有属性标记为可选属性

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

Required<T>

 将类型 T 的所有属性标记为必选属性

type Required<T> = {
    [P in keyof T]-?: T[P];
}

Readonly<T>

将类型 T 的所有属性标记为 readonly, 即只读

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}

Pick<T>

从类型 T 中过滤出某个或某些属性

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}

Record<K, T>

标记 K 中的属性为T类型; key=>value

type Record<K extends keyof any, T> = {
    [P in K]: T;
}

Exclude<T, U>

从 T 中排除可分配给 U 的类型

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

例子

// 结果:'b' | 'd'
type StringExclude = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>

Extract<T, U>

从 T 中提取可分配给 U 的类型。Exclude 的反操作

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

例子

// 结果:'a' | 'c' type StringExtract = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>

NonNullable<T>

T中剔除nullundefined

type NonNullable<T> = T extends null | undefined ? never : T;

ReturnType<T>

获取函数返回值类型。

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

InstanceType<T>

获取构造函数类型的实例类型

type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

Parameters<T>

返回函数参数元组

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Omit

以一个类型为基础支持剔除某些属性,然后返回一个新类型。

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;