打字稿通用约束

时间:2020-04-16 05:57:19

标签: typescript

我正在尝试编写一个通用的getter函数,给定一个仅返回字符串| boolean | number类型的键。因此,默认值应与返回值的类型相同

这是我尝试过的。

getSomething = async<T extends string | boolean | number>
    (key: string, defaultValue: T): Promise<T> => {
      const foo = bar.getValue("foo"); // return type is undefined|string|boolean|number
      return foo === undefined ? defaultValue : foo;
 }

我收到此错误,不确定是什么错误。

Type 'string | number | boolean | T' is not assignable to type 'T'.
  'string | number | boolean | T' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | number | boolean'.
    Type 'string' is not assignable to type 'T'.
      'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | number | boolean'.ts(2322)

可能是'string |的子类型。编号|布尔”是让我感到困惑的原因。他们不是原始类型吗?

2 个答案:

答案 0 :(得分:1)

没有bar的定义,这不是minimal reproducible example。由于我所知道的是bar.getValue()返回的类型为string | number | boolean | undefined的值,因此我将这样创建自己的bar

const bar = { getValue: (x: string): string | number | boolean | undefined => 12345 };

然后,我编写此代码,该代码没有编译器错误:

const str = Math.random().toString();
getSomething("baz", str).then(v => console.log(v.toUpperCase()));

如果您在TypeScript IDE中使用IntelliSense检查v的类型,它将为string。这是因为getSomething()的呼叫签名表示它将返回与第二个参数相同类型的Promise。我们传入了str,是string,所以我们得到了Promise<string>,对吗?

糟糕,不行。运行代码,您将收到运行时错误和类似TypeError: v.toUpperCase is not a function的消息。因为在运行时,v将是12345,因此从bar.getValue()返回的实际值。 12345number,没有toUpperCase方法。不知何故,我们陷入了number被误认为string的情况。错误在哪里?

这正是编译器警告您的地方:

return foo === undefined ? defaultValue : foo; // error!
// Type 'string | number | boolean | T' is not assignable to type 'T'.

TypeScript告诉您应该返回类型为T的值,但是编译器只能验证您是否返回类型为string | number | boolean | T的值。在上述情况下,Tstring,因此您可以将错误解释为“您声称要返回string,但我所知道的是您正在返回string | number | boolean,可能 string,但也许是numberboolean,在这种情况下,您的主张不正确,可能会发生不好的事情”。

希望您理解这是一个问题的原因。 T可以是stringnumberboolean,它们比联合类型string | number | boolean。您可以将T分配给string | number | boolean,反之亦然。


关于这个问题:“ string | number | boolean的子类型可能是令我感到困惑的东西。它们不是原始类型吗?”好吧,stringstring | number | boolean的子类型。联合A | B是其成员AB中每个成员的超类型。

此外,即使您只有stringnumberboolean,TypeScript中也有这些子类型:还有string literal types,如 type < / em> "foo"numeric literal types(如 type 123,甚至是布尔文字类型truefalse(提到{{ 3}},可能还有其他地方。这些文字类型表示特定的值。因此,类型"a""b"string的子类型,类型12number的子类型,类型{ {1}}和truefalse的子类型。

因此,实际上,当您使用第二个参数作为字符串,数字或布尔文字来调用boolean时,就可以推断出getSomething()的含义:

T

因此,const p = getSomething("qux", "peanut butter and jelly"); // const p: Promise<"peanut butter and jelly"> 不仅表示一个字符串的承诺(通常是不正确的),而且实际上表示了 specific 字符串p的一个承诺。糟糕!


那么我们如何解决此代码?好吧,这在很大程度上取决于您的用例。乍一看,我想也许它根本不应该是通用的,而只允许输入和输出都为"peanut butter and jelly"

string | number | boolean

该编译没有错误,然后出现运行时错误但没有编译器错误的早期代码现在为您提供了一个不错的编译器错误:

const getSomething2 = async (key: string, defaultValue: string | number | boolean):
    Promise<string | number | boolean> => {
    const foo = bar.getValue("foo"); 
    return foo === undefined ? defaultValue : foo;
}

现在知道值getSomething2("baz", str).then(v => console.log(v.toUpperCase())); // error! // ---------------------------------------------> ~~~~~~~~~~~ // Property 'toUpperCase' does not exist on type 'number'. v,您不能在其上调用string | number | boolean方法,因为它可能是toUpperCase或{{ 1}}。

您可能需要number才是通用名称,但是在那种情况下boolean所做的事情确实很重要,并且可能需要修改getSomething()的签名或明智的选择herebar.getValue()内部的某个地方,您将负责验证编译器无法执行的操作,并在运行时断言不正确的情况下处理后果。您对bar.getValue()的回答不太可能是正确的断言,尤其是考虑到文字类型时。我不会再对此方法进行推测。足以说明您需要仔细考虑使用类型断言使编译器错误消失时的声明。


好的,希望能有所帮助!祝你好运!

type assertion

答案 1 :(得分:0)

我必须投放foo as T

更改

return foo === undefined ? defaultValue : foo;

return foo === undefined ? defaultValue : foo as T;

我不确定为什么需要显式强制转换,因为foo肯定是T,因为它不是未定义的。 并且T被限制为类型string | boolean | number