【问题标题】:TypeScript: TS2352: 'Type' could be instantiated with an arbitrary type which could be unrelated to 'TreeNode<Type>'TypeScript:TS2352:“Type”可以用与“TreeNode<Type>”无关的任意类型实例化
【发布时间】:2021-04-29 14:52:42
【问题描述】:

我有以下代码:

abstract class TreeNode<Type extends TreeNode<Type>> {
    children: Type[];

    protected constructor() {
        this.children = [];
    }
    
    addSelfToParent(parent: Type) {
        parent.children.push(this as Type);
    }

    // ...
}

typescript 编译器在 this as Type 部分发出以下错误。

TS2352: Conversion of type 'this' to type 'Type' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  'this' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint 'TreeNode<Type>'.
    Type 'TreeNode<Type>' is not comparable to type 'Type'.
      'Type' could be instantiated with an arbitrary type which could be unrelated to 'TreeNode<Type>'.

当我用this as unknown as Type 替换零件时,错误消失了。

我对错误消息中的“无关”部分特别困惑。

问题:它怎么可能是“不相关”的类型? Type 被定义为TreeNode&lt;Type&gt; 的扩展类型。

TypeScript 版本是 4.1.5。

更新 1

添加示例代码以回答 Linda Paiste 的问题。

首先,带有更多示例代码的原始类代码(并修复了as 错误)。

   abstract class TreeNode<Type extends TreeNode<Type>> {
        children: Type[];

        protected constructor() {
            this.children = [];
        }

        addSelfToParent(parent: Type) {
            parent.children.push(this as unknown as Type);
        }

        // ...
    }

    class TreeNodeWithValue extends TreeNode<TreeNodeWithValue> {
        value: number;

        constructor() {
            super();
            this.value = 1;
        }
    }

    function sumValuesOfChildren(node: TreeNodeWithValue) {
        let sum = 0;
        for (let i = 0; i < node.children.length; i++)
            sum += getChildValue(node.children[i]);
        console.log(sum);
    }

    function getChildValue(node: TreeNodeWithValue) {
        return node.value;
    }

在这里,可以调用 getChildValue() 而不会出现任何打字错误。

但是当 'extends ...' 被如下删除时,


    abstract class TreeNode<Type>
    {
        children: TreeNode<Type>[];

        protected constructor() {
            this.children = [];
        }

        addSelfToParent(parent: TreeNode<Type>) {
            parent.children.push(this);
        }
    }

    class TreeNodeWithValue extends TreeNode<TreeNodeWithValue> {
        value: number;

        constructor() {
            super();
            this.value = 1;
        }
    }

    function sumValuesOfChildren(node: TreeNodeWithValue) {
        let sum = 0;
        for (let i = 0; i < node.children.length; i++)
            sum += getChildValue(node.children[i]);
        console.log(sum);
    }

    function getChildValue(node: TreeNodeWithValue) {
        return node.value;
    }

Typescript 编译器在 getChildValue(node.children[i]) 上发出以下错误。

TS2345: Argument of type 'TreeNode<TreeNodeWithValue>' is not assignable to parameter of type 'TreeNodeWithValue'.
   Property 'value' is missing in type 'TreeNode<TreeNodeWithValue>' but required in type 'TreeNodeWithValue'

我可以通过像这样向下转换来避免错误:getChildValue(node.children[i]) as TreeNodeWithValue

但我不希望这种沮丧。向下转换有效地消除了泛型本身的意义。为此,我不得不添加(丑陋的)“扩展……”。

更新 2

kaya3的建议大抵可行,但如果在子类中添加处理children属性的方法就不行了,如下:

abstract class TreeNode {
    children: this[];

    protected constructor() {
        this.children = [];
    }

    addSelfToParent(parent: this) {
        parent.children.push(this);
    }
}

class TreeNodeWithValue extends TreeNode {
    value: number;

    constructor() {
        super();
        this.value = 1;
    }

    addChild() {
        this.children.push(new TreeNodeWithValue());  // ERROR
    }
}

错误如下:

TS2345: Argument of type 'TreeNodeWithValue' is not assignable to parameter of type 'this'.
  'TreeNodeWithValue' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'TreeNodeWithValue'.

【问题讨论】:

  • extends 真的有必要吗? abstract class TreeNode&lt;Type&gt; { children: TreeNode&lt;Type&gt;[]; addSelfToParent(parent: TreeNode&lt;Type&gt;) { parent.children.push(this); } } 根本不需要任何断言
  • @LindaPaiste 谢谢。我添加了相关的示例代码和一些解释。请参阅“更新”部分。
  • 顺便说一句,您的代码中有很多突变。 TS 不适合他们。

标签: typescript


【解决方案1】:

你想要的是孩子和父母有相同的类型,所以你应该使用this as a polymorphic type而不是泛型类型参数:

abstract class TreeNode {
    children: this[];

    protected constructor() {
        this.children = [];
    }

    addSelfToParent(parent: this) {
        parent.children.push(this);
    }
}

class TreeNodeWithValue extends TreeNode {
    value: number;

    constructor() {
        super();
        this.value = 1;
    }
}

Playground Link

您的泛型类型不太适用,因为Type extends TreeNode&lt;Type&gt; 不一定意味着Type 等于 TreeNode&lt;Type&gt;。所以this 的类型为TreeNode&lt;Type&gt;,你想在预期Type 的地方使用它,但前者是后者的超类型,因此如果没有类型断言,它就不能赋值。另一方面,如果将children 的类型更改为TreeNode&lt;Type&gt;[],那么当您从数组中获取一个孩子时,它是Type 的超类型,因此您无法按照您的意愿使用它。

【讨论】:

  • 谢谢。使用多态类型看起来比泛型 + 转换更干净。顺便说一句,关于类型关系,我也希望 Type 是 TreeNode 的子类型。但是打字稿编译器另有说明。它在错误消息中说 Type 和 TreeNode 可以是“不相关的”。因此,必须使用“硬”强制转换 - 这与 Type 一样未知,而不是“正常”向下强制转换 - 这作为 Type。我想知道原因。它们怎么可能是不相关的类型?
  • 它们不能不相关,因为 Type 被声明为扩展 TreeNode&lt;Type&gt; 所以它必须是一个子类型。 Typescript 只是给出了一个误导性的错误信息。
  • 我已经尝试过您的建议,并且它大部分都有效,但是在子类中添加方法后它会中断。请在问题中查看我的更新 2。
  • 您的更新代码失败,因为如果您编写TreeNodeWithValue 的子类,那么它的children 数组必须只包含该子类的实例,但您的代码会添加TreeNodeWithValue 的实例到此即使是子类的数组。这是您添加的方法的逻辑问题;您可以编写let cls = Object.getPrototypeOf(this);,然后使用new cls(this) 构造正确类的子类,但请注意,在这种情况下,层次结构中的每个类都需要具有具有该签名的构造函数。或者,放弃继承并改用组合。
  • 顺便说一句,在您收到问题的原始答案后,大幅更改问题的要求是不好的。这会使现有答案无效,并使您的问答对搜索相同问题的人的用处降低。
【解决方案2】:

在 TS 中你可以显式输入this

例子:

abstract class TreeNode<Type extends TreeNode<Type>> {
  children: Type[];

  protected constructor() {
    this.children = [];
  }
  // special syntax of typing THIS
  addSelfToParent(this: Type, parent:Type) {
    parent.children.push(this); // ok
  }

}

如您所见 - 没有类型断言。

顺便说一句,你会发现this articlequestion 很有趣。

【讨论】:

  • 谢谢,但它使类中的其他方法(尽管示例代码中不存在)无法调用该方法(没有一些强制转换)。 Typescript 发出以下错误——TS2684:“this”类型的“this”上下文不可分配给“Type”类型的方法“this”。 “this”可分配给“Type”类型的约束,但“Type”可以用约束“TreeNode”的不同子类型来实例化。类型“TreeNode”不可分配给类型“Type”。 'Type' 可以用与 'TreeNode' 无关的任意类型实例化。
  • 没问题,你发布了全部代码吗?
  • 问题中的代码是人工的,可以用最少的代码重现(奇怪的)错误。在实际代码中,该类还有其他几个方法。
猜你喜欢
  • 2020-11-28
  • 1970-01-01
  • 2021-01-19
  • 2021-05-11
  • 1970-01-01
  • 2021-10-26
  • 2021-10-08
  • 2021-09-21
  • 1970-01-01
相关资源
最近更新 更多