Typescript: 建议:在类中添加抽象静态方法,在接口中添加静态方法

创建于 2017-03-12  ·  98评论  ·  资料来源: microsoft/TypeScript

作为 #2947 的后续问题,它允许在方法声明上使用abstract修饰符,但在静态方法声明上不允许使用它,我建议通过在方法声明上允许abstract static修饰符将此功能扩展到静态方法声明.

相关问题涉及接口方法声明上的static修饰符,这是不允许的。

1.问题

1.1。 抽象类中的抽象静态方法

在使用抽象类及其实现的某些情况下,我可能需要一些依赖于类(而不是依赖于实例)的值,这些值应该在子类的上下文中(而不是在对象的上下文中)访问,没有创建一个对象。 允许这样做的特性是方法声明上的static修饰符。

例如(示例 1):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

FirstChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class FirstChildClass'
SecondChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class SecondChildClass'

但是在某些情况下,当我只知道访问类是从 AbstractParentClass 继承时,我还需要访问这个值,但我不知道我正在访问哪个特定的子类。 所以我想确定的是,AbstractParentClass 的每个孩子都有这个静态方法。

例如(示例 2):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

abstract class AbstractParentClassFactory {

    public static getClasses(): (typeof AbstractParentClass)[] {
        return [
            FirstChildClass,
            SecondChildClass
        ];
    }
}

var classes = AbstractParentClassFactory.getClasses(); // returns some child classes (not objects) of AbstractParentClass

for (var index in classes) {
    if (classes.hasOwnProperty(index)) {
        classes[index].getSomeClassDependentValue(); // error: Property 'getSomeClassDependentValue' does not exist on type 'typeof AbstractParentClass'.
    }
}

结果,编译器确定发生错误并显示消息:“typeof AbstractParentClass”类型上不存在属性“getSomeClassDependentValue”。

1.2. 接口中的静态方法

在某些情况下,接口逻辑意味着实现类必须具有静态方法,该方法具有预定的参数列表并返回确切类型的值。

例如(示例 3):

interface Serializable {
    serialize(): string;
    static deserialize(serializedValue: string): Serializable; // error: 'static' modifier cannot appear on a type member.
}

编译此代码时,出现错误:“静态”修饰符不能出现在类型成员上。

2.解决方案

这两个问题(1.1 和 1.2)的解决方案是允许在抽象类中的静态方法声明上使用static修饰符,在接口中允许使用abstract #$2$#$ 修饰符。

3.JS实现

这个特性在 JavaScript 中的实现应该类似于接口、抽象方法和静态方法的实现。

这意味着:

  1. 在抽象类中声明抽象静态方法不应影响抽象类在 JavaScript 代码中的表示。
  2. 在接口中声明静态方法不应影响 JavaScript 代码中接口的表示(它不存在)。

例如,这个 TypeScript 代码(示例 4):

interface Serializable {
    serialize(): string;
    static deserialize(serializedValue: string): Serializable;
}

abstract class AbstractParentClass {
    public abstract static getSomeClassDependentValue(): string;
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass implements Serializable {

    public serialize(): string {
        var serialisedValue: string;
        // serialization of this
        return serialisedValue;
    }

    public static deserialize(serializedValue: string): SecondChildClass {
        var instance = new SecondChildClass();
        // deserialization
        return instance;
    }

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

应该编译成这个JS代码:

var AbstractParentClass = (function () {
    function AbstractParentClass() {
    }
    return AbstractParentClass;
}());

var FirstChildClass = (function (_super) {
    __extends(FirstChildClass, _super);
    function FirstChildClass() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    FirstChildClass.getSomeClassDependentValue = function () {
        return 'Some class-dependent value of class FirstChildClass';
    };
    return FirstChildClass;
}(AbstractParentClass));

var SecondChildClass = (function (_super) {
    __extends(SecondChildClass, _super);
    function SecondChildClass() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    SecondChildClass.prototype.serialize = function () {
        var serialisedValue;
        // serialization of this
        return serialisedValue;
    };
    SecondChildClass.deserialize = function (serializedValue) {
        var instance = new SecondChildClass();
        // deserialization
        return instance;
    };
    SecondChildClass.getSomeClassDependentValue = function () {
        return 'Some class-dependent value of class SecondChildClass';
    };
    return SecondChildClass;
}(AbstractParentClass));

4.相关点

  • 抽象类的抽象静态方法的声明应使用abstract static修饰符进行标记
  • 抽象类的抽象静态方法的实现应使用abstract staticstatic修饰符进行标记
  • 接口的静态方法的声明应使用static修饰符进行标记
  • 接口的静态方法的实现应该用static修饰符标记

abstract static修饰符的所有其他属性都应该继承自abstractstatic修饰符属性。

static接口方法修饰符的所有其他属性都应该继承自接口方法和static修饰符属性。

5. 语言功能清单

  • 句法

    • _这个特性的语法是什么?_ -这个特性的语法是抽象类方法的$#$ abstract static static修饰符和接口方法的static修饰符。

    • _JavaScript 向后兼容有什么影响吗? 如果是这样,它们是否得到充分缓解?_ -对 JavaScript 向后兼容没有任何影响。

    • _此语法是否会干扰 ES6 或合理的 ES7 更改?_ -此语法不会干扰 ES6 或合理的 ES7 更改。

  • 语义

    • _在提议的特性下有什么错误?_ -现在编译抽象类方法的static abstract static和接口方法的 static 修饰符有错误。

    • _该功能如何影响子类型、超类型、身份和可分配关系?_ -该功能不影响子类型、超类型、身份和可分配关系。

    • _该功能如何与泛型交互?_ -该功能不与泛型交互。

  • 发射

    • _这个特性对 JavaScript 发射有什么影响?_ - 这个特性对 JavaScript 发射没有影响。

    • _在存在“任何”类型的变量的情况下是否正确发射?_ -是的。

    • _对声明文件 (.d.ts) 排放有何影响?_ -对声明文件没有影响。

    • _这个功能与外部模块配合得好吗?_ -是的。

  • 兼容性

    • _这是 1.0 编译器的重大变化吗?_ -可能是的,1.0 编译器将无法编译实现此功能的代码。

    • _这是对 JavaScript 行为的重大改变吗?_ -不。

    • _这是未来 JavaScript(即 ES6/ES7/更高版本)功能的不兼容实现吗?_ -不。

  • 其他

    • _可以在不对编译器性能产生负面影响的情况下实现该功能吗?_ -可能是的。

    • _它对工具场景有什么影响,例如编辑器中的成员完成和签名帮助?_ -可能它没有这种类型的任何影响。

Awaiting More Feedback Suggestion

最有用的评论

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

我想在 Serializable 的子类中强制实现静态反序列化方法。
是否有任何解决方法来实现这种行为?

所有98条评论

静态接口方法一般没有意义,见 #13462

推迟这个,直到我们听到更多关于它的反馈。

我们在考虑这个问题时遇到了一个主要问题:谁可以调用abstract static方法? 大概你不能直接调用AbstractParentClass.getSomeClassDependentValue 。 但是您可以在AbstractParentClass类型的表达式上调用该方法吗? 如果是这样,为什么应该允许? 如果不是,该功能的用途是什么?

静态接口方法一般没有意义,见 #13462

在 #13462 的讨论中,我没有看到为什么static接口方法是没有意义的。 我只看到它们的功能可以通过其他方式实现(这证明它们不是无意义的)。

从实际的角度来看,接口是一种规范——在实现该接口的类中实现它们所必需的一组方法。 接口不仅定义了对象提供的功能,它还是一种契约。

所以我看不出为什么class可能有static方法而interface没有任何合乎逻辑的原因。

如果我们遵循这样的观点,即所有已经可以用该语言实现的东西都不需要对语法和其他东西进行任何改进(即这个论点是#13462讨论的主要观点之一),那么以此为指导从观点来看,我们可以确定while循环是多余的,因为它可以同时使用forif来实现。 但我们不会取消while

我们在考虑这个问题时遇到了一个主要问题:谁可以调用abstract static方法? 大概你不能直接调用AbstractParentClass.getSomeClassDependentValue 。 但是您可以在AbstractParentClass类型的表达式上调用该方法吗? 如果是这样,为什么应该允许? 如果不是,该功能的用途是什么?

好问题。 既然您正在考虑这个问题,您能否分享您对此的想法?

只是想到编译器层面直接调用AbstractParentClass.getSomeClassDependentValue的情况是不会被追踪的(因为无法追踪),会出现JS运行时错误。 但我不确定这是否与 TypeScript 意识形态一致。

我只看到它们的功能可以通过其他方式实现(这证明它们不是无意义的)。

仅仅因为某些东西是可实现的,并不意味着它是有意义的。 😉

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

我想在 Serializable 的子类中强制实现静态反序列化方法。
是否有任何解决方法来实现这种行为?

对此有何最新看法? 我只是试图写一个抽象类的抽象静态属性,当它被禁止时我真的很惊讶。

@patryk-zielinski93 说了什么。 来自我们确实转换为 TS 的一些 PHP 项目,我们确实需要接口中的静态方法。

一个非常常见的用例是 React 组件,它们是具有静态属性的类,例如displayNamepropTypesdefaultProps

由于这个限制,React 的类型目前包括两种类型: Component类和包含构造函数和静态属性的ComponentClass接口。

要对 React 组件及其所有静态属性进行全面类型检查,一个有两个使用这两种类型。

没有ComponentClass的示例:静态属性被忽略

import React, { Component, ComponentClass } from 'react';

type Props = { name: string };

{
  class ReactComponent extends Component<Props, any> {
    // expected error, but got none: displayName should be a string
    static displayName = 1
    // expected error, but got none: defaultProps.name should be a string
    static defaultProps = { name: 1 }
  };
}

ComponentClass示例:静态属性经过类型检查

{
  // error: displayName should be a string
  // error: defaultProps.name should be a string
  const ReactComponent: ComponentClass<Props> = class extends Component<Props, any> {
    static displayName = 1
    static defaultProps = { name: 1 }
  };
}

我怀疑很多人目前没有使用ComponentClass ,不知道他们的静态属性没有经过类型检查。

相关问题: https ://github.com/DefinitelyTyped/DefinitelyTyped/issues/16967

这方面有进展吗? 或者抽象类上的构造函数的任何解决方法?
这是一个例子:

abstract class Model {
    abstract static fromString(value: string): Model
}

class Animal extends Model {
    constructor(public name: string, public weight: number) {}

    static fromString(value: string): Animal {
        return new Animal(...JSON.parse(value))
    }
}

@roboslone

class Animal {
    static fromString(value: string): Animal {
        return new Animal();
    }
}

function useModel<T>(model: { fromString(value: string): T }): T {
    return model.fromString("");
}

useModel(Animal); // Works!

同意这是一个非常强大和有用的功能。 在我看来,这个特性使班级成为“一等公民”。 类/静态方法的继承可以而且确实是有意义的,特别是对于静态方法工厂模式,其他发帖人在这里多次提到过这种模式。 这种模式对于反序列化特别有用,反序列化是 TypeScript 中经常执行的操作。 例如,想要定义一个接口来提供一个契约声明所有实现类型都可以从 JSON 实例化是非常有意义的。

不允许抽象静态工厂方法需要实现者创建抽象工厂类,而不必要地将类定义的数量加倍。 而且,正如其他发帖人所指出的,这是在其他语言(例如 PHP 和 Python)中实现的强大且成功的功能。

Typescript 的新手,但我也很惊讶默认情况下不允许这样做,而且很多人都试图证明不添加该功能是合理的:

  1. TS 不需要该功能,因为您仍然可以通过其他方式完成您正在尝试做的事情(如果您提供一个非常客观地更好的做某事方式的示例,这只是一个有效的论点,我已经看到很少)
  2. 仅仅因为我们可以并不意味着我们应该。 太好了:但是人们正在发布它如何有用/有益的具体示例。 我不明白允许它有什么伤害。

另一个简单的用例:(理想的方式,这行不通)

import {map} from 'lodash';

export abstract class BaseListModel {
  abstract static get instance_type();

  items: any[];

  constructor(items?: any[]) {
    this.items = map(items, (item) => { return new this.constructor.instance_type(item) });
  }

  get length() { return this.items.length; }
}

export class QuestionList extends BaseListModel {
  static get instance_type() { return Question }
}

相反,列表实例最终直接暴露了实例类型,这与列表实例本身无关,应该通过构造函数访问。 感觉很脏。 如果我们谈论记录模型会感觉更脏,其中通过相同的机制等指定一组默认值。

在使用 ruby​​/javascript 这么久之后,我真的很高兴能在一种语言中使用真正的抽象类,但最终却对实现限制感到沮丧——上面的例子只是我遇到它的第一个例子,虽然我能想到很多其他有用的用例。 主要是,作为创建简单 DSL/配置作为静态接口一部分的一种方法,通过确保一个类指定一个默认值对象或它可能是什么。 - 你可能会想,这就是接口的用途。 但是对于像这样简单的事情,它并没有真正的意义,只会导致事情变得比他们需要的更复杂(子类需要扩展抽象类并实现一些接口,使命名变得更加复杂,等等)。

我对我的项目有两次类似的要求。 它们都与保证所有子类都提供一组静态方法的具体实现有关。 我的场景描述如下:

class Action {
  constructor(public type='') {}
}

class AddAppleAction extends Action {
  static create(apple: Apple) {
    return new this(apple);
  }
  constructor(public apple: Apple) {
    super('add/apple');
  }
}

class AddPearAction extends Action {
  static create(pear: Pear) {
    return new this(pear);
  }

  constructor(public pear: Pear) {
    super('add/pear');
  }
}

const ActionCreators = {
  addApple: AddAppleAction
  addPear: AddPearAction
};

const getActionCreators = <T extends Action>(map: { [key: string]: T }) => {
  return Object.entries(map).reduce((creators, [key, ActionClass]) => ({
    ...creators,
    // To have this function run properly,
    // I need to guarantee that each ActionClass (subclass of Action) has a static create method defined.
    // This is where I want to use abstract class or interface to enforce this logic.
    // I have some work around to achieve this by using interface and class decorator, but it feels tricky and buggy. A native support for abstract class method would be really helpful here.
    [key]: ActionClass.create.bind(ActionClass)
  }), {});
};

希望这可以解释我的要求。 谢谢。

对于任何寻找解决方法的人,您可以使用这个装饰器:

class myClass {
    public classProp: string;
}

interface myConstructor {
    new(): myClass;

    public readonly staticProp: string;
}

function StaticImplements <T>() {
    return (constructor: T) => { };
}

<strong i="7">@StaticImplements</strong> <myConstructor>()
class myClass implements myClass {}
const getActionCreators = <T extends Action>(map: { [key: string]: {new () => T, create() : T}}) => {

由于我们是通过类型参数调用的,因此永远不会涉及带有假设的abstract static工厂方法的实际基类。 当类型参数被实例化时,子类的类型在结构上是相关的。

我在这个讨论中没有看到的是通过基类的类型而不是某些综合类型来调用实现的情况。

还需要考虑的是,与 C# 等语言不同, abstract成员实际上在派生类、JavaScript 成员实现、实例或其他方面的实现中_overridden_​​,从不覆盖继承的数字,而是_shadow_它们。

从用户的角度来看,我在这里的 5 美分是:

对于接口的情况,应该允许 _static_ 修饰符

接口定义契约,通过实现类来实现。 这意味着接口是比类更高级别的抽象。

现在我可以看到,类可以比接口更具表现力,在某种程度上,我们可以在类中拥有静态方法,但我们不能从契约定义本身强制执行它。

在我看来,这感觉是错误的和阻碍。

是否有任何技术背景使该语言功能难以或无法实现?

干杯

接口定义契约,通过实现类来实现。 这意味着接口是比类更高级别的抽象。

类有两个接口,两个实现契约,这是无可避免的。
像 C# 这样的语言也没有接口的静态成员是有原因的。 从逻辑上讲,接口是对象的公共表面。 接口描述对象的公共表面。 这意味着它不包含任何不存在的东西。 在类的实例上,实例上不存在静态方法。 它们只存在于类/构造函数中,因此它们只应在该接口中描述。

在类的实例上,实例上不存在静态方法。

你能详细说明一下吗? 这不是 C#

它们只存在于类/构造函数中,因此它们只应在该接口中描述。

我们得到了它,这就是我们想要改变的。

接口定义契约,通过实现类来实现。 这意味着接口是比类更高级别的抽象。

我完全同意这一点。 请参阅 Json 接口示例

@kitsonk ,请您详细说明:

类有两个接口,两个实现契约,这是无可避免的。

我不明白那部分。

从逻辑上讲,接口是对象的公共表面。 接口描述对象的公共表面。 这意味着它不包含任何不存在的东西。

我同意。 我看不出与我所说的有任何矛盾。 我什至说得更多。 我说接口是要实现的的契约。

在类的实例上,实例上不存在静态方法。 它们只存在于类/构造函数中,因此它们只应在该接口中描述。

不确定我是否理解正确,很清楚它在说什么,但不是你为什么这么说。 我可以解释为什么我认为我的陈述仍然有效。 我一直将接口作为参数传递给我的方法,这意味着我可以访问接口方法,请注意,我在这里不是使用接口来定义数据结构,而是定义在其他地方创建/水合的具体对象. 所以当我有:

fetchData(account: SalesRepresentativeInterface): Observable<Array<AccountModel>> {
    // Method Body
}

在那个方法体中,我确实可以使用account方法。 我想做的是能够使用来自SalesRepresentativeInterface的静态方法,这些方法已经强制在我在account接收的任何类中实现。 也许我对如何使用该功能有一个非常简单或完全错误的想法。

我认为允许static修饰符将允许我执行以下操作: SalesRepresentativeInterface.staticMethodCall()

我错了吗 ?

干杯

@davidmpaz :您的语法不太正确,但很接近。 这是一个示例使用模式:

interface JSONSerializable {
  static fromJSON(json: any): JSONSerializable;
  toJSON(): any;
}

function makeInstance<T extends JSONSerializable>(cls: typeof T): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializable implements JSONSerializable {
  constructor(private json: any) {
  }
  static fromJSON(json: any): ImplementsJSONSerializable {
    return new ImplementsJSONSerializable(json);
  }
  toJSON(): any {
    return this.json;
  }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable);

不幸的是,要实现这一点,我们需要 TypeScript 的两个特性:(1)接口中的静态方法和抽象静态方法; (2) 使用typeof作为泛型类的类型提示的能力。

@davidmpaz

我不明白那部分。

类有两个接口。 构造函数和实例原型。 关键字class本质上就是为此而生的糖。

interface Foo {
  bar(): void;
}

interface FooConstructor {
  new (): Foo;
  prototype: Foo;
  baz(): void;
}

declare const Foo: FooConstructor;

const foo = new Foo();
foo.bar();  // instance method
Foo.baz(); // static method

@jimmykane

你能详细说明一下吗? 这不是 C#

class Foo {
    bar() {}
    static baz() {}
}

const foo = new Foo();

foo.bar();
Foo.baz();

.baz() Foo.baz()仅存在于构造函数中。

从类型的角度来看,您可以引用/提取这两个接口。 Foo指公共实例接口, typeof Foo指公共构造函数接口(包括静态方法)。

为了跟进@kitsonk的解释,这里是上面重写的示例以实现您想要的:

interface JSONSerializable <T>{
    fromJSON(json: any): T;
}

function makeInstance<T>(cls: JSONSerializable<T>): T {
    return cls.fromJSON({});
}

class ImplementsJSONSerializable {
    constructor (private json: any) {
    }
    static fromJSON(json: any): ImplementsJSONSerializable {
        return new ImplementsJSONSerializable(json);
    }
    toJSON(): any {
        return this.json;
    }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable); 

请注意:

  • implements子句并不是真正需要的,每次使用该类时,都会进行结构检查,并验证该类以匹配所需的接口。
  • 您不需要typeof来获取构造函数类型,只需确保您的T是您打算捕获的内容

@mhegazy :您必须在 JSONSerializable 接口中删除我的toJSON调用。 虽然我的简单示例函数makeInstance只调用fromJSON ,但想要实例化一个对象然后使用它是很常见的。 你已经取消了我对makeInstance返回的内容进行调用的能力,因为我实际上并不知道T里面是什么 $ makeInstance (特别是如果makeInstance是一个类方法,内部用于创建实例,然后使用它)。 我当然可以这样做:

interface JSONSerializer {
  toJSON(): any;
}

interface JSONSerializable <T extends JSONSerializer> {
  fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializer implements JSONSerializer {
  constructor (private json: any) {
  }
  static fromJSON(json: any): ImplementsJSONSerializer {
    return new ImplementsJSONSerializer(json);
  }
  toJSON(): any {
    return this.json;
  }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);

现在我知道我的T将拥有JSONSerializer上可用的所有方法。 但这过于冗长且难以推理(等等,你正在传递ImplementsJSONSerializer ,但这不是JSONSerializable ,是吗?等等,你在打鸭子??) . 关于版本的更容易理解的是:

interface JSONSerializer {
  toJSON(): any;
}

interface JSONSerializable <T extends JSONSerializer> {
  fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializer implements JSONSerializer {
  constructor (private json: any) {
  }
  toJSON(): any {
    return this.json;
  }
}

class ImplementsJSONSerializable implements JSONSerializable<ImplementsJSONSerializer> {
  fromJSON(json: any): ImplementsJSONSerializer {
    return new ImplementsJSONSerializer(json);
  }

}

// returns an instance of ImplementsJSONSerializable
makeInstance(new ImplementsJSONSerializable());

但正如我在之前的评论中指出的那样,我们现在必须为要实例化的每个类创建一个工厂类,这比鸭子类型的示例更加冗长。 这当然是一直用 Java 完成的可行模式(我也假设 C# 也是如此)。 但它不必要地冗长和重复。 如果在接口中允许使用静态方法并且在抽象类中允许使用抽象静态方法,那么所有这些样板都会消失。

不需要额外的课程。 类可以有static成员.. 你只是不需要implements子句。 在名义类型系统中,您确实需要它来断言您的班级的“是”关系。 在结构类型系统中,您实际上并不需要它。 无论如何,每次使用都会检查“is a”关系,因此您可以安全地删除implements子句并且不会失去安全性。

所以换句话说,您可以拥有一个具有静态fromJSON并且其实例具有toJSON的类:

interface JSONSerializer {
    toJSON(): any;
}

interface JSONSerializable<T extends JSONSerializer> {
    fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
    return cls.fromJSON({});
}

class ImplementsJSONSerializer {
    constructor (private json: any) {
    }
    static fromJSON(json: any): ImplementsJSONSerializer {
        return new ImplementsJSONSerializer(json);
    }
    toJSON(): any {
        return this.json;
    }
}


// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);

@mhegazy我已经指出这是一个可行的选择。 请参阅我的第一个“鸭子打字”示例。 我认为这是不必要的冗长且难以推理。 然后我断言更容易推理的版本(工厂类)更加冗长。

没有人不同意可以解决接口中缺少静态方法的问题。 事实上,我认为通过在工厂类上使用函数式样式有一个更优雅的解决方法,尽管这是我个人的偏好。

但是,在我看来,实现这种异常常见的模式的最简洁和最容易理解的方法是使用抽象静态方法。 我们这些已经开始喜欢其他语言(Python、PHP)的这个功能的人怀念在 TypeScript 中拥有它。 谢谢你的时间。

然后我断言更容易推理的版本(工厂类)

不确定我是否同意这更容易推理。

但是,在我看来,实现这种异常常见的模式的最简洁和最容易理解的方法是使用抽象静态方法。 我们这些已经开始喜欢其他语言(Python、PHP)的这个功能的人怀念在 TypeScript 中拥有它。

这个问题正在跟踪添加这个。 我只是想确保该线程的未来访问者明白,如果没有implements子句,这在今天是可行的。

@kitsonk是的,我错过了。

伙计们,所以我喜欢 LocationModule,它应该有获取选项以存储在数据库中的方法,它应该从中重新创建自己。
每个 LocationModule 都有自己的娱乐类型选项。

所以现在我必须用泛型创建工厂来娱乐,即使这样我也没有编译时检查。 你知道,打败这里的目的。

起初我就像“接口没有静态所以让我们坚持使用抽象类但即使这会很脏,因为现在我必须在我的代码类型中从接口到抽象类的任何地方进行更改,以便检查器能够识别我正在寻找的方法.

这里:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
}

export abstract class AbstractLocationModule implements LocationModuleInterface {
  abstract readonly name: string;

  abstract getRecreationOptions(): ModuleRecreationOptions;

  abstract static recreateFromOptions(options: ModuleRecreationOptions): AbstractLocationModule;
}

然后我偶然发现,静态不能与抽象。 它不能在界面中。

伙计们,说真的,我们不是保护不实施这个只是为了不实施这个吗?

我热爱 TypeScript。 我的意思是疯了。 但是总有但是。

而不是强制实现静态方法,我将不得不仔细检查,就像我在普通的旧 JavaScript 中所做的一切一样。

架构已经充满了模式、很酷的决策等,但其中一些只是为了克服使用接口静态方法等简单事物的问题。

我的工厂必须在运行时检查静态方法是否存在。 那个怎么样?

甚至更好:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
  dontForgetToHaveStaticMethodForRecreation();
}

如果您以多态方式使用类对象,这是它实现接口的唯一原因,那么指定类的静态端要求的接口是否由类本身在语法上引用并不重要因为如果必要的接口没有实现,它将在使用现场进行检查并发出设计时错误。

@malina-kirn

请参阅我的第一个“鸭子打字”示例。

这个问题与是否使用鸭子类型无关。 TypeScript 是鸭式的。

@aluanhaddad你的暗示是不正确的。 我可以理解您的观点,即implements ISomeInterface主要功能是用于多态地使用类对象。 然而,类是否引用描述类对象的静态形状的接口很重要。

您暗示隐式类型推断和使用站点错误涵盖所有用例。
考虑这个例子:

interface IComponent<TProps> {
    render(): JSX.Element;
}

type ComponentStaticDisplayName = 'One' | 'Two' | 'Three' | 'Four';
interface IComponentStatic<TProps> {
    defaultProps?: Partial<TProps>;
    displayName: ComponentStaticDisplayName;
    hasBeenValidated: 'Yes' | 'No';
    isFlammable: 'Yes' | 'No';
    isRadioactive: 'Yes' | 'No';
    willGoBoom: 'Yes' | 'No';
}

interface IComponentClass<TProps> extends IComponentStatic<TProps> {
    new (): IComponent<TProps> & IComponentStatic<TProps>;
}

function validateComponentStaticAtRuntime<TProps>(ComponentStatic: IComponentStatic<TProps>, values: any): void {
    if(ComponentStatic.isFlammable === 'Yes') {
        // something
    } else if(ComponentStatic.isFlammable === 'No') {
        // something else
    }
}

// This works, we've explicitly described the object using an interface
const componentStaticInstance: IComponentStatic<any> = {
    displayName: 'One',
    hasBeenValidated: 'No',
    isFlammable: 'Yes',
    isRadioactive: 'No',
    willGoBoom: 'Yes'
};

// Also works
validateComponentStaticAtRuntime(componentStaticInstance, {});

class ExampleComponent1 implements IComponent<any> {
    public render(): JSX.Element { return null; }

    public static displayName = 'One'; // inferred as type string
    public static hasBeenValidated = 'No'; // ditto ...
    public static isFlammable = 'Yes';
    public static isRadioactive = 'No';
    public static willGoBoom = 'Yes';
}

// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent1, {});

class ExampleComponent2 implements IComponent<any> {
    public render(): JSX.Element { return null; }

    public static displayName = 'One';
    public static hasBeenValidated = 'No';
    public static isFlammable = 'Yes';
    public static isRadioactive = 'No';
    public static willGoBoom = 'Yes';
}

// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent2, {});

在上面的示例中,需要显式类型声明; 不描述类对象的形状(静态端)会导致使用站点出现设计时错误,即使实际值符合预期的形状。

此外,缺乏引用描述静态端的接口的方法,让我们唯一的选择是显式声明每个单独成员的类型; 然后在每节课上重复一遍。

在我之前的帖子的基础上,我觉得接口中的static修饰符对于一些需要解决的用例来说是一个非常好的解决方案。 我为这条评论的增长幅度道歉,但我想说明很多。

1) 有时我们需要能够明确地描述类对象的静态侧的形状,例如在我上面的示例中。
2) 有时非常需要在定义站点进行形状检查,例如在@OliverJAsh的示例中,使用站点位于不会检查的外部代码中,您需要在定义站点进行形状检查.

关于第 2 点,我读过的许多帖子都建议进行形状检查,因为使用站点绰绰有余。 但是,如果使用站点位于很远很远的星系模块中……或者当使用站点位于不会检查的外部位置时,显然情况并非如此。

其他帖子建议#workarounds在定义站点进行形状检查。 虽然这些可怕的解决方法可以让您在定义站点检查形状(在某些情况下),但存在一些问题:

  • 他们没有解决上面的第 1 个问题,您仍然需要显式声明每个类中每个成员的类型。
  • 在许多情况下,它们缺乏清晰、优雅和可读性。 它们并不总是易于使用,不直观,并且可能需要很长时间才能弄清楚它们是可能的。
  • 它们并非在所有情况下都有效,并且发现#workaroundsToMakeTheWorkaroundsWork并不有趣……或富有成效……但大多数情况下并不有趣。

这是我最近看到的在定义站点强制进行形状检查的解决方法的示例...

// (at least) two separate interfaces
interface ISomeComponent { ... }
interface ISomeComponentClass { ... }

// confusing workaround with extra work to use, not very intuitive, etc
export const SomeComponent: ISomeComponentClass =
class SomeComponent extends Component<IProps, IState> implements ISomeComponent {
...
}

现在尝试将这种解决方法与抛出的抽象类一起使用

interface ISomeComponent { ... }
// need to separate the static type from the class type, because the abstract class won't satisfy
// the shape check for new(): ... 
interface ISomeComponentStatic { ... }
interface ISomeComponentClass { 
    new (): ISomeComponent & ISomeComponentStatic;
}

// I AM ERROR.    ... cannot find name abstract
export const SomeComponentBase: ISomeComponentStatic =
abstract class SomeComponentBase extends Component<IProps, IState> implements ISomeComponent {
...
}

export const SomeComponent: ISomeComponentClass =
class extends SomeComponentBase { ... }

我们会的,我想我们也会解决这个问题......叹息

...

abstract class SomeComponentBaseClass extends Component<IProps, IState> implements ISomeComponent { 
   ... 
}

// Not as nice since we're now below the actual definition site and it's a little more verbose
// but hey at least were in the same module and we can check the shape if the use site is in
// external code...
// We now have to decide if we'll use this work around for all classes or only the abstract ones
// and instead mix and match workaround syntax
export const SomeComponentBase: ISomeComponentStatic = SomeComponentBaseClass;

但是等等,还有更多! 让我们看看如果我们使用泛型会发生什么......

interface IComponent<TProps extends A> { ... }
interface IComponentStatic<TProps extends A> { ... }

interface IComponentClass<TProps extends A, TOptions extends B> extends IComponentStatic<TProps> {
    new (options: TOptions): IComponent<TProps> & IComponentStatic<TProps>;
}

abstract class SomeComponentBaseClass<TProps extends A, TOptions extends B> extends Component<TProps, IState> implements IComponent<TProps> {
...
}

// Ruh Roh Raggy: "Generic type .... requires 2 type argument(s) ..."
export const SomeComponentBase: IComponentStatic = SomeComponentBaseClass;


// "....  requires 1 type argument(s) ... "    OH NO, We need a different workaround
export const SomeComponent: IComponentStatic =
class extends SomeComponentBase<TProps extends A, ISomeComponentOptions> {
...
}

此时,如果您在代码中有一个将由打字稿检查的使用站点,您可能应该咬紧牙关并依赖它,即使它真的很远。

如果您使用的是外部站点,请说服社区/维护者接受接口成员的static修饰符。 与此同时,您将需要另一种解决方法。 它不在定义站点上,但我相信您可以使用 #8328 中描述的变通方法的修改版本来实现此目的。 请参阅@mhegazy于 2016 年 5 月 16 日由@RyanCavanaugh提供的评论中的解决方法

如果我遗漏了关键点,请原谅我。 我对接口成员支持static犹豫不决的理解主要是不喜欢使用相同的 interface + implements关键字来描述构造函数的形状和实例的形状。

让我通过说我喜欢 typescript 提供的东西来开始下面的类比,并非常感谢那些开发它的人以及为处理过多的功能请求付出了很多思考和努力的社区; 尽最大努力实现非常适合的功能。

但是,在我看来,这种犹豫不决是为了以牺牲可用性为代价来保持“概念上的纯粹性”。 再次抱歉,如果这个类比离题太远了:

这提醒了从Java切换到C# 。 当我第一次看到C#代码这样做时
C# var something = "something"; if(something == "something") { ... }
警钟开始在我的脑海中响起,世界即将结束,他们需要使用"something".Equals(something)

==供参考等于! 您有一个单独的.Equals(...)用于字符串比较...
并且字符串文字需要首先出现,这样您就不会在 null 变量上调用 .Equals(...) 的 null ref ...
和... 和...换气过度

然后在使用C#几周后,我开始意识到由于这种行为,它使用起来变得多么容易。 尽管这意味着放弃两者之间的明显区别,但它在可用性方面做出了如此显着的改进,这是值得的。

这就是我对接口成员的static修饰符的感受。 它将允许我们像我们已经做的那样继续描述实例的形状,允许我们描述构造函数的形状,我们只能在个别情况下使用糟糕的解决方法,并允许我们以相对干净和容易的方式进行大大地。 可用性方面的巨大改进,IMO。

我将在下一条评论中权衡abstract static ....

接口总是需要在实现类上重新指定成员类型。 (在_explicitly_实现类中推断成员签名是一个单独的,但不受支持的特性)。

您收到错误的原因与此提案无关。 要绕过它,您需要在需要是文字类型的类成员(静态或其他)的实现上使用readonly修饰符; 否则,将推断string

同样, implements关键字只是一个意图规范,它不影响类型的结构兼容性,也不引入名义类型。 这并不是说它没有用,但它不会改变类型。

所以abstract static ...不值得。 太多的问题。

您将需要新的复杂语法来隐式声明已在使用站点检查的内容(如果使用站点将由 typescript 编译器检查)。

如果使用站点是外部的,那么您真正需要的是接口的static成员......抱歉,插件完成了......晚安!

@aluanhaddad没错,但即使在显式实现类中推断成员签名是受支持的功能,我们也缺乏为类的静态端声明成员签名的简洁方法。

我试图表达的观点是我们缺乏方法来explicitly声明类的静态端的预期结构,并且在某些情况下这确实很重要(或者为了支持功能而重要)

首先,我想反驳您评论的这一部分“是否在语法上引用了指定类静态方面要求的接口并不重要”。

我试图以类型推断为例,说明为什么它对未来的支持重要。
通过在此处let something: ISomethingStatic = { isFlammable: 'Ys', isFlammable2: 'No' ... isFlammable100: 'No' }语法上引用接口,我们不必显式声明类型为 'Yes' | '不' 100 次分开。

为了在类的静态端(将来)实现相同的类型推断,我们将需要某种方式在语法上引用接口。

在阅读了您的最新评论后,我认为这并不像我希望的那样明确。 也许一个更好的例子是,当类的静态端的使用站点位于外部代码中时,打字稿编译器不会检查。 在这种情况下,我们目前不得不依靠基本上创建人工使用站点的变通方法,提供较差的可用性/可读性,并且在许多情况下不起作用。

以牺牲一些冗长为代价,您可以获得相当程度的类型安全:

type HasType<T, Q extends T> = Q;

interface IStatic<X, Y> {
    doWork: (input: X) => Y
}

type A_implments_IStatic<X, Y> = HasType<IStatic<X, Y>, typeof A>    // OK
type A_implments_IStatic2<X> = HasType<IStatic<X, number>, typeof A> // OK
type A_implments_IStatic3<X> = HasType<IStatic<number, X>, typeof A> // OK
class A<X, Y> {
    static doWork<T, U>(_input: T): U {
        return null!;
    }
}

type B_implments_IStatic = HasType<IStatic<number, string>, typeof B> // Error as expected
class B {
    static doWork(n: number) {
        return n + n;
    }
}

同意这应该被允许。 以下简单的用例将受益于抽象静态方法:

export abstract class Component {
  public abstract static READ_FROM(buffer: ByteBuffer): Component;
}

// I want to force my DeckComponent class to implement it's own static ReadFrom method
export class DeckComponent extends Component {
  public cardIds: number[];

  constructor(cardIds: number[]) {
    this.cardIds = cardIds;
  }

  public static READ_FROM(buffer: ByteBuffer): DeckComponent {
    const cardIds: number[] = [...];
    return new DeckComponent(cardIds);
  }
}

@RyanCavanaugh我认为还没有人确切地提到过这一点(尽管我在快速通读时可能会错过它),但作为回应:

我们在考虑这个问题时遇到了一个主要问题:谁可以调用抽象静态方法? 大概你不能直接调用 AbstractParentClass.getSomeClassDependentValue 。 但是您可以在 AbstractParentClass 类型的表达式上调用该方法吗? 如果是这样,为什么应该允许? 如果不是,该功能的用途是什么?

这可以通过实施#3841 的一部分来解决。 通读该问题,提出的主要反对意见似乎是派生类的构造函数类型通常与其基类的构造函数不兼容。 然而,同样的问题似乎不适用于任何其他静态方法或字段,因为 TypeScript 已经在检查覆盖的静态与它们在基类中的类型是否兼容。

所以我建议给T.constructor类型Function & {{ statics on T }} 。 这将允许声明abstract static foo字段的抽象类通过this.constructor.foo安全地访问它,同时不会导致构造函数不兼容的问题。

即使没有实现自动将静态添加到T.constructor ,我们仍然可以通过手动声明"constructor": typeof AbstractParentClass在基类中使用abstract static属性。

我想很多人都希望@patryk-zielinski93 的例子能够奏效。 相反,我们应该使用违反直觉、冗长和神秘的解决方法。 既然我们已经有了“语法糖化”的类和静态成员,为什么我们不能在类型系统中有这样的糖呢?

这是我的痛苦:

abstract class Shape {
    className() {
        return (<typeof Shape>this.constructor).className;
    }
    abstract static readonly className: string; // How to achieve it?
}

class Polygon extends Shape {
    static readonly className = 'Polygon';
}

class Triangle extends Polygon {
    static readonly className = 'Triangle';
}

也许我们可以改为/并行引入static implements子句? 例如

interface Foo {
    bar(): number;
}

class Baz static implements Foo {
    public static bar() {
        return 4;
    }
}

这具有向后兼容为静态和实例成员声明单独接口的优点,如果我正确阅读此线程,这似乎是当前选择的解决方法。 这也是一个微妙不同的功能,我可以看到它本身很有用,但这不是重点。

statically implements会更好,但这会引入一个新关键字。值得商榷。不过,它可能是上下文相关的。)

无论我多么努力地试图理解那些对 OP 的提议持怀疑态度的人的论点,我都失败了。

只有一个人(https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-379645122)回复了你@RyanCavanaugh 。 我重写了 OP 的代码以使其更简洁,并说明您的问题和我的答案:

abstract class Base {
  abstract static foo() // ref#1
}

class Child1 extends Base {
  static foo() {...} // ref#2
}

class Child2 extends Base {
  static foo() {...}
}

谁可以调用抽象静态方法? 大概你不能直接调用Base.foo

如果您的意思是 ref#1,那么是的,它不应该是可调用的,因为它是抽象的,甚至没有主体。
您只能调用 ref#2。

但是你能在 Base 类型的表达式上调用该方法吗?
如果是这样,为什么应该允许?

function bar(baz:Base) { // "expression of type Base"
  baz.foo() // ref#3
}

function bar2(baz: typeof Base) { // expression of type Base.constructor
  baz.foo() // ref#4
}

ref#3 是一个错误。 不,您不能在那里调用“foo”,因为baz应该是 Child1 或 Child2 的 __instance__,并且实例没有静态方法

ref#4 是(应该)正确的。 您可以(应该能够)在那里调用静态方法 foo,因为baz应该是 Child1 或 Child2 的构造函数,它们都扩展了 Base,因此必须实现 foo()。

bar2(Child1) // ok
bar2(Child2) // ok

你可以想象这种情况:

bar2(Base) // ok, but ref#4 should be red-highlighted with compile error e.g. "Cannot call abstract  method." 
// Don't know if it's possible to implement in compiler, though. 
// If not, compiler simply should not raise any error and let JS to do it in runtime (Base.foo is not a function). 

该功能有什么用?

参见 ref#4 当我们不知道我们接收哪个子类作为参数(它可能是 Child1 或 Child2)时,使用它,但我们想调用它的静态方法并确保该方法存在。
我正在重新编写node.js 框架,知道吗,叫AdonisJS。 它是用纯 JS 编写的,所以我将其转换为 TS。 我无法更改代码的工作方式,我只是添加类型。 缺少这个功能让我非常难过。

ps 在这篇评论中,为了简单起见,我只写了抽象类,没有提到接口。 但是我写的一切都适用于接口。 您只需将抽象类替换为接口,一切都会正确。 有可能,当 TS 团队出于某种原因(不知道为什么)不想在abstract class中实施abstract static $ 时,但是只实施static就可以了接口中的单词,我猜这会让我们很开心。

pps 我在您的问题中编辑了类和方法名称,以使其符合我的代码。

基于其他怀疑论者的主要论点,有几点想法:

“不,这一定不能实现,但你已经可以这样做了*写了 15 倍以上的代码行*”。

这就是语法糖的用途。 让我们在 TS 中添加一点(糖)。 但是不,我们最好折磨那些试图突破装饰器和十几个泛型的开发人员的大脑,而不是在接口中添加一个简单的static字。 为什么不将static视为另一种让我们的生活更轻松的糖呢? 与 ES6 添加class的方式相同(如今已无处不在)。 但是不,让我们成为书呆子,以旧的和“正确”的方式做事。

“js类有两个接口:构造函数和实例”。

好的,那为什么不给我们一种方法来为构造函数制作接口呢? 然而,简单地添加static容易,更直观。

关于这一点:

推迟这个,直到我们听到更多关于它的反馈。

一年多过去了,也提供了大量反馈,这个问题要搁置多久? 有人可以回答吗,拜托。

这篇文章可能看起来有点苛刻......但不,这是一个热爱 TS 的人的要求,同时在将我们的旧 JS 代码库转换为 TS 时,确实找不到我们应该使用丑陋黑客的一个充分理由。 除此之外,huuuge感谢TS团队。 TS 太棒了,它改变了我的生活,我比以往任何时候都更喜欢编码和我的工作......但是这个问题正在毒害我的经验......

可能的解决方法?

export type Constructor<T> = new (...args: any[]) => T;

declare global {
  interface Function extends StaticInterface {
  }
}

export interface StaticInterface {
  builder: (this: Constructor<MyClass>) => MyClass
}

// TODO add decorator that adds "builder" implementation
export class MyClass {
  name = "Ayyy"
}

export class OtherClass {
  id = "Yaaa"
}

MyClass.builder() // ok
OtherClass.builder() // error

提案:接口和类型中的静态成员,抽象类中的抽象成员,v2

用例

符合幻想世界的类型

````打字稿
接口应用程序扩展应用{(a:A)的静态:适用 }

const of = >(c: C, a: A): new C => c.of(a); ````

Serializable类型与静态deserialize方法

````打字稿
接口可序列化{
静态反序列化(s:字符串):可序列化;

serialize(): string;

}
````

一般合同

````打字稿
接口合约{
static create(x: number): 合约;
static new(x: number): 合约;
}

const factory1 = (c: static Contract): Contract => c.create(Math.random());
const factory2 = (C: static Contract): Contract => new C(Math.random());
常量工厂3 =(c: C): 新 C => c.create(Math.random());

常量 c1 = factory1(ContractImpl); // Contract
常量 c2 = factory2(ContractImpl); // Contract
常量 c3 = factory3(ContractImpl); // ContractImpl
````

句法

静态方法和字段

````打字稿
接口可序列化{
静态反序列化(s:字符串):可序列化;
}

类型可序列化 = {
静态反序列化(s:字符串):可序列化;
};

抽象类可序列化{
静态抽象反序列化(s:字符串):可序列化;
}
````

静态构造函数签名

typescript interface Contract { static new(): Contract; }

后续:静态调用签名

讨论:

如何表达静态调用签名?
如果我们在调用签名前简单地添加static修饰符来表达它们,
我们如何将它们与名称'static'的实例方法区分开来?
我们可以使用与'new'命名方法相同的解决方法吗?
在这种情况下,这绝对是一个突破性的变化。

static类型运算符

typescript const deserialize = (Class: static Serializable, s: string): Serializable => Class.deserialize(s);

new类型运算符

typescript const deserialize = <C extends static Serializable>(Class: C, s: string): new C => Class.deserialize(s);

内部插槽

[[Static]]内部插槽

类型的“静态”接口将存储在[[Static]]内部槽中:

````打字稿
// 方式
接口可序列化{
静态反序列化(s:字符串):可序列化;
序列化:字符串;
}

// 将在内部表示为
// 接口可序列化 {
// [[静止的]]: {
// [[Instance]]: 可序列化; // 见下文。
// 反序列化(s: string): 可序列化;
// };
// 序列化():字符串;
// }
````

默认情况下有类型never

[[Instance]]内部插槽

将存储类型的“实例”接口
[[Instance]] [[Static]]内部插槽类型的 [[Instance]] 内部插槽中。

默认情况下有类型never

语义

没有static运算符

当一个类型用作值类型时,
[[Static]]内部插槽将被丢弃:

````打字稿
声明 const 可序列化:可序列化;

类型 T = 可序列化类型;
// { 序列化():字符串; }
````

但是类型本身仍然保留[[Static]]内部插槽:

typescript type T = Serializable; // { // [[Static]]: { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }; // serialize(): string; // }

可分配性

静态感知类型的两个值都可以分配给结构相同的
(当然[[Static]]除外),反之亦然。

static运算符

| 关联性 | 优先级 |
| :-----------: | :--------------------------------: |
| 对 | IDK,但等于new的一个 |

static类型运算符返回该类型的[[Static]]内部槽的类型。
它有点类似于typeof类型的运算符,
但它的参数必须是类型而不是值。

typescript type T = static Serializable; // { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }

typeof类型运算符也丢弃了[[Instance]]内部槽:

````打字稿
声明 const SerializableImpl:静态可序列化;

类型 T = typeof SerializableImpl;
// { deserialize(s: string): 可序列化; }
````

可分配性

实例感知类型的两个值都可以分配给结构相同的
(当然,除了[[Instance]]内部插槽),反之亦然。

new运算符

| 关联性 | 优先级 |
| :-----------: | :------------------------------------------------: |
| 对 | IDK,但等于static的一个 |

new运算符返回该类型的[[Instance]]内部槽的类型。
它有效地反转了static运算符:

typescript type T = new static Serializable; // { // [[Static]]: { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }; // serialize(): string; // }

extends / implements语义

实现具有非默认[[Static]]内部插槽的接口的类
必须有一个兼容的[[Static]]内部插槽。
兼容性检查应与(或类似)相同
定期(实例)兼容性检查。

````打字稿
类 SerializableImpl 实现可序列化 {
静态反序列化(S:字符串):SerializableImpl {
// 反序列化逻辑在这里。
}

// ...other static members
// constructor
// ...other members

serialize(): string {
    //
}

}
````

去做

  • [ ] 决定静态调用签名应该使用哪种语法。
    可能没有破坏性的变化。
  • [ ] 有条件类型和infer运算符的特殊情况吗?
  • [ ] 对非抽象类成员语义的更改。 _可能会坏掉。_

我花了几个小时阅读这个问题和其他问题来解决我的问题,这将通过静态修饰符或类的接口来解决。 我找不到解决我问题的单一解决方法。

我遇到的问题是我想对代码生成的类进行修改。 例如我想做的是:

import {Message} from "../protobuf";

declare module "../protobuf" {
    interface Message {
        static factory: (params: MessageParams) => Message
    }
}

Message.factory = function(params: MessageParams) {
    const message = new Message();
    //... set up properties
    return message;
}

export default Message;

在当前版本的 TS 中,我找不到一个可以让我做同样事情的解决方法。 我目前是否缺少解决此问题的方法?

这感觉与在此处发布的用例有关,显然没有解决方法,当然也没有直接的解决方法。

如果要根据接口检查类的实例和静态端,可以这样做:

interface C1Instance {
  // Instance properties ...

  // Prototype properties ...
}
interface C2Instance extends C1Instance {
  // Instance properties ...

  // Prototype properties ...
}

interface C1Constructor {
  new (): C1Instance;

  // Static properties ...
}
interface C2Constructor {
  new (): C2Instance;

  // Static properties ...
}

type C1 = C1Instance;
let C1: C1Constructor = class {};

type C2 = C2Instance;
let C2: C2Constructor = class extends C1 {};

let c1: C1 = new C1();
let c2: C2 = new C2();

我们中的太多人都在这方面浪费了太多时间。 为什么不是一件事?!¿i!
为什么对于应该可读、易消化的东西以及简单的 1-liner 的东西,答案都是巨大的变通方法。 我不希望有人必须弄清楚我的代码试图用一些 hacky 解决方法做什么。 很抱歉用一些不是很有价值的东西进一步混淆了这个话题,但目前这是一个巨大的痛苦和浪费时间,所以我觉得它对我、现在的其他人以及后来寻找答案的人都很有价值。

任何语言的东西,无论是自然的还是人工的,它都应该自然地发展为高效且易于使用。 最终人们决定使用减少语言,(“okay”=>“ok”,“going to”=>“gonna”),发明了新的荒谬词,如“selfie”和“google”,用 l33tspeak 重新定义了拼写和东西,甚至禁止了一些词,而且,不管你想不想使用它们,每个人仍然有点理解它们的意思,我们中的一些人确实使用它们来完成一些特定的任务。 对于这些人来说,没有任何充分的理由,但是某些人在某些任务中的效率,这完全取决于实际使用它们的人数。 这段对话的数量清楚地表明,很多人可以利用这个static abstract来满足他们的任何该死的考虑。 我出于同样的原因来到这里,因为我想实现Serializable ,所以我尝试了所有直观的(对我而言)方法,但都没有奏效。 相信我,我会寻找的最后一件事是解释为什么我不需要这个功能并且应该去其他的东西。 一年半,天哪! 我敢打赌,某个地方已经有一个 PR,有测试和东西。 请做到这一点,如果有某种不鼓励的使用方式,我们有一个 tslint。

可以通过this.constructor.staticMember从基类访问子类的静态成员,因此抽象静态成员对我来说很有意义。

class A {
  f() {
    console.log(this.constructor.x)
  }
}

class B extends A {
  static x = "b"
}

const b = new B
b.f() // logs "b"

A 类应该能够指定它需要静态x成员,因为它在f方法中使用它。

任何新闻 ?
这些功能真的很需要😄
1)接口中的static函数
2)抽象类中的abstract static函数

虽然我讨厌拥有静态接口的想法,但出于所有实际目的,今天以下内容应该足够了:

type MyClass =  (new (text: string) => MyInterface) & { myStaticMethod(): string; }

可以用作:

const MyClass: MyClass = class implements MyInterface {
   constructor(text: string) {}
   static myStaticMethod(): string { return ''; }
}

更新:

关于这个想法的更多细节和一个活生生的例子

// dynamic part
interface MyInterface {
    data: number[];
}
// static part
interface MyStaticInterface {
    myStaticMethod(): string;
}

// type of a class that implements both static and dynamic interfaces
type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface;

// way to make sure that given class implements both 
// static and dynamic interface
const MyClass: MyClass = class MyClass implements MyInterface {
   constructor(public data: number[]) {}
   static myStaticMethod(): string { return ''; }
}

// works just like a real class
const myInstance = new MyClass([]); // <-- works!
MyClass.myStaticMethod(); // <-- works!

// example of catching errors: bad static part
/*
type 'typeof MyBadClass1' is not assignable to type 'MyClass'.
  Type 'typeof MyBadClass1' is not assignable to type 'MyStaticInterface'.
    Property 'myStaticMethod' is missing in type 'typeof MyBadClass1'.
*/
const MyBadClass1: MyClass = class implements MyInterface {
   constructor(public data: number[]) {}
   static myNewStaticMethod(): string { return ''; }
}

// example of catching errors: bad dynamic part
/*
Type 'typeof MyBadClass2' is not assignable to type 'MyClass'.
  Type 'typeof MyBadClass2' is not assignable to type 'new (data: number[]) => MyInterface'.
    Type 'MyBadClass2' is not assignable to type 'MyInterface'.
      Property 'data' is missing in type 'MyBadClass2'.
*/
const MyBadClass2: MyClass = class implements MyInterface {
   constructor(public values: number[]) {}
   static myStaticMethod(): string { return ''; }
}

@aleksey-bykov 这可能不是 Typescript 的错,但我无法获得这个工作的 Angular 组件装饰器及其 AoT 编译器。

@aleksey-bykov 这很聪明,但仍然不适用于抽象静态。 如果您有MyClass的任何子类,则不会强制执行类型检查。 如果您涉及泛型,情况也会更糟。

// no errors
class Thing extends MyClass {

}

我真的希望 TypeScript 团队重新考虑他们对此的立场,因为构建需要静态属性的最终用户库没有任何合理的实现。 我们应该能够有一个要求接口实现者/抽象类扩展器具有静态的契约。

@bbugh我质疑这里讨论的问题的存在,如果可以通过常规类的实例来完成,为什么还要使用静态抽象继承方法来解决所有这些麻烦?

class MyAbstractStaticClass {
    abstract static myStaticMethod(): void; // <-- wish we could
}
class MyStaticClass extends MyAbstractStaticClass {
    static myStaticMethod(): void {
         console.log('hi');
    }
}
MyStaticClass.myStaticMethod(); // <-- would be great

对比

class MyAbstractNonStaticClass {
    abstract myAbstractNonStaticMethod(): void;
}
class MyNonStaticClass extends MyAbstractNonStaticClass {
    myNonStaticMethod(): void {
        console.log('hi again');
    }
}
new MyNonStaticClass().myNonStaticMethod(); // <-- works today

@aleksey-bykov 有很多原因。 例如(来自@patryk-zielinski93):

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

我想在 Serializable 的子类中强制实现静态反序列化方法。
是否有任何解决方法来实现这种行为?

编辑:我知道你也可以使用deserialize作为构造函数,但是类只能有 1 个构造函数,这使得工厂方法成为必要。 人们想要一种在接口中要求工厂方法的方法,这是完全有意义的。

只需将期望化逻辑带到一个单独的类,因为将静态反序列化方法附加到被反序列化的类没有任何好处

class Deserializer {
     deserializeThis(...): Xxx {}
     deserializeThat(...): Yyy {}
}

问题是什么?

@aleksey-bykov 单独的课程看起来不那么漂亮

@aleksey-bykov,问题是有不止一个类需要序列化,所以你的方法是强制创建一个可序列化的字典,这是一个超级类反模式,对它们中的任何一个进行修改都需要在这个超级类中进行编辑,这使得代码支持令人讨厌。 虽然拥有一个可序列化的接口可以强制实现任何给定的对象类型,并且还支持多态继承,这是人们想要它的主要原因。 请不要猜测是否有好处,任何人都应该能够有选择权并选择对自己的项目有利的东西。

@octaharon有一个专门用于反序列化的类与你所说的完全相反,它有单一的责任,因为你唯一改变它的时候是你关心反序列化的时候

相反,像您建议的那样向类本身添加反序列化会赋予它额外的责任和额外的改变理由

最后我对静态方法没有问题,但我真的很想看到抽象静态方法和静态接口的实际用例示例

@octaharon有一个专门用于反序列化的类与你所说的完全相反,它有单一的责任,因为你唯一改变它的时候是你关心反序列化的时候

除了您必须在两个地方而不是一个地方更改代码,因为您的(反)序列化取决于您的类型结构。 这不计入“单一责任”

相反,像您建议的那样向类本身添加反序列化会赋予它额外的责任和额外的改变理由

我不明白你在说什么。 一个负责自己(反)序列化的类正是我想要的,请不要告诉我它是对还是错。

单一责任并没有说明涉及多少文件,它只是说必须有一个原因

我的意思是,当你添加一个新类时,你的意思是它不仅仅是被反序列化,所以它已经有理由存在并分配给它; 然后你添加另一个能够反序列化自身的责任,这给了我们两个责任,这违反了 SOLID,如果你继续向其中添加更多的东西,比如渲染、打印、复制、传输、加密等,你会得到一个神级

请原谅我告诉你一些陈词滥调,但是关于好处和实际用例的问题(和答案)是推动这个功能提案得以实施的原因

SOLID 不是全能神在平板电脑上发送的,即使是,它的解释也有一定程度的自由度。 您可能会随心所欲地理想主义,但请帮我一个忙:不要将您的信仰传播到整个社区。 每个人都有权以任何他想要的方式使用任何工具并打破他知道的所有可能的规则(你不能因为谋杀而责备一把刀)。 定义工具质量的是对某些功能的需求和对它们的报价之间的平衡。 这个分支显示了需求量。 如果您不需要此功能 - 请不要使用它。 我愿意。 而且这里的很多人也确实需要它,而且_我们_有一个实际的用例,而实际上你说我们应该忽略它。 这只是关于对维护者更重要的东西——神圣原则(无论他们以何种方式理解)或社区。

伙计,任何好的提案都有用例,这个没有

image

所以我只是表达了一些好奇并问了一个问题,既然提案没有说,为什么你们可能需要它

它归结为典型:正当理由,你不敢

就我个人而言,我不在乎实体或 oop,我只是碰巧在很久以前就过度生长了,您通过抛出“超级反模式”论点来提出它,然后支持“对其解释的一定程度的自由度”

在整个讨论中提到的唯一实际原因是: https ://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119

一个非常常见的用例是 React 组件,它们是具有静态属性的类,例如 displayName、propTypes 和 defaultProps。

和一些类似的帖子https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -345496014

但它被(new (...) => MyClass) & MyStaticInterface覆盖

这就是确切的用例,也是我来这里的原因。 看到票数了吗? 为什么你认为由你个人决定什么是practical什么不是? Practical是可以放入practice的,并且(在撰写本文时)83 人会发现这个功能非常实用。 在开始将短语从上下文中拉出来并展示各种流行语之前,请尊重他人并阅读完整的主题。 无论你克服了什么,那绝对不是你的自我。

这是常识,实用的东西是那些解决问题的东西,非实用的东西是那些让你的美感刺痛的东西,我尊重别人,但尽管尊重别人,但这个问题(现在大多是修辞)仍然存在:这个提案打算解决什么问题解决给定(new (...) => MyClass) & MyStaticInterfacehttps://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119 和https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -289084844 的原因

请不要回答

出于同样的原因,我有时会使用type声明来减少大型注释,我认为像abstract static这样的关键字会比已经提到的相对更难消化的结构更具可读性例子。

另外,我们还没有解决抽象类?

在我看来,不使用抽象类的解决方案不是解决方案。 那是一种解决方法! 解决什么问题?

我认为此功能请求存在是因为包括请求者在内的许多人发现接口中不存在预期的功能,例如abstract staticstatic

根据提供的解决方案,如果有避免使用它的变通方法,是否需要存在static关键字? 我认为这同样是荒谬的。

这里的问题是static更有意义。
基于产生的兴趣,我们可以进行一些不那么轻视的讨论吗?

该提案是否有任何更新? 任何值得考虑的论点都证明了为什么我们不应该有static abstract之类的东西?
我们能否提出更多建议来说明它为何有用?

或许我们需要整理并总结讨论过的内容,这样我们才能找到解决方案。

据我了解,有两个建议:

  1. 接口和类型可以定义静态属性和方法
interface ISerializable<T> { 
   static fromJson(json: string): T;
}
  1. 抽象类可以定义抽象静态方法
abstract class MyClass<T> implements ISerializable<T> {
   abstract static fromJson(json: string): T;
}

class MyOtherClass extends MyClass<any> {
  static fromJson(json: string) {
  // unique implementation
  }
}

至于建议一,从技术上讲,有一个解决方法! 这不是很好,但至少是这样。 但这是一种解决方法。

您可以将您的接口分成两个并重新编写您的类,例如

interface StaticInterface {
  new(...args) => MyClass;
  fromJson(json): MyClass;
}

interface InstanceInterface {
  toJson(): string;
}

const MyClass: StaticInterface = class implements InstanceInterface {
   ...
}

在我看来,这是很多额外的工作并且可读性稍差,并且有以一种有趣的方式重写你的类的缺点,这很奇怪并且偏离了我们使用的语法。

但是,那么提案 2 呢? 对此没有什么可以做的,不是吗? 我认为这也值得解决!

其中一种类型的实际用途是什么?如何使用其中一种?

interface JsonSerializable {
    toJSON(): string;
    static fromJSON(serializedValue: string): JsonSerializable;
}

已经可以说一个值必须是一个像{ fromJSON(serializedValue: string): JsonSerializable; }这样的对象,所以这只是为了强制执行一个模式吗? 从类型检查的角度来看,我没有看到这样做的好处。 附带说明:在这种情况下,它将强制执行一种难以使用的模式——最好将序列化过程移动到单独的序列化程序类或函数中,原因有很多,我不会在这里讨论。

另外,为什么要这样做?

class FirstChildClass extends AbstractParentClass {
    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

那么使用模板方法或策略模式呢? 那会更有效,更灵活,对吧?

目前,我反对这个特性,因为对我来说,它似乎增加了不必要的复杂性来描述一个难以使用的类设计。 也许我缺少一些好处?

静态 React 方法有一个有效用例,仅此而已

@aleksey-bykov 啊,好的。 对于这些情况,如果在constructor属性上添加类型会导致在这种罕见情况下进行类型检查,则可能会更好。 例如:

interface Component {
    constructor: ComponentConstructor;
}

interface ComponentConstructor {
    displayName?: string;
}

class MyComponent implements Component {
    static displayName = 5; // error
}

这对我来说似乎更有价值。 它不会给语言增加任何额外的复杂性,只会在检查类是否正确实现接口时为类型检查器增加更多工作。


顺便说一句,我认为一个有效的用例是从实例到构造函数,最后到带有类型检查的静态方法或属性,但这已经可以通过在类型上键入constructor属性来实现并将在#3841 中为类实例解决。

我有一个类似的想法: https ://github.com/Microsoft/TypeScript/issues/14600#issuecomment -437071092

上游描述的序列化/反序列化模式不是“有效”吗?

@dsherret没有“从类型检查的角度看到好处”。 首先进行静态分析的重点是尽早发现错误。 我键入内容,以便如果调用签名需要更改,那么调用它的每个人——或者,关键的是,负责实现使用签名的方法的每个人——都更新到新的签名。

假设我有一个库,它提供了一组带有静态foo(x: number, y: boolean, z: string)方法的兄弟类,并且期望用户将编写一个工厂类,该类采用其中的几个类并调用该方法来构造实例。 (也许是deserializecloneunpackloadFromServer ,没关系。)用户还创建了相同(可能是抽象的)父级的子类从图书馆上课。

现在,我需要更改该合同,以便最后一个参数是一个选项对象。 为第三个参数传递一个字符串值是一个不可恢复的错误,应该在编译时进行标记。 任何工厂类都应该在调用foo时更新为传递对象,并且实现foo的子类应该更改它们的调用签名。

我想保证更新到新库版本的用户将在编译时捕获重大更改。 该库必须导出上述静态接口解决方法之一(如type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface; ),并希望消费者将其应用到所有正确的地方。 如果他们忘记装饰其中一个实现,或者如果他们没有使用库导出类型来描述工厂类中foo的调用签名,编译器将无法判断任何更改,并且会出现运行时错误. 相比之下,在父类中合理地实现abstract static方法——不需要特殊的注释,对消费代码没有负担,它开箱即用。

@thw0rted大多数库不会需要对这些类进行某种注册,并且那时可以进行类型检查吗? 例如:

// in the library code...
class SomeLibraryContext {
    register(classCtor: Function & { deserialize(serializedString: string): Component; }) {
        // etc...
    }
}

// then in the user's code
class MyComponent extends Comonent {
    static deserialize(serializedString: string) {
        return JSON.parse(serializedString) as Component;
    }
}

const context = new SomeLibraryContext();
// type checking occurs here today. This ensures `MyComponent` has a static deserialize method
context.register(MyComponent);

或者这些类的实例是否与库一起使用,并且库从实例到构造函数以获取静态方法? 如果是这样,可以将库的设计更改为不需要静态方法。

附带说明一下,作为接口的实现者,如果我被迫编写静态方法,我会非常恼火,因为由于缺少构造函数(不可能有 ctor 依赖项),很难将依赖项注入静态方法。 这也使得很难根据情况将反序列化的实现换成其他东西。 例如,假设我使用不同的序列化机制从不同的源反序列化......现在我有一个静态反序列化方法,按照设计只能以一种方式实现类的实现。 为了解决这个问题,我需要在类上有另一个全局静态属性或方法来告诉静态反序列化方法使用什么。 我更喜欢单独的Serializer接口,它可以轻松换出要使用的内容,并且不会将(反)序列化与正在(反)序列化的类耦合。

我明白你的意思——要使用工厂模式,最终你必须将实现类传递给工厂,并且此时会发生静态检查。 我仍然认为有时你想提供一个符合工厂标准的类,但目前没有实际使用它,在这种情况下, abstract static约束会更早发现问题并使含义更清晰。

我还有另一个例子,我认为它不会与您的“旁注”问题发生冲突。 我在前端项目中有许多同级类,我让用户选择使用哪个“提供者”来实现某个功能。 当然,我可以在name => Provider的某个地方制作字典并使用键来确定要在选择列表中显示的内容,但我现在实现它的方式是需要一个静态name字段在每个 Provider 实现上。

在某些时候,我将其更改为同时要求shortNamelongName (用于在具有不同可用屏幕空间量的不同上下文中显示)。 将abstract static name: string;更改为abstract static shortName: string;等会更直接,而不是将选择列表组件更改为具有providerList: Type<Provider> & { shortName: string } & { longName: string } 。 它在正确的位置(即在抽象父级中,而不是在使用组件中)传达意图,并且易于阅读和更改。 我认为我们可以说有一种解决方法,但我仍然认为客观上它比提议的更改更糟糕。

我最近偶然发现了这个问题,当我需要在接口中使用静态方法时。 如果之前已经解决过这个问题,我深表歉意,因为我没有时间阅读这么多评论。

我当前的问题:我有一个私有的 .js 库,我想在我的 TypeScript 项目中使用它。 所以我继续为该库编写一个 .d.ts 文件,但由于该库使用静态方法,我无法真正完成。 在这种情况下建议的方法是什么?

感谢您的回答。

@greeny这里是一个可行的解决方案: https ://github.com/Microsoft/TypeScript/issues/14600#issuecomment -437071092

特别是这部分:

type MyClass = (new (text: string) => MyInterface) & { myStaticMethod(): string; }

另一个用例:生成的代码/部分类垫片

  • 我正在使用@rsuter/nswag 库,它会生成招摇规范。
  • 您可以编写一个“扩展”文件,将其合并到生成的文件中。
  • 扩展文件永远不会运行,但它需要自己编译!
  • 有时在该文件中我需要引用尚不存在的东西(因为它已生成)
  • 因此我想为它声明一个垫片/接口如下
  • 注意:您可以指定在最终合并中忽略某些import语句,以便在最终生成的代码中忽略“shim”包含。
interface SwaggerException
{
    static isSwaggerException(obj: any): obj is SwaggerException;
}

但是我不能这样做,并且必须实际编写一个类-这很好,但仍然感觉不对。 我只想 - 正如许多其他人所说 - 说“这是合同”

只是想在此处添加此内容,因为我没有看到任何其他提及代码生成的内容-但是许多“您为什么需要这个”评论只会让人恼火。 我希望在界面中“需要”这个“静态”功能的人中有相当一部分人这样做只是为了让他们可以参考外部图书馆项目。

此外,我真的很想看到更干净的d.ts文件 - 此功能和那些文件必须有一些重叠。 很难理解JQueryStatic之类的东西,因为它似乎只是一个 hack。 此外,现实情况是d.ts文件通常已过时且未维护,您需要自己声明 shim。

(很抱歉提到 jQuery)

对于序列化案例,我做了类似的事情。

export abstract class World {

    protected constructor(json?: object) {
        if (json) {
            this.parseJson(json);
        }
    }

    /**
     * Apply data from a plain object to world. For example from a network request.
     * <strong i="6">@param</strong> json Parsed json object
     */
    abstract parseJson(json: object): void;

    /**
     * Parse instance to plane object. For example to send it through network.
     */
    abstract toJson(): object;
}

但是使用这样的东西仍然会更容易:

export abstract class World {

    /**
     * Create a world from plain object. For example from a network request.
     * <strong i="10">@param</strong> json Parsed json object
     */
    abstract static fromJson(json: object): World;

    /**
     * Parse instance to plane object. For example to send it through network.
     */
    abstract toJson(): object;
}

我红了很多这个线程,我仍然不明白为什么对这种模式说不是好的和正确的。 如果你同意这个观点,你也会说 java 和其他流行的语言是错误的。 还是我错了?

本期开放两年,有 79 条评论。 它被标记为Awaiting More Feedback 。 你能说一下你还需要什么反馈来做出决定吗?

我不能在接口和抽象类中描述静态方法(仅限声明),这真的很烦人。 由于此功能尚未实现,因此编写变通方法非常难看=(

例如,next.js 使用静态getInitialProps函数在构建页面之前获取页面属性。 如果它抛出,页面不会被构建,而是错误页面。

https://github.com/zeit/next.js/blob/master/packages/next/README.md#fetching -data-and-component-lifecycle

但不幸的是,实现此方法的线索 ts 可以给它任何类型签名,即使它在运行时导致错误,因为它无法进行类型检查。

我认为这个问题存在这么长时间是因为 JavaScript 本身不擅长静态的东西🤔

静态继承首先不应该存在。 🤔🤔

谁想调用静态方法或读取静态字段? 🤔🤔🤔

  • 子类:它不能是静态的
  • 父类:使用构造函数参数
  • 其他:使用 ISomeClassConstructor 接口

还有其他用例吗?🤔🤔🤔🤔

有任何更新吗?

如果有任何帮助,我在需要为类键入静态接口的情况下所做的是使用装饰器来强制类上的静态成员

装饰器定义为:

export const statics = <T extends new (...args: Array<unknown>) => void>(): ((c: T) => void) => (_ctor: T): void => {};

如果我将静态构造函数成员接口定义为

interface MyStaticType {
  new (urn: string): MyAbstractClass;
  isMember: boolean;
}

并在应该将 T 上的成员静态声明为的类上调用:

@statics<MyStaticType>()
class MyClassWithStaticMembers extends MyAbstractClass {
  static isMember: boolean = true;
  // ...
}

最常见的例子很好:

interface JsonSerializable {
    toJSON(): string;
    static fromJSON(serializedValue: string): JsonSerializable;
}

但正如 #13462这里所说:

接口应该定义对象提供的功能。 此功能应该是可覆盖和可互换的(这就是接口方法是虚拟的原因)。 静力学是动态行为/虚拟方法的平行概念。

我同意 TypeScript 中的接口仅描述对象实例本身以及如何使用它的观点。 问题是对象实例不是定义, static符号可能只存在于定义上。

因此,我可能会提出以下建议,包括所有缺陷:


一个接口可以是描述一个对象,也可以是一个。 假设一个接口用关键字class_interface

class_interface ISerDes {
    serialize(): string;
    static deserialize(str: string): ISerDes
}

类(和类接口)可以使用statically implements关键字来使用对象接口声明它们的静态符号(接口不能被statically实现)。

类(和类接口)仍将implements关键字与对象接口或接口一起使用。

然后类接口可以在静态实现的对象接口和实例实现的接口之间混合。 因此,我们可以得到以下信息:

interface ISerializable{
    serialize(): string;
}
interface IDeserializable{
    deserialize(str: string): ISerializable
}

class_interface ISerDes implements ISerializable statically implements IDeserializable {}

这样,接口可以保持其含义,并且class_interface将成为一种专用于类定义的新型抽象符号。

还有一个小的非关键用例:
在用于 AOT 编译的 Angular 中,您不能在装饰器中调用函数(例如在@NgModule模块装饰器中)
角度问题

对于 service worker 模块,你需要这样的东西:
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production})
我们的环境使用子类,用默认值和抽象属性扩展抽象类来实现。 类似的实现示例
所以 AOT 不起作用,因为构造一个类是一个函数,并抛出如下错误: Function calls are not supported in decorators but ..

为了使其工作并保持自动完成/编译器支持,可以在静态级别定义相同的属性。 但是为了“实现”属性,我们需要与抽象类中的静态成员或抽象静态成员的接口。 两者都还不可能。

with interface 可以这样工作:

// default.env.ts
interface ImplementThis {
  static propToImplement: boolean;
}

class DefaultEnv {
  public static production: boolean = false;
}

// my.env.ts
class Env extends DefaultEnv implements ImplementThis {
  public static propToImplement: true;
}

export const environment = Env;

抽象静态可以这样工作:

// default.env.ts
export abstract class AbstractDefaultEnv {
  public static production: boolean = false;
  public abstract static propToImplement: boolean;
}
// my.env.ts
class Env extends AbstractDefaultEnv {
  public static propToImplement: true;
}

export const environment = Env;

解决方法,但它们都很弱:/

@DanielRosenwasser @RyanCavanaugh为这些提及道歉,但似乎这个功能建议——得到了社区的大力支持,我觉得很容易实施——已经深深地埋在问题类别中。 你们中的任何一个人对此功能有什么意见吗?欢迎 PR 吗?

这个问题不是#1263的重复吗? 😛

26398(基于实现类型的构造函数属性的类型检查静态成员)看起来是一个更好的解决方案......如果要实现这样的事情,那么我希望它就是那个。 这不需要任何额外的语法/解析,并且只是针对单个场景的类型检查更改。 它也没有提出这么多的问题。

我觉得接口中的静态方法不像抽象类上的静态抽象方法那样直观。

我认为向接口添加静态方法有点粗略,因为接口应该定义一个对象,而不是一个类。 另一方面,抽象类当然应该允许具有静态抽象方法,因为抽象类用于定义子类。 就其实现而言,它只需要在扩展抽象类(例如class extends MyAbstractClass )时进行类型检查,而不是在将其用作类型时(例如let myInstance: MyAbstractClass )。

例子:

abstract class MyAbstractClass {
  static abstract bar(): number;
}

class Foo extends MyAbstractClass {
  static bar() {
    return 42;
  }
}

现在出于必要我使用这个

abstract class MultiWalletInterface {

  static getInstance() {} // can't write a return type MultiWalletInterface

  static deleteInstance() {}

  /**
   * Returns new random  12 words mnemonic seed phrase
   */
  static generateMnemonic(): string {
    return generateMnemonic();
  }
}

这很不方便!

我遇到了一个问题,我向“对象”添加属性,这是一个沙盒示例

interface Object {
    getInstanceId: (object: any) => number;
}

Object.getInstanceId = () => 42;
const someObject = {};
Object.getInstanceId(someObject); // correct
someObject.getInstanceId({}); // should raise an error but does not

任何对象实例现在都被认为具有属性getInstanceId而只有Object应该。 有了静态属性,问题就解决了。

你想增加 ObjectConstructor,而不是 Object。 当您实际上想要将方法附加到构造函数本身时,您正在声明一个实例方法。 我认为这已经可以通过声明合并来实现:

````ts
声明全局{
接口对象构造器{
你好():字符串;
}
}

对象.你好();
````

@thw0rted 太好了! 谢谢我不知道 ObjectConstructor

更大的一点是您正在扩充构造函数类型而不是实例类型。 我刚刚在lib.es5.d.ts中查找了Object声明,发现它的类型是ObjectConstructor

我很难相信这个问题仍然存在。 这是一个合法有用的功能,有几个实际用例。

TypeScript 的重点是能够确保我们代码库中的类型安全,那么,为什么这个功能在经过两年的反馈后仍然“等待反馈”呢?

我可能在这方面还差得很远,但是像 Python 的元类这样的东西是否能够在不违反 TypeScript 的范式(即为实例和班上)?

像这样的东西:

interface DeserializingClass<T> {
    fromJson(serializedValue: string): T;
}

interface Serializable {
    toJson(): string;
}

class Foo implements Serializable metaclass DeserializingClass<Foo> {
    static fromJson(serializedValue: string): Foo {
        // ...
    }

    toJson(): string {
        // ...
    }
}

// And an example of how this might be used:
function saveObject(Serializable obj): void {
    const serialized: string = obj.toJson();
    writeToDatabase(serialized);
}

function retrieveObject<T metaclass DeserializingClass<T>>(): T {
    const serialized: string = getFromDatabase();
    return T.fromJson(serialized);
}

const foo: Foo = new Foo();
saveObject(foo);

const bar: Foo = retrieveObject<Foo>();

老实说,这种方法最棘手的部分似乎是为metaclass ... staticimplementsclassimplementswithstatic提出一个有意义的 TypeScript-y 关键字implementsstatic ...不确定。

这有点像@GerkinDev提议,但没有单独的接口。 在这里,只有一个接口的概念,它们可以用来描述实例或类的形状。 实现类定义中的关键字将告诉编译器应该检查每个接口的哪一侧。

让我们在 #34516 和 #33892 进行讨论,具体取决于您要使用的功能

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

rwyborn picture rwyborn  ·  210评论

rbuckton picture rbuckton  ·  139评论

Taytay picture Taytay  ·  174评论

chanon picture chanon  ·  138评论

blakeembrey picture blakeembrey  ·  171评论