我在 TypeScript 中为此错误消息苦苦挣扎。我已经查看了以下SO答案,但我根本不了解解决方案。
- https://stackoverflow.com/a/59363875/765987
- https://stackoverflow.com/a/61162701/765987
这是我的代码的精简版本,其中包含错误:
interface R {
field: string;
operator: string;
value: any;
}
interface RG {
combinator: 'and'|'or';
rules: (R|RG)[];
}
interface RGIC {
rules: (RGIC|R|string)[];
}
type RGT = RG | RGIC;
const f = <T extends RGT>(r: T): T => {
if ('combinator' in r) {
return { combinator: 'and', rules: [] }; // <-- TS error here
}
return { rules: [] }; // <-- TS error here
}
我想表达的是,如果r
是类型RG
,那么函数f
将返回一个也是类型RG
的对象。如果r
的类型是RGIC
,则函数f
将返回一个也是类型RGIC
的对象。但这个错误让我感到困惑。
TS 操场链接。
您遇到的问题是,T extends RGT
形式的泛型约束并不意味着"T
必须完全RG
或RGIC
"。 它所暗示的是T
与RGT
兼容,这意味着它是RGT
的子类型或可分配给。 有些亚型RGT
比RG
或RGIC
更具特异性,例如:
interface Whaa {
combinator: 'or',
rules: R[],
brainCellCount: number;
}
const whaa: Whaa = {
combinator: 'or',
rules: [],
brainCellCount: 86e9
}
Whaa
接口可分配给RG
;每个Whaa
都是一个有效的RG
。 但是Whaa
有一个必须"or"
的combinator
,并且它还具有number
类型的附加brainCellCount
属性。
如果您的函数f
具有如下所示的调用签名:
declare const f: <T extends RGT>(r: T) => T;
您是说f
的返回类型将与传入r
的类型完全相同。 因此,以下调用还将生成类型Whaa
的结果:
const hmm = f(whaa);
// const hmm: Whaa
如果是这样,那么这将很好:
hmm.brainCellCount.toFixed(2); // no compiler error
但是你实现f
就不好了:
const f = <T extends RGT>(r: T): T => {
if ('combinator' in r) {
return { combinator: 'and', rules: [] }; // error
}
return { rules: [] }; // error
}
hmm.brainCellCount.toFixed(2); // no compiler error
// BUT AT RUNTIME: brainCellCount is undefined !!
现在这个错误应该有希望有意义。 您返回的值{combinator: "and", rules: []}
并声称它与r
的类型相同T
。 但是编译器说这样的值虽然可以分配给RGT
,但可能无法分配给T
。
有不同的方法可以解决这个问题。 既然你真的只想说,如果输入是RG
那么输出也是如此,如果输入是RGIC
那么输出也是如此,那么最简单的方法可能是使f
成为具有两个调用签名的重载函数:
// call signatures
function f(r: RG): RG;
function f(r: RGIC): RGIC;
// implementation
function f(r: RGT): RGT {
if ('combinator' in r) {
return { combinator: 'and', rules: [] };
}
return { rules: [] };
}
这样编译时没有错误。 尽管您应该小心:它并不能真正保证类型安全;编译器不会捕获像if ('cobminator' in r)
这样的错误,因为重载实现是松散检查的。只要每个return
语句都适用于某个调用签名,编译器就会很高兴,即使你得到错误的。 因此,只需仔细检查和三重检查您的实现是否适用于每个调用签名。 确实如此,所以这很好。
让我们看看当我们调用它时它是如何工作的:
const hmm = f(whaa);
// const hmm: RG
hmm.brainCellCount //<-- now this is a compiler error
看起来不错。 现在编译器接受whaa
是RG
,输出类型也是RG
。 如果您尝试访问hmm
的brainCellCount
属性,编译器现在抱怨RG
上没有这样的属性。
另一种类似的处理方法是继续使用泛型函数,但让返回类型是条件类型,如下所示:
const f = <T extends RGT>(r: T): T extends RG ? RG : RGIC => {
if ('combinator' in r) {
return { combinator: 'and', rules: [] } as any;
}
return { rules: [] } as any;
}
该返回类型T extends RG ? RG : RGIC
捕获了将返回类型扩大到RG
的意图,如果输入是RG
的某个子类型,或者以其他方式扩大到RGIC
。 请注意,在两个return
语句中,我都必须使用类型断言来any
(我本可以as T extends RG ? RG : RGIC
代替)来编译它。 这是因为编译器实际上无法理解何时可以根据未指定的泛型类型参数将某些特定值分配给条件类型。 在 microsoft/TypeScript#33912 上有一个开放的功能请求,要求那里有更好的东西,但我不知道它何时或是否会改进。目前,类型断言或重载是要走的路。
无论如何,您可以看到它的行为相同:
const hmm = f(whaa);
// const hmm: RG
hmm.brainCellCount // error
当你调用f(whaa)
编译器推断T
是Whaa
,然后计算T extends RG ? RG: RGIC
是RG
。 因此hmm
是所需的RG
类型,不知道它具有brainCellCount
属性。 您会在预期的地方收到编译器错误。