【问题标题】:Extending JavaScript class properties in TypeScript implicitly在 TypeScript 中隐式扩展 JavaScript 类属性
【发布时间】:2020-10-12 00:04:00
【问题描述】:

假设我在 JavaScript 中有这些类,我正在尝试将它们转换为 TypeScript:

class Dog {
    tags = {
        barked: true,
    };
}

class Pug extends Dog {
    tags = {
        ...this.tags,
        snorted: true,
    };
}

const pug = new Pug();

但是,TypeScript 中的相同代码会产生以下错误:

error TS7022: 'tags' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

这里的错误很明显。我在重新定义tags 时使用this,所以TypeScript 很不高兴。

是否有其他方法可以安抚 TypeScript?到目前为止,我想到的唯一方法是按照错误提示进行操作,并明确键入它。然而,在更大的类型定义中,它会变得重复。

归根结底,我的类的属性是定义的键/值对的对象。并且每个子类都扩展了超类的对象并添加了额外的键/值。

【问题讨论】:

  • 上面写着... this.events,你的意思是...this.tags 对吧?
  • 没有。您使用this 并不难过,您没有声明属性的类型(也无法推断)令人难过。是的,明确输入!
  • @Jorjon 是的,很抱歉造成混乱。我正在修剪这个玩具示例的实际代码,却不小心留下了那个错误。

标签: javascript typescript class


【解决方案1】:

即使了解Pugtags 属性依赖于Dogtags 属性,编译器仍将tags = {...this.tags} 视为循环,因此无法计算找出它会是什么类型。如果需要,您可以显式注释 Pugtags 属性,例如:

class PugExplicit extends Dog {
  tags: { snorted: boolean, barked: boolean } = {
    ...this.tags,
    snorted: true,
  };
}

但这很麻烦。另一种继续的方法是assert tags 初始化器中的this 类型为Dog 而不是Pug

class Pug extends Dog {
  tags = {
    ...(this as any as Dog).tags,
    snorted: true,
  };
}

这具有推断Pugtags{snorted: boolean, barked: boolean} 类型的预期效果,而无需您显式注释它。 (我不知道为什么我需要通过anythis as any as Dog 断言,而不仅仅是this as Dog。但是,如果没有中间类型,编译器仍然会抱怨循环性。我猜this-ness 会一直存在一个断言而不是两个断言?)

Playground link to code

【讨论】:

  • 谢谢。是的,后一个选项更好。这实际上是我目前正在使用的,但是 as any 部分最近在我的代码库中导致了一个模糊的错误,所以我试图看看是否有更好的解决方案可以重构我的代码库。
【解决方案2】:

您将继承与组合混合在一起,这就是 Typescript 不喜欢您所做的事情的原因。

最优雅的方式,就是像 React 那样做。

每次扩展类时,都会将要添加的属性作为泛型传递。

出于某种原因,仅在使用构造函数时有效。但也许其他人可以为您提供比这更好的方法。

class Dog<T = {}> {
    tags = {
        barked: true,
    } as {barked: boolean} & T;
}

class Pug extends Dog<{snorted: boolean}> {
    constructor() {
      super();
      this.tags = {
        ...this.tags,
        snorted: true,
      };
    }
}

const pug = new Pug();

Typescript Plaground

【讨论】:

    【解决方案3】:

    您几乎没有错,但您需要传播 super 的标签而不是 this 的标签

    class Pug extends Dog {
        tags = {
            ...super.tags, // <-- change from 'this' to 'super'
            snorted: true,
        };
    }
    

    Playground link

    【讨论】:

    • 感谢您的建议。它确实感觉正确,但似乎 TypeScript 中存在错误。在此示例中,super.tags 将是 undefined 到运行时,因此实际上没有传播任何内容。要进行测试,只需在您链接的脚本末尾添加console.log(pug.tags.barked);。你会看到 TypeScript 认为它是 boolean 类型,而实际上它是 undefined 因为 Dog 的标签实际上并没有传播。 super 只能访问方法,不能访问属性。
    • 该死,我认为那会奏效。既然您指出super 只能访问方法,也许您可​​以在子类想要访问基类的字段时为它们提供一个受保护的方法,我已经尝试过here
    • 这不是一个很棒的解决方案,因为您需要将受保护的访问器添加到您打算扩展的每个类中,但这几乎是我能想到的全部
    【解决方案4】:

    首先,这是我要使用的简单解决方案

    class DogTags {
        barked = true;
    }
    
    class PugTags extends DogTags {
        snorted = true;
    }
    
    class Dog {
        tags = new DogTags();
    }
    
    class Pug extends Dog {
        tags = new PugTags();
    }
    

    说实话,我不是这个解决方案的忠实拥护者。但它正确地模拟了我想要的原始问题,而没有使用任何太“hacky”的东西,例如抛出一堆 as any TypeScript 强制。

    Jorjon 的answer 正确识别出我正在尝试通过继承来修复组合范例。如果可以的话,一个更好的、仅由继承驱动的答案应该是这样的:

    class Dog {
        tagBarked = true;
    }
    
    class Pug extends Dog {
        tagSnorted = true;
    }
    

    典型的类继承会自动继承这些属性,因此不需要 hacky 代码。但是对于我从 JS 更新到 TS 的大型代码库,这个解决方案将不起作用,因为对于 Pug 类,它总是被称为 pug.tag.snorted,而且通常也是动态的。一旦我完成了 TS 转换,我就可以开始解决上面提到的范式问题。但一步一步。

    最后,对于任何未来的读者,这里还有一个没有提到的选项:

    class Dog {
        getTags = () => ({
            barked: true,
        });
    
        tags: ReturnType<this["getTags"]> = this.getTags() as any;
    }
    
    class Pug extends Dog {
        getTags = () => ({
            ...super.getTags(),
            snorted: true,
        });
    }
    

    tags 上的动态显式输入对我来说感觉很 hacky,所以这就是为什么我没有使用它作为我的解决方案。另外,我一直在寻找隐式类型。也许这种方法的见解会对其他开发人员有用。

    【讨论】:

      猜你喜欢
      • 2021-07-11
      • 2021-01-18
      • 1970-01-01
      • 2021-02-11
      • 1970-01-01
      • 1970-01-01
      • 2021-05-05
      • 1970-01-01
      • 2016-12-06
      相关资源
      最近更新 更多