chengaofeng
发布于 2024-07-23 / 24 阅读
0
0

Typescript函数重载

为什么要用函数重载?

enum MessageType {
  "Image" = "Image",
  "Audio" = "Audio",
}

type Message = {
  id: number;
  type: MessageType;
  content: string;
};

const messages: Message[] = [
  {
    id: 1,
    type: MessageType.Image,
    content: "图片消息",
  },
  {
    id: 2,
    type: MessageType.Audio,
    content: "音频消息",
  },
];

function filterByTypeOrId(p: MessageType | number) {
  if (typeof p === "number") {
    return messages.find((message) => message.id === p);
  } else {
    return messages.filter((message) => message.type === p);
  }
}
// 可以看到函数的类型:function filterByTypeOrId(p: MessageType | number): Message | Message[] | undefined
filterByTypeOrId(1); // Message | Message[] | undefined  推断不准确,我们希望返回的是Message
filterByTypeOrId(MessageType.Image); // Message | Message[] | undefined  推断不准确,我们希望返回的是Message[]
// 这里的问题是,TypeScript无法根据参数的类型来推断返回值的类型,因为p的类型是MessageType | number,所以返回值的类型是Message | Message[] | undefined
// 我们只能使用断言来指定类型,但是这样会使代码变得不够安全,这个时候我们可以使用函数重载来解决这个问题

函数重载的定义:一组具有相同名字,不同参数列表的和返回值无关并且具有一个实现签名和一个或多个重载签名的函数

enum MessageType {
  "Image" = "Image",
  "Audio" = "Audio",
}

type Message = {
  id: number;
  type: MessageType;
  content: string;
};
 
const messages: Message[] = [
  {
    id: 1,
    type: MessageType.Image,
    content: "图片消息",
  },
  {
    id: 2,
    type: MessageType.Audio,
    content: "音频消息",
  },
];

// 重载签名
function filterByTypeOrId(p: MessageType): Message[];
function filterByTypeOrId(p: number): Message | undefined;
// 实现签名
function filterByTypeOrId(p: MessageType | number) {
  if (typeof p === "number") {
    return messages.find((message) => message.id === p);
  } else {
    return messages.filter((message) => message.type === p);
  }
}

filterByTypeOrId(MessageType.Image); // Message[]
filterByTypeOrId(1); // Message | undefined

export {};

泛型函数重载

// 快速排序
function quickSort<T>(arr: T[]): T[] {
  if (arr.length <= 1) {
    return arr;
  }
  const pivot = arr[0];
  const left = [];
  const right = [];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat(pivot, quickSort(right));
}

const arr = [85, 24, 63, 45, 17, 31, 96, 50];
console.log(quickSort(arr)); // [17, 24, 31, 45, 50, 63, 85, 96]
const arr2 = ["c", "a", "e", "b", "d"];
console.log(quickSort(arr2)); // ["a", "b", "c", "d", "e"]

// 中文排序
function sortChinese(arr: string[]): string[] {
  return arr.sort((a, b) => a.localeCompare(b, "zh-CN"));
}

const arr3 = ["张三", "李四", "王五", "赵六"];
console.log(sortChinese(arr3)); // [ '李四', '王五', '张三', '赵六' ]

// 判断数组中是否包含中文
function isChinese(strArr: string[]): boolean {
  const reg = /^[\u4e00-\u9fa5]+$/g;
  return strArr.some((str) => reg.test(str));
}

const arr4 = ["张三", "李四", "王五", "赵六"];
console.log(isChinese(arr4)); // true

// 字符串自排序
function sortString(str: string): string {
  return str.split("").sort().join("");
}

const str = "bcadef";
console.log(sortString(str)); // "abcdef"

// 排序方法
// 不使用函数重载时,无法推断出返回值类型
// 使用泛型函数重载
function sort<T>(data: T): T;
// 由于有了函数重载,这里的返回值类型可以是any[] | string | undefined,也可以不写
// 如function sort<T>(data: T): {}, typeScript会自动推断出返回值类型
function sort<T>(data: T): any[] | string | undefined {
  if (Array.isArray(data)) {
    if (isChinese(data)) {
      return sortChinese(data);
    } else {
      return quickSort(data);
    }
  } else if (typeof data === "string") {
    return sortString(data);
  }
  return undefined;
}
const arr5 = [85, 24, 63, 45, 17, 31, 96, 50];
sort(arr5); // 推断出number[]
const arr6 = ["c", "a", "e", "b", "d"];
sort(arr6); // 推断出string[]
const arr7 = ["张三", "李四", "王五", "赵六"];
sort(arr7); // 推断出string[]
const str2 = "bcadef";
sort(str2); // 推断出string
// 以上就是使用泛型函数重载的好处,可以根据不同的参数类型推断出不同的返回值类型

export {};

泛型工厂函数类型

面向切面编程的一个基础

定义:可以代表任意一个类构造函数的函数类型

TypeScript 中的泛型工厂函数类型是一种使用泛型来创建不同类型实例的函数类型。这种模式允许函数根据传入的类型参数动态地返回不同类型的实例,增加了代码的复用性和灵活性。通过泛型,你可以在调用函数时指定具体的类型,使得函数能够处理多种数据类型,同时保持类型安全。

下面是一个简单的泛型工厂函数类型的例子:

// 定义一个泛型接口

interface Constructable<T> {

    new (...args: any[]): T; // 构造签名

}

// 定义一个泛型工厂函数,它接受一个类型和一些参数,然后返回这个类型的实例

function createInstance<T>(Type: Constructable<T>, ...args: any[]): T {

    return new Type(...args);

}

class MyClass {

    constructor(public name: string) {}

}

// 使用泛型工厂函数创建 MyClass 的实例

const myInstance = createInstance(MyClass, "Test");

console.log(myInstance.name); // 输出: Test

在这个例子中:

  • Constructable<T> 是一个泛型接口,定义了一个构造函数签名。这个签名接受任意数量的参数并返回类型 T 的实例。

  • createInstance 是一个泛型工厂函数,它接受一个 Constructable<T> 类型的构造函数和一些参数,然后使用这些参数通过 new 关键字创建并返回一个类型为 T 的实例。

  • MyClass 是一个简单的类,带有一个构造函数和一个 name 属性。

  • 最后,我们使用 createInstance 函数创建了 MyClass 的一个实例,并传入了必要的参数。

泛型工厂函数类型的优势在于它的通用性和灵活性。你可以用同一个工厂函数创建不同类型的对象,只要这些类型满足工厂函数的类型约束。这种模式在需要根据不同条件创建不同类型对象的场景中非常有用,同时也有助于减少代码重复。

交叉类型和通用交叉方法

TypeScript 的交叉类型(Intersection Types)是一种高级类型,它允许你将多个类型合并为一个类型。这意味着你可以把多个现有的类型叠加到一起,得到一个包含所有类型成员的单一类型。交叉类型在你需要将对象或函数的多个类型特征组合到一起时非常有用。

交叉类型的基本用法

交叉类型使用 & 符号定义,如下所示:

type LeftType = {

    id: number;

    left: string;

};

type RightType = {

    id: number;

    right: string;

};

type IntersectionType = LeftType & RightType;

// 这个变量同时拥有 LeftType 和 RightType 的所有属性

let intersection: IntersectionType = {

    id: 1,

    left: "left value",

    right: "right value"

};

在这个例子中,IntersectionType 是一个交叉类型,它包含了 LeftTypeRightType 的所有属性。因此,一个 IntersectionType 类型的对象需要同时满足 LeftTypeRightType 的结构。

通用交叉方法

在 TypeScript 中,你可以使用泛型与交叉类型结合来创建更通用和可复用的交叉类型方法。这种方法可以动态地将多个类型合并为一个类型。

下面是一个使用泛型创建交叉类型的函数的例子:

function combine<T, U>(first: T, second: U): T & U {

    return { ...first, ...second };

}

const obj1 = { name: "Alice" };

const obj2 = { age: 25 };

const combined = combine(obj1, obj2);

console.log(combined); // 输出: { name: "Alice", age: 25 }

在这个例子中,combine 函数接受两个参数 firstsecond,它们分别是泛型 TU 的实例。函数返回一个新对象,这个对象是通过展开运算符将 firstsecond 的所有属性合并到一个对象中来创建的。因此,返回的对象类型是 T & U,即 firstsecond 类型的交叉。

通过这种方式,你可以灵活地将任意数量和类型的对象合并,创建出满足所有原始对象类型约束的新对象。这种通用交叉方法在处理多个来源的数据或属性时非常有用。

合并多个对象

function combineObjects<T extends object[]>(
  ...objects: T
): T extends [infer U] ? U : UnionToIntersection<T[number]> {
  return Object.assign({}, ...objects) as any;
}

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

// 使用示例
const obj1 = { a: 1 };
const obj2 = { b: "text" };
const obj3 = { c: true };

const result = combineObjects(obj1, obj2, obj3);
// result 的类型为 { a: number; } & { b: string; } & { c: boolean; },即 { a: number; b: string; c: boolean; }

这里的 combineObjects 函数使用了泛型 T 来接受一个对象数组,其中每个对象都扩展自 object 类型。函数返回值的类型是通过条件类型和工具类型 UnionToIntersection 计算得到的交叉类型。如果 T 是单个类型的数组(即只有一个元素的数组),它直接返回该类型 U;否则,使用 UnionToIntersection 工具类型将联合类型转换为交叉类型。

UnionToIntersection<U> 工具类型的核心在于使用条件类型和分布式条件类型的特性,将联合类型转换为交叉类型。这是通过将联合类型 U 映射到一个函数签名的返回类型,然后通过推断(infer)这些函数签名的联合类型来实现的,最终得到交叉类型 I

这种方法允许你动态地合并多个对象,并且能够保持 TypeScript 的类型安全,确保返回值包含所有输入对象的类型信息。


评论