typescript深入理解系列之函数重载:约束函数类型

400 阅读3分钟

写在最前:笔者从去年就开始接触学习ts,但是看了几次文档都朦朦的,所以本系列旨在分享学习ts路上一些较为深入的理解(结合一个实际例子去讲解)

函数重载

函数重载,我们可以这样理解,相同的函数名,但是参数(入参或返参)的类型或者个数不同。

function test(a: number): number;
function test(x): void {
  ...
}

可以看到上面的代码就是一个函数重载。

函数重载的概念比较简单,但是有一点我们需要注意下: 在定义重载的时候,一定要把最精确的定义放在最前面。

那函数重载有什么用呢,或者说有什么场景下用函数重载呢?

其实很简单,就是当你的函数参数类型有判断条件时,大白话就是,你想写个if else 给参数的类型时,这时候就需要用到函数重载了。

接下来,举个实际的例子。

约束函数参数类型

场景描述: 有这样一个函数,根据对象数组中对象的时间进行排序,然后时间的key可以自定义

先上代码:

function sortDatas(data: Record<string, unknown>[], timeKey = 'time') {
  return data.sort((a, b) => a[timeKey] - b[timeKey]);
};
// The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.ts(2362)

可以看到,这里报错了,因为 a[timeKey] 类型是unknown, 那你这里都用上减法了,肯定是知道类型的。这个我们先不管。

我们再梳理一下,函数的类型声明这里我们需要做到什么:

  1. data是对象数组
  2. timeKey是data里的对象的key
  3. 当没有自定义timeKey时,data里的对象需要有time这个key,且对应的值的类型为number
  4. 当自定义timeKey时,data里的对象需要有time这个key,且对应的值的类型为number

接下来我们拆解地来看如何实现:

// 1. 简单定义 就能做到
function sortDatas(data: Record<string, unknown>[], timeKey = 'time') {
  return data.sort((a, b) => a[timeKey] - b[timeKey]);
};
​
// 2. 没简单了,我们上泛型和keyof
function sortDatas<T extends Record<string, unknown>>(data: T[], timeKey?: keyof T) {
  const key = timeKey ?? 'time';
  return data.sort((a, b) => a[key] - b[key]);
}
​
// 3. 写不了...

可以看到,第三点我们就写不了了,因为我们没法在函数声明里写If else, 这时候函数重载就上场了,我们先把各种情况单独写好

// 3. 假设就是默认用time,即不传第二个参数
function sortDatas<T extends Record<'time', number>>(data: T[]) {
  const key = 'time';
  return data.sort((a, b) => a[key] - b[key]);
}
​
​
// 4. 假设就是传了第二个参数,这时候需要用第二个泛型K反向约束T
function sortDatas<T extends Record<K, number>, K extends string>(data: T[], timeKey: K) {
  const key = timeKey;
  return data.sort((a, b) => a[key] - b[key]);
}
​

到了这个时候,我们的代码已经组织的差不多了。 这时候我们来看最终的代码并加上一些测试例子:

export function sortData<T extends Record<'time', number>>(data: T[]): T[];
export function sortData<T extends Record<K, number>, K extends string>(data: T[], timeKey: K): T[];
export function sortData<T extends Record<K, number>, K extends string>(
  data: T[],
  timeKey?: K
): T[] {
  const key = timeKey ?? ('time' as keyof T); // 需要手动约束下time这个关键字
  return data.sort((a, b) => a[key] - b[key]);
}
​
​
sortData([{ a: 123 }]); // TS 报错, 因为没有a这个关键词
sortData([{ time: '123' }]); // TS 报错,因为 '123' 不是 number 类型
sortData([{ time: 123 }]);
sortData([{ a: 123 }], 'a');

优化我们的函数重载声明

可以参考一下Dayjs

interface DayjsTimezone {
    (date: ConfigType, timezone?: string): Dayjs
    (date: ConfigType, format: string, timezone?: string): Dayjs
}

const tz: DayjsTimezone