Typescript: Ajouter des interfaces de support pour définir des méthodes statiques

Créé le 13 janv. 2017  ·  43Commentaires  ·  Source: microsoft/TypeScript

Version TypeScript: 2.0.3

Code

interface Foo {
public static myStaticMethod(param1: any);
}

Comportement prévisible:
Aucune erreur
Comportement réel:
Non pris en charge

Question

Commentaire le plus utile

@aluanhaddad Je trouve que votre code est une solution de contournement de quelque chose qui est évident.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Lequel voyez-vous le plus clairement?

Tous les 43 commentaires

Que voulez-vous accomplir avec cela? pouvez-vous élaborer sur le scénario?

J'imagine que les classes abstract sont ce que vous recherchez.

@mhegazy
Exemple:

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

@rozzzly Dans cet exemple, abstract class n'est pas valide.

Je pense que ce serait plutôt cool! Java a ajouté cette fonctionnalité dans la dernière version.

@Serginho
Je pense que vous trouverez peut-être cela intéressant:

interface JsonSerializableStatic<C extends new (...args) => JsonSerializable<C>> {
  fromJson(json: string): JsonSerializable<C>;
}

interface JsonSerializable<C extends new (...args) => any> {
  toJson: () => string;
  constructor: C;
}

interface A extends JsonSerializable<typeof A> { }
class A implements JsonSerializable<typeof A> {

  constructor(readonly id: number, readonly name: string) { }
  toJson() { return JSON.stringify(this); }

  static fromJson(json: string): A {
    const obj = JSON.parse(json);
    return new A(obj.id, obj.name);
  }
}

const a = new A(1, 'Charlize');

const json = a.toJson();

const y = A.fromJson(json);
console.info(a, json, y);
console.info(new a.constructor(1, 'Theron'));
const m = new A.prototype.constructor(1, 'Charlize Theron');
console.info(m);

@aluanhaddad Je trouve que votre code est une solution de contournement de quelque chose qui est évident.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Lequel voyez-vous le plus clairement?

@aluanhaddad Je trouve que votre code est une solution de contournement de quelque chose qui est évident.

interface JsonSerializable {
public static fromJson (obj: any);
public toJson (): chaîne;
}
Lequel voyez-vous le plus clairement?

En effet, mais la mienne n'est pas une solution de contournement, c'est une justification de votre cas d'utilisation. Avoir la désérialisation statique et la sérialisation des instances implémentées classe par classe et ainsi encapsulées et sécurisées.
Votre déclaration:

interface JsonSerializable {
     public static fromJson(obj: any);
     public toJson(): string;
}

n'est pas pertinent car il re-déclare essentiellement l'interface de l'objet JSON. Cela ne nécessite absolument aucune méthode d'interface statique.

Vous perdez le concept d'interface. class A implements JsonSerializable devrait me faire implémenter les deux méthodes. Mais en fait, je mets en œuvre:

toJson: () => string;
constructor: new (...args) => JsonSerializableStatic<C>;

Ce n'est pas une solution claire.

Il n'y a aucune raison technique pour ne pas permettre de définir des méthodes statiques sur les interfaces.

@Serginho Je ne dis pas que la situation est idéale. J'essayais simplement d'illustrer que cela peut être exprimé.

@aluanhaddad Allez! Ouvre ton esprit. Pensez-vous que dactylographié devrait autoriser les méthodes statiques dans les interfaces? cela a été implémenté dans Java 8 dans la dernière version, donc je pense que je ne dis pas de bêtises.

@Serginho Je ne pense pas que ce soit une

Comme @aluanhaddad l'a déjà écrit, TypeScript dispose déjà d'un mécanisme pour exprimer ce que vous désirez. La grande différence est que dans ce cas, vous traitez la classe comme un objet, qui continue à être cohérent logiquement. De mon point de vue, l'approche que vous proposez n'est pas particulièrement bien adaptée à TypeScript (et au développement JavaScript) car les classes sont une sorte de hack / citoyens de seconde classe. Les modèles actuellement en vigueur comptent sur la programmation de type ducktyping / forme, pas sur des classes rigides.

Je suppose que vous pouvez également ouvrir votre esprit et essayer différents styles de programmation. Le monde ne commence ni ne se termine avec la POO. Vous pouvez trouver la programmation avec des fonctions, des objets simples et (et dans une certaine mesure) des prototypes agréable et même meilleure. Un tel style est beaucoup plus conforme à ce pour quoi JavaScript a été initialement conçu. Sur une note latérale, je suis d'accord que JS évolue lentement vers des modèles OOP-esque (au moins au niveau du comité), mais c'est à cause de l'énorme poussée des gens qui ne sont pas à l'aise avec différents paradigmes de programmation et techniques de développement. Pour moi, c'est une chose profondément triste et décevante.

Comme @gcnew l'a dit, une interface décrit la forme d'un objet individuel, pas sa classe. Mais il n'y a aucune raison pour laquelle nous ne pouvons pas utiliser une interface pour décrire la forme de la classe elle-même, puisque les classes sont aussi des objets.

import assert = require("assert");

interface JsonSerializableStatic<JsonType, InstanceType extends JsonSerializable<JsonType>> {
    fromJson(obj: JsonType): InstanceType;
}
interface JsonSerializable<JsonType> {
    toJson(): JsonType;
}

interface PointJson { x: number; y: number; }
class Point /*static implements JsonSerializableStatic<PointJson, Point>*/ {
    static fromJson(obj: PointJson): Point {
        return new Point(obj.x, obj.y)
    }

    constructor(readonly x: number, readonly y: number) {}

    toJson(): PointJson {
        return { x: this.x, y: this.y };
    }
}
// Hack for 'static implements'
const _: JsonSerializableStatic<PointJson, Point> = Point;

function testSerialization<JsonType, InstanceType extends JsonSerializable<JsonType>>(cls: JsonSerializableStatic<JsonType, InstanceType>, json: JsonType) {
    const instance: InstanceType = cls.fromJson(json);
    const outJson: JsonType = instance.toJson();
    assert.deepEqual(json, outJson);
}
testSerialization(Point, { x: 1, y: 2 });

Ne pas respecter la signature de toJson ou fromJson entraîne une erreur de compilation. (Malheureusement, l'erreur est à const _ plutôt qu'à la méthode.)

Probablement lié (puisqu'il traite de la saisie de méthodes statiques): # 5863.


@aluanhaddad : Votre exemple a une erreur: le membre JsonSerializable de constructor ferait en fait référence à une propriété d'instance appelée constructor , qui, lorsqu'elle est invoquée avec new retourne un JsonSerializableStatic . Cela signifierait que (new ((new X()).constructor)).fromJson({}) devrait fonctionner. La raison pour laquelle la compilation réussit est que interface A extends JsonSerializable<typeof A> déclare l'implémentation valide sans la vérifier. Par exemple, cela compile sans erreur:

interface I { m(): void; }
interface A extends I { }
// No compile error
class A implements I { em() {} }

@Serginho Pas un utilisateur Java, mais il ne semble pas que le langage vous permette de définir une interface pour le côté statique d'une classe (ce qui signifie que les classes implémentant l'interface devraient implémenter une méthode statique pour se conformer). Java vous permet de définir une méthode statique avec un corps dans une interface, dont l'équivalent TypeScript serait:

interface I { ... }
namespace I {
   export function interfaceStaticMethod() {}
}

Et si vous essayiez de créer une usine générique?

interface Factorizable {
  static factory<U>(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T extends Factorizable>(): T[] {
    return this.data.map(T.factory);
  }
}

class Bar implements Factorizable {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y: Bar[] = x.bar();

Je ne suis pas sûr de la syntaxe interface que je propose ici, mais je sais que la syntaxe «d'utilisation» est ce que j'essaie de réaliser. C'est un modèle que j'utilise fréquemment dans Swift (en utilisant protocols ) et je pense que ce serait vraiment bien dans TypeScript. Bien que n'étant pas un concepteur de langage ou un implémenteur de compilateur, je ne suis pas sûr s'il correspond à la direction prévue de TypeScript ou s'il est réaliste pour être implémenté.

La classe n'a pas besoin de implement l'interface. vous définissez simplement l'interface et les vérifications de type structurel mettront en cache tous les problèmes sur le site d'utilisation, par exemple:

interface Factorizable<U> {
    factory(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T>(factory: Factorizable<T>): T[] {
    return this.data.map(factory.factory);
  }
}

class Bar {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y = x.bar(Bar); // Bar[]

@mhegazy qui est une solution relativement bonne. Merci pour cela! 🙏

Il y a encore deux choses qui me gênent (il est vrai que ce ne sont pas non plus des bouchons):

  1. Nous ne pouvons toujours pas déclarer que Bar implémente ou se conforme explicitement à Factorizable .

    • En pratique, j'imagine que ce n'est vraiment pas un problème. Puisqu'il serait vrai que si l'interface Factorizable change de manière incompatible, l'utilisation x.bar(Bar) commencerait à se tromper et alors vous corrigeriez les changements de dérive.
  2. Pour moi, c'est toujours un fardeau cognitif de déclarer le type de y sur le côté droit de l'affectation.

    • Plus étrange encore, cette syntaxe permettrait ce comportement: var y: Baz[] = x.bar(Bar) . Evidemment, une erreur, mais la syntaxe permet au développeur de sur-contraindre le problème en définissant le type de retour à deux endroits.

Nous ne sommes toujours pas en mesure de déclarer que Bar implémente ou se conforme explicitement à Factorizable.

Il y a deux types impliqués, 1. la fonction constructeur (par exemple le côté statique de la classe), et 2. le côté instance (ce qui sort lors de l'appel de new ). Mélanger ces deux dans un seul type n'est pas correct. En théorie, vous pouvez avoir implements et static implements mais en pratique, comme vous l'avez noté, cela est rarement utilisé et la clause implements n'ajoute vraiment pas grand-chose. La vérification est effectuée sur le site d'utilisation de toutes les manières, que vous ayez une clause implements ou non.

Pour moi, c'est toujours un fardeau cognitif de déclarer le type de y sur le côté droit de l'affectation.

Les deux signifient des choses différentes, var y = x.bar(Bar) déclare une nouvelle variable y avec le même type que x.bar(Bar) ; où comme var y: Bar[] = x.bar(Bar) déclare une nouvelle variable y de type Bar[] et vérifie que le type de x.bar(Bar) est assignable à Bar[] .

Ayant siad cela, c'est plutôt un problème de style. Personnellement, ma recommandation est de ne pas utiliser les annotations de type explicitement sauf si vous devez le faire; laissez les types circuler dans le système. J'ai vu des bases de code, cependant, où le guide de style est le contraire, où tout a une annotation de type explicite.

@mhegazy merci pour la discussion / perspective.

@ andy-hanson Merci d'avoir pris le temps de me corriger. J'ai corrigé l'erreur dans mon exemple.

Cela fonctionne également et affiche une erreur au moment de la compilation sans aucun appel de fonction supplémentaire:

interface Type<T> {
    new (...args: any[]): T;
}

/* static interface declaration */
interface ComparableStatic<T> extends Type<Comparable<T>> {
    compare(a: T, b: T): number;
}

/* interface declaration */
interface Comparable<T> {
    compare(a: T): number;
}

/* class decorator */
function staticImplements<T>() {
    return (constructor: T) => {}
}

@staticImplements<ComparableStatic<TableCell>>()   /* this statement implements both normal interface & static interface */
class TableCell { /* implements Comparable<TableCell> { */  /* not required. become optional */
    value: number;

    compare(a: TableCell): number {
        return this.value - a.value;
    }

    static compare(a: TableCell, b: TableCell): number {
        return a.value - b.value;
    }
}

Quel est le résultat de la discussion?

J'ai rencontré ceci et je veux aussi utiliser static interface :

interface IDb {
  public static instance: () => Db,
}

La plupart des gens oublient qu'il existe déjà des interfaces _statiques_ dans le sens où une fonction / classe constructeur a déjà deux interfaces, l'interface constructeur et l'interface d'instance.

interface MyFoo {
  method(): void;
}

interface MyFooConstructor {
  new (): MyFoo;
  prototype: MyFoo;
  staticMethod(): any;
}

const MyFoo = function MyFoo() {
  this.prop = '';
} as any as MyFooConstructor;

MyFoo.prototype = {
  method() { console.log(this); }
}

MyFoo.staticMethod = function () { /* do something static */ }

Si vous n'utilisez pas de classes _abstract_, vous avez déjà le pouvoir.

Merci @kitsonk d' avoir répondu.

Votre déclaration semble devoir fonctionner, mais elle est trop verbeuse pour le cas.

Et j'ai juste essayé les classes _abstract_, mais il ne semble pas prendre en charge static avec abstract .

[ts] 'static' modifier cannot be used with 'abstract' modifier.

@zixia qui est le numéro 14600

Ouais, votons.

Quelqu'un voudrait-il répondre avec une solution à ma question ici: http://stackoverflow.com/questions/44047874/dynamically-modify-typescript-classes-through-a-generic-function

Je pense que tout ce problème serait résolu en étant capable de spécifier des membres statiques sur les interfaces, et si ce problème est résolu parce qu'il n'est pas nécessaire , j'aimerais beaucoup voir comment le résoudre à la place.

@grantila J'ai répondu à votre question. Comme mentionné précédemment dans ce numéro, sauf si vous avez d'autres exigences non mentionnées ici, cela peut facilement être résolu en traitant les classes comme des objets.

@ Enet4 J'ai mis à jour la question, elle était trop simplifiée. Le vrai problème ne peut malheureusement pas être résolu par le hack Object.defineProperty() . Quel btw, est un hack. Je veux m'assurer de ne pas avoir mal orthographié make - en gros une vérification statique correcte.

C'est un vrai problème que j'ai, du code que j'ai commencé à porter sur TypeScript mais que j'ai maintenant conservé en JavaScript, car je ne peux actuellement pas réécrire des quantités aussi énormes de code que cela aurait été nécessaire autrement.

Je veux m'assurer de ne pas avoir mal orthographié make - essentiellement une vérification statique appropriée.

La vérification statique ne peut aller jusque-là ici. Mais si votre seul souci maintenant est que vous définissez la bonne propriété, alors la conversion en un type de fabricant et la définition de la propriété à partir de là semblent résoudre ce problème.

@ Enet4 c'est une solution de travail, merci. Je pense que ce problème (13462) devrait être réexaminé, car des solutions comme la vôtre, utilisant la conversion de type, ne sont en fait pas sûres, et si c'est le seul moyen de résoudre le problème de travailler avec le type de classe comme valeur, nous perdez beaucoup de la flexibilité d'un langage dynamique.

des solutions comme la vôtre, qui utilisent la conversion de type, ne sont en fait pas sûres, et si c'est la seule façon de résoudre le problème de travailler avec le type de classe en tant que valeur, nous perdons beaucoup de flexibilité d'un langage dynamique.

@grantila Pour ma défense, c'est discutable. : wink: votre cas d'utilisation est différent de ceux présentés dans ce numéro, en ce que votre type de classe peut (ou non) fournir une méthode en fonction des conditions d'exécution. Et IMO qui est plus dangereux que le type cast présenté dans ma réponse, qui n'a été effectué que pour permettre l'insertion d'un champ dans un objet. En ce sens, le type de classe résultant C & Maker<T> devrait rester compatible avec tout le reste reposant sur un C .

J'ai également essayé d'imaginer où les méthodes statiques dans les interfaces pourraient vous aider ici, mais il me manque peut-être quelque chose. Même si vous aviez quelque chose comme un static make?(... args: any[]): self dans votre interface, il faudrait vérifier son existence au moment de l'exécution avant un appel. Si vous souhaitez poursuivre cette discussion, envisagez de le faire ailleurs pour réduire le bruit. : légèrement_smiling_face:

Nous ne pouvons donc pas taper les méthodes de fabrique statique dans les classes qui implémentent la même interface?

Mon cas d'utilisation est:

interface IObject {
    static make(s: string): IObject;
}

class A implements IObject{
    static make(s: string): IObject {
        // Implementation A...
    }
}

class B implements IObject{
    static make(s: string): IObject {
        // Implementation B...
    }
}

A.make("string"); // returns A
B.make("string"); // returns B

Je ne veux pas écrire une nouvelle classe d'usine juste pour ça.

@ tyteen4a03 Supprimez IObject de cet exemple, et il compilera. Voir également https://github.com/Microsoft/TypeScript/issues/17545#issuecomment -319422545

@ andy-ms Oui, évidemment, cela fonctionne mais le but de la vérification de type est de .. vérifier les types. Vous pouvez toujours dégrader suffisamment la sécurité des types pour que chaque cas d'utilisation soit compilé, mais cela ignore le fait qu'il s'agit d'une demande de fonctionnalité et pas si folle que cela.

Oui, évidemment, cela fonctionne, mais le point entier de la vérification de type est de .. vérifier les types.

Il s'agit d'un long long fil de discussion sur la façon dont le côté statique de la classe est une interface distincte du côté de l'instance et implements pointe vers le côté de l'instance. @ andy-ms indiquait à @ tyteen4a03 comment faire fonctionner un extrait de code, car il était _ faux_, ne pas renoncer à la vérification de type.

Mon cas d'utilisation pour autoriser les méthodes statiques à utiliser le paramètre générique de classe concerne les classes mixin. Je construis un cadre d'entité qui utilise des annotations pour définir des colonnes, des bases de données, etc. et j'aimerais avoir une fonction statique mélangée à mes classes d'entité qui permettrait un accès pratique au référentiel correctement typé.

class RepositoryMixin<T> {
    public static repository(): EntityRepository<T> {
        return new EntityRepository<T>(Object.getPrototypeOf(this));
    }
}

@mixin(RepositoryMixin)
class Entity implements RepositoryMixin<Entity> {
    public id: number;
}

Entity.repository().save(new Entity());

@rmblstrp Pouvez-vous montrer comment vous utiliseriez la fonctionnalité proposée dans votre exemple? De préférence comme quelque chose de vérifiable?

@rmblstrp qui ne nécessite pas cette fonctionnalité. En fait, puisque vous utilisez un décorateur, vous pouvez en fait _son type_ pour vérifier que les classes annotées fournissent les méthodes statiques requises. Vous n'avez pas besoin des deux et ce serait en fait plutôt redondant.

Bonjour, je ne veux pas être hors sujet ou hors du champ de cette conversation.
Cependant, puisque vous avez mis en discussion différents paradigmes de programmation (devrions-nous utiliser la POO ou la fonctionnalité?), Je veux parler spécifiquement des usines statiques qui sont généralement utilisées pour créer une connexion à une base de données ou fournir une sorte de service.
Dans de nombreux langages comme PHP et Java, les usines statiques ont été comme obsolètes en faveur de l'injection de dépendances. L'injection de dépendances et les conteneurs IOC sont devenus populaires grâce à des frameworks comme Symfony et Spring.
Typescript a un merveilleux conteneur IOC nommé InversifyJS.

Ce sont les fichiers où vous pouvez voir comment tout est géré.
https://github.com/Deviad/virtual-life/blob/master/models/generic.ts
https://github.com/Deviad/virtual-life/blob/master/service/user.ts
https://github.com/Deviad/virtual-life/blob/master/models/user.ts
https://github.com/Deviad/virtual-life/blob/master/utils/sqldb/client.ts
https://github.com/Deviad/virtual-life/blob/master/bootstrap.ts

Je ne dis pas que c'est une solution parfaite (et cela laisse probablement certains scénarios découverts) mais cela fonctionne aussi avec React, il y a déjà quelques exemples que vous pouvez regarder.

Aussi, je vous suggère de regarder cette vidéo sur la programmation fonctionnelle qui couvre cet aspect sur lequel est le meilleur: https://www.youtube.com/watch?v=e-5obm1G_FY&t=1487s
SPOILER: personne n'est meilleur, cela dépend du problème que vous souhaitez résoudre. Si vous traitez avec des utilisateurs, des salles de classe, des enseignants, la POO sera préférable de modéliser votre problème à l'aide d'objets.
Si vous avez besoin d'un analyseur qui scanne un site Web, peut-être fonctionnel peut-être mieux utiliser des générateurs de fonctions qui renvoient des résultats partiels, etc. :)

@Deviad @aluanhaddad Depuis mon message, j'utilise InversifyJS et cela a été absolument génial et certainement une bien meilleure façon de procéder. Au moment de mon article, je venais de commencer à utiliser Typescript / Node après avoir été PHP / C # auparavant. Il a juste fallu un peu de temps pour se familiariser avec l'environnement et les packages disponibles.

Quel est le statut de cela? Pourquoi continuez-vous à fermer les problèmes non résolus dans le repo?

Je crois que c'est l'un de ces cas où le problème devrait être explicitement étiqueté avec "wontfix", car le choix de ne pas avoir de méthodes statiques dans les interfaces est intentionnel.

@ enet4 , je suis un nouveau venu, mais ce n'était pas clair du tout. En lisant ceci et d'autres problèmes connexes, il semble que ce soit principalement les problèmes suivants:

A. C'est dur
B. Nous n'aimons pas (chaque) syntaxe spécifique que nous avons vue jusqu'à présent.
C. Une petite minorité ne pense pas que cela devrait être faisable du tout, et préfère supprimer la façon étrange actuelle de le faire.

Si c'est réellement par conception, et que les responsables ne le veulent pas, ils devraient écrire un document public et le relier à ce sujet et à d'autres. Cela nous ferait gagner beaucoup de temps en nous gardant dans les limbes.

Nous avons déjà lié à # 14600 dans ce fil et c'est le problème à suivre pour la demande de fonctionnalité.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

remojansen picture remojansen  ·  3Commentaires

blendsdk picture blendsdk  ·  3Commentaires

Roam-Cooper picture Roam-Cooper  ·  3Commentaires

siddjain picture siddjain  ·  3Commentaires

dlaberge picture dlaberge  ·  3Commentaires