ts泛型详解参考

1、声明文件和declare关键字

处理问题:当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。这是因为前端第三方库大多都是非 TypeScript 库,基本上都是使用 JS 编写的,在 TS 中使用非 TS 编写的第三方库,需要有个 xx.d.ts 声明文件。
注意:.d.ts 文件中的顶级声明必须以 "declare" 或 "export" 修饰符开头。
declare 就是申明一个全局的类型或者变量或者模块
// 例1 声明一个类型
declare type Asd {
    name: string;
}

// 例2 申明一个模块
declare module "*.vue" {
    import Vue from 'vue'
    export default Vue
}

2..d.ts是干嘛的?

用途:.d.ts文件是ts用来声明变量,模块,type,interface等等的。
引用:在.d.ts声明变量或者模块等东西之后,在其他地方可以不用import导入这些东西就可以直接用,用,而且有语法提示。需要预编译,所以需要在tsconfig.json文件里面的include数组里面添加这个文件

3 、ts工具函数

交叉类型(&)
通过 & 将多个类型合并为一个类型
T & U
function extend<T , U>(first: T, second: U) : T & U {
    let result: <T & U> = {}
    for (let key in first) {
        result[key] = first[key]
    }
    for (let key in second) {
        if(!result.hasOwnProperty(key)) {
            result[key] = second[key]
        }
    }
    return result
}
Partial : 将选入的属性变为可选项(全局变可选)
首先我们需要理解两个关键字 keyof in, keyof 可以用来取得一个对象接口的所有 key 值.
interface Foo {
  name: string;
  age: number
}
type T = keyof Foo // -> "name" | "age"

in 则可以遍历枚举类型, 例如:

type Keys = "a" | "b"
type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any }

Partial源码

type Partial<T> = { [P in keyof T]?: T[P] };
Required将传入的属性变为必选项

Required源码

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

注意:我们发现一个有意思的用法 -?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项. 与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的.

Mutable (未包含)
类似地, 其实还有对 + 和 -, 这里要说的不是变量的之间的进行加减而是对 readonly 进行加减.
以下代码的作用就是将 T 的所有属性的 readonly 移除,你也可以写一个相反的出来.

Mutable源码

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}
Readonly
将传入的属性变为只读选项, 源码如下

Readonly源码

type Readonly<T> = { readonly [P in keyof T]: T[P] };
Record
将 K 中所有的属性的值转化为 T 类型

Record源码

type Record<K extends keyof any, T> = { [P in K]: T };
Pick
从 T 中取出 一系列 K 的属性

Pick源码

type Pick<T, K extends keyof T> = { [P in K]: T[P] };
Exclude
从T 中排除 U
在 ts 2.8 中引入了一个条件类型, 示例如下

T extends U ? X : Y
以上语句的意思就是 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y

Exclude源码

type Exclude<T, U> = T extends U ? never : T;
Extract
从 T 中提取出 U

Extract源码

type Extract<T, U> = T extends U ? T : never;
Omit (未包含)
用之前的 Pick 和 Exclude 进行组合, 实现忽略对象某些属性功能

Omit源码

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

// 使用
type Foo = Omit<{name: string, age: number}, 'name'> // -> { age: number }
ReturnType我们可以用它获取函数的返回类型
infer 关键字
在条件类型语句中, 我们可以用 infer 声明一个类型变量并且对它进行使用,

源码

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

具体用法

function foo(x: number): Array<number> {
  return [x];
}
type fn = ReturnType<typeof foo>;
AxiosReturnType(未包含)
开发经常使用 axios 进行封装 API层 请求, 通常是一个函数返回一个 AxiosPromise<Resp>, 现在我想取到它的 Resp 类型, 根据上一个工具泛型的知识我们可以这样写.
import { AxiosPromise } from 'axios' // 导入接口
type AxiosReturnType<T> = T extends (...args: any[]) => AxiosPromise<infer R> ? R : any

// 使用
type Resp = AxiosReturnType<Api> // 泛型参数中传入你的 Api 请求函数

4、忽略 undefined 和 null 类型

x! 将从 x 值域中排除 null 和 undefined 。
例子如下

function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'. 
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

5、调用函数时忽略 undefined 类型

const num2 = numGenerator!(); //OK

type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // Object is possibly 'undefined'.(2532)
  // Cannot invoke an object which is possibly 'undefined'.(2722)
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); //OK
}

6、确定赋值断言

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

7、typeof 关键字

typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
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}'.`);
}

8、类型约束

通过关键字 extend 进行约束,不同于在 class 后使用 extends 的继承作用,泛型内使用的主要作用是对泛型加以约束
引用
type BaseType = string | number | boolean

// 这里表示 copy 的参数
// 只能是字符串、数字、布尔这几种基础类型
function copy<T extends BaseType>(arg: T): T {
  return arg
}

9、条件类型

条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。
//意思就是,如果 T 是 U 的子集,就是类型 X,否则为类型 Y
T extends U ? X : Y

10、ts 函数重载

允许创建数项名称相同但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力
引用
关于typescript函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用 |操作符或者?操作符,把所有可能的输入类型全部包含进去
// 上边是声明
function add (arg1: string, arg2: string): string
function add (arg1: number, arg2: number): number
// 因为我们在下边有具体函数的实现,所以这里并不需要添加 declare 关键字

// 下边是实现
function add (arg1: string | number, arg2: string | number) {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2
  if (typeof arg1 === 'string' && typeof arg2 === 'string') {
    return arg1 + arg2
  } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
    return arg1 + arg2
  }
}

11、泛型参数默认类型

概念:在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。

语法:<T=Default Type>

示例:

interface A<T=string> {
  name: T;
}

const strA: A = { name: "Semlinker" };
const numB: A<number> = { name: 101 };

12、Record定义一个对象的 key 和 value 类型

示例:比如我需要一个对象,有 ABC 三个属性,属性的值必须是数字,那么就这么写:
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
  A: 1,
  B: 2,
  C: 3
}

suipa
237 声望16 粉丝

前端程序猿