没有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() 返回的实际值。而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 类型的值。在上述情况下,T 是 string,因此您可以将错误解释为“您声称返回 string 但我所知道的是您返回的是 string | number | boolean,其中 可能是string,但也可能是number 或boolean,在这种情况下,您的声明不正确,可能会发生坏事”。
希望您了解为什么会出现此问题。 T 可以是string 或number 或boolean,它们都比联合类型string | number | boolean窄。您可以将T 分配给string | number | boolean,但反之则不行。
关于这个问题:“什么可能是string | number | boolean 的子类型让我感到困惑。它们不是已经是原始类型了吗?”好吧,string 是string | number | boolean 的子类型。联合 A | B 是其每个成员 A 和 B 的超类型。
此外,即使你只有 string 或 number 或 boolean,TypeScript 中也有这些的子类型:有 string literal types 像 type "foo", @ 987654323@ 就像 type 123,甚至是布尔字面量类型 true 和 false(提到了 here 和可能的其他地方)。这些文字类型代表特定的值。所以"a"和"b"是string的子类型,1和2是number的子类型,true和false是@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 方法,因为它可能是number 或boolean。
您可能需要 getSomething() 是通用的,但在这种情况下,bar.getValue() 的作用真的很重要,并且可能需要修改 bar.getValue() 的签名,或者在 @ 内部某处的某个明智的 type assertion 987654403@ 你负责验证编译器无法验证的内容,并在运行时证明你的断言不真实时处理后果。您对return foo === undefined ? defaultValue : foo as T; 的回答不太可能是正确的断言,尤其是考虑到文字类型。不过,我不会进一步推测这种方法。可以这么说,当您使用类型断言消除编译器错误时,您需要仔细考虑您所做的声明。
好的,希望对您有所帮助!祝你好运!
Playground link to code