【问题标题】:Typescript generic constrain打字稿通用约束
【发布时间】:2020-07-29 07:13:34
【问题描述】:

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

这是我尝试过的。

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 | 的子类型?号码 | boolean' 让我感到困惑。它们不是已经是原始类型了吗?

【问题讨论】:

标签: typescript


【解决方案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&lt;string&gt;,对吧?

哎呀,不。运行代码,您将收到运行时错误和类似TypeError: v.toUpperCase is not a function 的消息。因为在运行时,v 将是12345,从bar.getValue() 返回的实际值。而12345 是一个number,它没有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 typestype "foo", @ 987654323@ 就像 type 123,甚至是布尔字面量类型 truefalse(提到了 here 和可能的其他地方)。这些文字类型代表特定的值。所以"a""b"string的子类型,12number的子类型,truefalse是@98765438的子类型.

所以事实上,当您调用 getSomething() 时,将第二个参数作为字符串、数字或布尔文字,这就是 T 的推断结果:

const p = getSomething("qux", "peanut butter and jelly");
// const p: Promise<"peanut butter and jelly">

所以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 方法,因为它可能是numberboolean

您可能需要 getSomething() 是通用的,但在这种情况下,bar.getValue() 的作用真的很重要,并且可能需要修改 bar.getValue() 的签名,或者在 @ 内部某处的某个明智的 type assertion 987654403@ 你负责验证编译器无法验证的内容,并在运行时证明你的断言不真实时处理后果。您对return foo === undefined ? defaultValue : foo as T; 的回答不太可能是正确的断言,尤其是考虑到文字类型。不过,我不会进一步推测这种方法。可以这么说,当您使用类型断言消除编译器错误时,您需要仔细考虑您所做的声明。


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

Playground link to code

【讨论】:

    【解决方案2】:

    我必须投 foo as T

    改变

    return foo === undefined ? defaultValue : foo;
    

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

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

    【讨论】:

    • 参见上面的评论,解释为什么这种强制转换可能有害
    猜你喜欢
    • 2021-11-08
    • 2018-03-12
    • 2018-09-17
    • 2018-03-15
    • 2020-03-27
    • 2021-11-06
    • 2019-09-23
    • 2021-05-06
    • 1970-01-01
    相关资源
    最近更新 更多