如何在 TypeScript 中对两个重载函数使用相同的参数?

How to use the same parameters with two overloaded functions in TypeScript?

我试图在 typescript 的重载函数中调用另一个重载函数。由于类型 Func2 与类型 Func1 相同,因此可以肯定从 func2 传递到 func1 的参数将被正确键入。然而,打字稿似乎无法接受,并抛出一个错误。

type Func1 = {
  (a: string, b: string): void
  (a: undefined, b: undefined): void
}
const func1: Func1 = (a, b) => {
  console.log(a, b)
}

type Func2 = {
  (a: string, b: string): void
  (a: undefined, b: undefined): void
}
const func2: Func2 = (a, b) => {
  func1(a, b)
  console.log(a, b)
}

/*
No overload matches this call.
  Overload 1 of 2, '(a: string, b: string): void', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
      Type 'undefined' is not assignable to type 'string'.
  Overload 2 of 2, '(a: undefined, b: undefined): void', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'undefined'.
      Type 'string' is not assignable to type 'undefined'.
*/

我该如何解决?

编辑: 这是我尝试的实际实现:

type ParseRandomArgs = {
  (a: undefined, b: undefined): [number, number]
  (a: number, b: undefined): [number, number]
  (a: number, b: number): [number, number]
  (a: [number, number], b: undefined): [number, number]
  (a: [number], b: undefined): [number, number]
}

type RandomNumber = {
  (a: undefined, b: undefined): number
  (a: number, b: undefined): number
  (a: number, b: number): number
  (a: [number, number], b: undefined): number
  (a: [number], b: undefined): number
}

const isNullish = (value: any) => value === undefined || value === null

const parseRandomArgs: ParseRandomArgs = (a, b) => {
  if (Array.isArray(a)) {
    if (a.length === 2) return a
    return [0, a[0]]
  }
  else if (isNullish(b)) return [0, isNullish(a) ? 1 : a as number]
  else return [a as number, b as number]
}

const randomFloat: RandomNumber = (a, b) => {
  let [min, max] = parseRandomArgs(a, b) // [min, max]
  return Math.random() * (max - min) + min
}

const randomInt: RandomNumber = (a, b) => {
  let [min, max] = parseRandomArgs(a, b) // [min, max]
  min = Math.ceil(min)
  max = Math.floor(max)
  return Math.floor(Math.random() * (max - min) + min)
}

我认为你尝试制作太干净的代码

考虑第二个例子(随机数):typescript实际上加入了所有可能的参数变体

const randomInt: RandomNumber = (a, b) => {
   //a: number | [number, number] | [number] | undefined
   //b: number | undefined
}

您的 RandomNumber/ParseRandomArgs 定义只能用于调用验证

所以第一个解决方案是用所有参数的联合扩展 ParseRandomArgs

type ParseRandomArgs = {
 ...
 (a: [number]| [number, number] | number | undefined, b: number | undefined): [number, number]
}

其次,将此类联合移动到受保护的函数并将 ParseRandomArgs 转换为代理

const _parseRandomArgs = function(a: [number] | [number,number] | number | undefined ,b?: number | undefined): [number,number]{
   ...
}

const parseRandomArgs: ParseRandomArgs = (a, b) => {
  return _parseRandomArgs(a,b);
}

const randomFloat: RandomNumber = (a, b) => {
  let [min, max] = _parseRandomArgs(a, b) // [min, max]
  return Math.random() * (max - min) + min
}

并且我建议您将所有未定义的参数设为可选

完整代码在Playground

正如@Mike 所提到的,使 undefined 参数可选使重载更容易解决。这应该有效:

type RandomGenerator = {
    (start: number, end?: number): number
    (range: [number, number]): number;
    (range: [number]): number;
}

const randomFloat: RandomGenerator = (start, end?) => {
    const [min, max] = parseRandomArgs(start, end as number | undefined);
    return computeRandom(min, max);
}

const randomInt: RandomGenerator = (start, end?) => {
    const [min, max] = parseRandomArgs(start, end as number | undefined);
    return Math.floor(computeRandom(min, max));
}

const computeRandom = (min: number, max: number): number => {
    return Math.random() * (max - min) + min;
}

const parseRandomArgs = (first: number | [number, number] | [number], second: number | undefined): [number, number] => {
    let args: [number, number];
    if (Array.isArray(first) && first.length === 1) {
        args = [0, first[0]];
    } else if (Array.isArray(first) && first.length === 2) {
        args = first;
    } else {
        args = Number.isFinite(second) ? [first, second as number] : [0, first];
    }
    const [min, max] = args;
    return [Math.ceil(min), Math.floor(max)];
}

您不需要复制参数,甚至不需要使用任何断言:您可以使用 type predicate 让编译器区分空类型。

通过为参数使用带标签的元组,您甚至可以通过编辑器中有用的 IntelliSense 建议获得更好的开发人员体验:

TS Playground

type Fn<
  Params extends unknown[] = any[],
  Result = any,
> = (...args: Params) => Result;

type CommonParams = [
  [max?: number],
  [min: number, max?: number],
  [minAndMax: [min: number, max: number]],
  [maxOnly: [max: number]],
];

type ParseFn = Fn<CommonParams[number], [number, number]>;
type RandomFn = Fn<CommonParams[number], number>;

function isNullish <T>(value: T): value is Exclude<T, NonNullable<T>> {
  return value === undefined || value === null;
}

const parseRandomArgs: ParseFn = (...args) => {
  const [a, b] = args;
  if (Array.isArray(a)) return a.length === 2 ? a : [0, a[0]];
  if (isNullish(a)) return [0, 1];
  return isNullish(b) ? [0, a] : [a, b];
};

const randomFloat: RandomFn = (...args) => {
  const [min, max] = parseRandomArgs(...args);
  return Math.random() * (max - min) + min;
};

const randomInt: RandomFn = (...args) => {
  let [min, max] = parseRandomArgs(...args);
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min) + min);
};