typescript 有没有一种方法来检查一个类型是否未定义或另一个特定的值来确定函数的返回类型?

zi8p0yeb  于 2023-05-08  发布在  TypeScript
关注(0)|答案(2)|浏览(110)

有没有办法做到下面这样的事情?目标是bar()返回一个字符串,而bar(10)返回一个数字。

function bar<T>(baz?: T): (T extends typeof undefined ? string : number)  {
    if (baz === undefined) {
        return '42'
    }
    return 42
}
ldfqzlk8

ldfqzlk81#

如果你想在TypeScript中编写一个函数,其中返回类型取决于参数的类型,那么你要么需要使它成为一个带有调用签名的generic函数,该函数用一个或多个类型参数来表达内容,要么需要使它成为一个带有多个调用签名的重载函数。你使用哪一个是你的用例的问题。
您的示例展示了一种尝试使用泛型的方法,因此让我们先来看看它。如果参数丢失或未定义,则输出应为string;如果参数存在并定义,则输出应为number。实际上,为此编写一个通用的调用签名是非常烦人的。这些输入和输出之间没有简单的内置Map。你可以这样写:

declare function bar<T extends [baz?: unknown]>(...[baz]: T):
  T extends [undefined?] ? string : number;

其中T对应于rest参数的通用元组类型,返回类型为conditional type。因此,当有人调用bar()时,T被推断为空元组[]。如果有人调用bar(true),则T被推断为一元组[boolean](或者[true])。然后如果T可赋值给[undefined?](意味着长度为零的元组,或者长度为1的元组包含undefined),那么就得到string,否则它是一个数字。
(Do注意undefined类型已经存在,你不需要写typeof undefined
这对呼叫者来说足够了:

const x = bar(); // const x: string;
console.log(x.repeat(2)); // "4242"
const y = bar(true); // const y: number;
console.log(y.toFixed(2)); // "42.00"

但是当你实现它时,你会得到警告,因为泛型条件类型超出了编译器的推理能力:

function bar<T extends [baz?: unknown]>(...[baz]: T):
  T extends [undefined?] ? string : number {
  if (baz === undefined) {
    return '42'; // error! 
    // 'string' is not assignable to 'T extends [undefined?] ? string : number'.
  }
  return 42 // error! 
  // 'number' is not assignable to 'T extends [undefined?] ? string : number'.
}

这基本上是microsoft/TypeScript#33912中描述的TypeScript的一个缺失功能。现在,您必须通过使用类型Assert之类的东西来消 debugging 误。它不再是类型安全的(如果你把return 42return '42'弄混了,它不会被捕获),但它至少没有那么吵:

function bar<T extends [baz?: unknown]>(...[baz]: T):
  T extends [undefined?] ? string : number {
  if (baz === undefined) {
    return '42' as T extends [undefined?] ? string : number // okay
  }
  return 42 as T extends [undefined?] ? string : number // okay
}

但这一点都不符合人体工程学。调用签名是复杂和令人困惑的,并且实现需要Assert等。太丑了
另一方面,您可以将其编写为一个重载函数,并按照您希望解析的顺序为您希望支持的每种调用类型提供一个调用签名。然后在所有的调用签名之后,你可以用一个稍微宽松的调用签名来实现这个函数。以下是调用签名:

// call signatures
function bar(baz?: undefined): string;
function bar(baz: unknown): number;

这些已经比上面那个奇怪的通用条件REST元组好得多了。然后,您使用“覆盖”所有调用签名的宽输入和输出类型来实现它:

// implementation
function bar(baz?: unknown): string | number {
  if (baz === undefined) {
    return '42'
  }
  return 42
}

这里的实现中没有错误。请注意,这并不是因为编译器在类型检查方面更聪明。如果你把return 42return '42'弄混了,它仍然不会抓住错误。但是重载体的检查比一般的条件返回类型要宽松,所以至少写起来更符合人体工程学。
从呼叫方的Angular 来看,它也能很好地工作:

const x = bar();
console.log(x.repeat(2)); // "4242"
const y = bar(true);
console.log(y.toFixed(2)); // "42.00"

重载肯定有其缺点,但在这种情况下,这样做就不那么难看了。如果您只想支持对bar的具体调用,它应该可以很好地工作。
因此,在这种情况下,您可能希望使用重载函数,至少根据问题中给出的信息。
Playground链接到代码

goqiplq2

goqiplq22#

这是函数重载的一个用例:https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads

function bar(): string;
function bar(baz: number): number;
function bar(baz?: number): string | number {
    if (baz === undefined) {
        return '42'
    }
    return 42
}

const a = bar(); // string
const b = bar(10); // number

打字机Playground

相关问题