TS - 泛型小结

1,807 阅读4分钟

1. 泛型有什么用?

// 考虑以下场景
// 函数名为add,可以用于数字、字符串等相加减
// [Func - 数字类型]
function printLiteral(arg: number) {
  console.log(arg);
}
// [Func - 字符串类型]
function printLiteral(arg: string) {
  console.log(arg);
}

上述代码明显有重复,违背DRY原则,难道只能写成如下形式么?

// [Func - 任意类型]
function printLiteral(arg: any) {
  console.log(arg);
}

这样写其实失去了使用类型标记的意义,全部标记为any == 没有标记类型

这时候就要祭出泛型

function printLiteral<T>(arg: T) {
  console.log(arg);
}

@思考1-1 能否约束T类型让其是number或者string中的一种?
@思考1-2 能否让参数数量可变,类似console.log?

|

2. keyof关键字

如何要实现一个取泛型中的属性的函数?

// [Func - Unknow]
function pickValue<T>(obj: T, prop: string) {
  return obj[prop];
}
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'

// [Func - object]
function pickValue<T extends object>(obj: T, prop: string) {
  return obj[prop];
}
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'

// [Func - Trick]
function pickValue<T extends {[prop: string]: any}>(obj: T, prop: string) {
  return obj[prop];
}
// 不报错了,但是 == 写了个any,没有任何约束

关键字keyof能够获取类型中的包含字段

function pickValue<T extends object, U extends keyof T>(obj: T, prop: U) {
  return obj[prop];
}

@思考2-1 能否约束T使其能够支持new操作

function createInstance<T>(type: T): T {
  return new type();
}

|

3. 实现Partial

// 假设有以下类型
type Person = {
  name: string,
  age: number,
  temperature: number,
}
// 其所有属性要改成可选的
type OptionPerson = {
  name?: string,
  age?: number,
  temperature?: number,
}
// 有什么办法可以做到么?

有,内置工具Partial已经实现了

type OptionPerson = Partial<Person>

能否自己实现一下Partial?

type MPartial<T> = {
    [prop in keyof T]?: T[prop] | undefined;
}

@思考3-1 能否实现一个Partial将属性的可选值改为必填值?

|

4. 实现Pick

实现一个函数的pickValue,借鉴2中keyof关键字的介绍,可以实现如下

function pickValues<T extends object, U extends keyof T>(obj: T, props: U[]) {
  return props.map(prop => obj[prop]);
}

如果是通过Pick新建一个类型呢

type Person = {
  name: string,
  age: number,
  temperature: number,
}

type HealthyPerson = Pick<Person, "name"|"temperature">
// type HealthyPerson = {
//   name: string;
//   temperature: number;
// }

@思考4-1 如何实现一个内置的Pick工具?

|

5. 条件类型 - 实现Exclude

条件类型

declare function getValue<T extends boolean>(arg: T): T extends true ? string : number;
getValue(true); // function getValue<true>(arg: true): string
getValue(false); // function getValue<false>(arg: false): number

Exclude内置工具效果

type NumberOrString = Exclude<number| string| undefined, undefined>;

@思考5-1 如何实现一个内置的Exclude工具?
@思考5-2 如何提取接口中的函数名称?

type Person = {
  name: string,
  age: number,
  temperature: number,
  fromWhere: () => string,
  measureTemperature: () => number,
}
type funNames = extractFunctionName<Person>; // "fromWhere" | "measureTemperature"

|

6. 实现Omit

工具效果

type Person = {
  name: string,
  age: number,
  temperature: number,
}
type UsefulInfo = Omit<Person, "age">;

// type UsefulInfo = {
//   name: string;
//   temperature: number;
// }

@思考6-1 如何实现一个内置Omit工具?

|

7. infer关键字 - 实现ReturnType

infer效果

type paramType<T extends (arg: any) => any> = T extends ((arg: infer U) => any) ? U : never;

type testFunc = (a: boolean) => number;

type testType = paramType<testFunc>; // boolean

@思考7-1 如何实现一个ReturnType工具?

|

8. 实现Record

type WaterBands = "NongFuSpring" | "Cestbon" | "Ganten";

const bottledWaterList = {
  "NongFuSpring": { net: 550 },
  "Cestbon": { net: 555 },
  "Ganten": { net: 560 },
}

风险点: 错写漏写bottledWaterList中的WaterBands品牌都不会报错,其属性net拼错也不会报错

此时需要借助Record实现bottledWaterList的类型定义

type WaterList = Record<WaterBands, { net: number }>

// type WaterList = {
//   NongFuSpring: {
//       net: number;
//   };
//   Cestbon: {
//       net: number;
//   };
//   Ganten: {
//       net: number;
//   };
// }

@思考8-1 如何实现一个Record工具?

|

9. 实战ts笔试题

github.com/LeetCode-Op…

|

参考答案

思考1-1

function printLiteral<T extends string|number>(arg: T) {
  console.log(arg);
}
// 限制类型为string或number
printLiteral(true) // 类型“true”的参数不能赋给类型“string | number”的参数

思考1-2

function printLiteral<T extends string|number>(...args: T[]) {
  console.log(...args);
}

思考2-1

function createInstance<T extends {new(): T}>(type: T): T {
  return new type();
}

思考3-1

type RPartial<T> = {
  [prop in keyof T]-?: T[prop]
}

思考4-1

type MPick<T, U extends keyof T> = {
    [k in U]: T[k]
}

思考5-1

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

思考5-2

type extractFunctionName<T> = {
  [k in keyof T]: T[k] extends Function ? k : never;
}[keyof T];

思考6-1

type MOmit<T, U extends keyof T> = MPick<T, MExclude<keyof T, U>>

思考7-1

type MReturnType<T extends (...arg: any) => any> = T extends (...arg: any) => infer U ? U : any;

思考8-1

type MRecord<T extends keyof string|number|symbol, U> = {
  [k in T]: U
}