类型"可分配给类型"T"的约束,但"T"可以使用约束"RGT"的不同子类型进行实例化



我在 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必须完全RGRGIC"。 它所暗示的是TRGT兼容,这意味着它是RGT的子类型可分配给。 有些亚型RGTRGRGIC更具特异性,例如:

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

看起来不错。 现在编译器接受whaaRG,输出类型也是RG。 如果您尝试访问hmmbrainCellCount属性,编译器现在抱怨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)编译器推断TWhaa,然后计算T extends RG ? RG: RGICRG。 因此hmm是所需的RG类型,不知道它具有brainCellCount属性。 您会在预期的地方收到编译器错误。

>游乐场链接到代码

相关内容

  • 没有找到相关文章

最新更新