Typescript: Autoriser un module à implémenter une interface

Créé le 10 août 2014  ·  34Commentaires  ·  Source: microsoft/TypeScript

Ce serait utile lorsqu'un module peut implémenter une interface en utilisant le mot clé implements . Syntaxe: module MyModule implements MyInterface { ... } .

Exemple:

interface Showable {
    show(): void;
}
function addShowable(showable: Showable) {

}

// This works:
module Login {
    export function show() {
        document.getElementById('login').style.display = 'block';
    }
}
addShowable(Login);

// This doesn't work (yet?)
module Menu implements Showable {
    export function show() {
        document.getElementById('menu').style.display = 'block';
    }
}
addShowable(Menu);
Suggestion help wanted

Commentaire le plus utile

Un cas d'utilisation serait pour les frameworks et les outils qui analysent un répertoire pour les modules au démarrage de l'application, en s'attendant à ce que ces modules exportent tous une certaine forme.

Par exemple, Next.js scanne ./pages/**/*.{ts,tsx} pour vos modules de page, générant des itinéraires basés sur vos noms de fichiers. C'est à vous de vous assurer que chaque module exporte les bonnes choses (un NextPage comme exportation par défaut, et un optionnel PageConfig export nommé config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Ce serait bien si vous pouviez à la place déclarer la forme d'exportation du module entier en une seule ligne près du haut, comme implements NextPageModule<Props> .

Une autre pensée: il serait intéressant qu'il y ait un moyen de spécifier dans une configuration TypeScript que tous les fichiers correspondant à un certain modèle (comme ./pages/**/*.{ts,tsx} ) doivent implémenter une certaine forme d'exportation, donc un module pourrait avoir son type d'export. vérifié uniquement parce qu'il se trouve dans le répertoire pages par exemple. Mais je ne sais pas s'il existe un précédent pour cette approche, et cela pourrait prêter à confusion.

Tous les 34 commentaires

Comment cela fonctionnerait-il avec des modules externes? Il est probable qu'une fois que les gens pourront l'utiliser pour un usage interne, ils voudront également l'utiliser avec un externe.

C'est une bonne question. Je ne sais pas quelle syntaxe serait la meilleure, mais voici quelques suggestions:

implements Showable; // I would prefer this one.
module implements Showable;
export implements Showable;

Cela ne devrait être autorisé que sur les modules externes qui n'utilisent pas une affectation d'exportation, car si vous utilisez une affectation d'exportation, la chose que vous exportez peut déjà avoir un implements à un autre endroit.

Approuvé. Nous préférons la syntaxe

export implements Showable;

et a convenu que cela n'était pas nécessaire pour les affectations de fichiers export = .

Quelques questions supplémentaires:

  • Puisque nous autorisons les modules à avoir des types sur les sites de déclaration, ne devrions-nous pas leur permettre également d'avoir des types sur les sites d'utilisation. par exemple:
declare module "Module" implements Interface { }

import i : Interface = require("Module");
  • Que faites-vous avec les déclarations fusionnées, devez-vous appliquer l'interface sur l'ensemble de toutes les déclarations? et que se passe-t-il s'ils ne correspondent pas en visibilité?
    par exemple:
module Foo {
    export interface IBar {
        (a:string): void;
    }

    export module Bar implements IBar {  // should this be an error?
        export interface Interface {}
    }    

    function Bar(a: string) : void { }  // not exported
}

var bar: Foo.IBar = Foo.Bar;

Il doit être autorisé sur les modules externes ambiants. Pour ces modules, deux syntaxes devraient être autorisées à mon avis:

declare module "first" implements Foo { }
declare module "second"  {
  interface Bar { }
  export implements Bar; // this syntax is necessary, with the first syntax you can't reference Bar.  
}

Ou Bar devrait-il être dans la portée d'une clause implements avant l'ouverture { ?

L'ajout d'informations de type à une instruction d'importation n'est pas vraiment utile à mon avis, car vous pouvez ajouter les informations de type au module lui-même.

Et pour les déclarations fusionnées, je dirais que le bloc de module qui contient la clause implements devrait implémenter l'interface. Cela évite également les problèmes de visibilité.

Comment cela serait-il lié à # 2159? Un espace de noms implémente une interface?

@jbondc Si nous avions cela, cela s'appliquerait également aux espaces de noms. Vous devriez considérer les modules internes et les espaces de noms comme isomorphes.

Êtes-vous sûr de vouloir emprunter un chemin d’implémentation où les «espaces de noms» peuvent implémenter des interfaces?

Oh wow, cela a été approuvé depuis un bon moment. @RyanCavanaugh , @DanielRosenwasser , @mhegazy sauf si vous avez des

Je retire mon scepticisme précédent, je suis en fait sorti pour les nouvelles possibilités structurelles que cela apporterait.

Conformément à cela, veuillez envisager d'appliquer l'interface de l'agrégat de l'interface au lieu du seul bloc qui déclare l'implémentation - La nature des espaces de noms / modules doit être étalée et contenir un grand nombre de composants non triviaux. J'aimerais pouvoir l'utiliser, mais je ne veux certainement pas définir tout mon espace de noms / module dans le même fichier. Pourquoi ne pas simplement utiliser une classe dans ce cas?

@ Elephant-Vessel Je ne sais pas si nous parlons de modules, ou d'espaces de noms, ou de packages, ou de fonctionnalités, ou ...

@aluanhaddad Que voulez-vous dire?

Je veux dire qu'au moment où cette discussion a commencé, le module ne voulait pas dire ce que cela signifie aujourd'hui. Nous utilisons maintenant le terme espace de noms pour désigner ce qui est décrit dans l'OP comme un module, tandis que module a pris une signification plus précise et incompatible. Ainsi, lorsque vous parlez de plusieurs fichiers participant à cette implémentation, parlez-vous d'espaces de noms ou de modules?

Je fais référence aux espaces de noms. Je suppose que je voulais juste me conformer à l'histoire de ce fil, désolé de ne pas se détacher :) Ou quand j'y pense, j'avais peut-être le terme générique de `` module '' dans ma tête, décrivant une unité de plus haut niveau composée d'un ensemble de sous-composants, assemblés pour fournir certaines fonctionnalités de haut niveau dans un système. Mais je suis d'accord avec juste des «espaces de noms».

Je veux donc être capable de décrire et de mettre des contraintes et des attentes sur des [_modules génériques_] qui peuvent contenir d'autres [_modules génériques_] ou des classes, en tirant parti des espaces de

J'espère que nous serons en mesure de mieux exprimer les attentes structurelles de plus haut niveau dans un système. Les classes ne s'adaptent pas bien, elles sont bonnes en tant que composants atomiques dans un système, mais je ne pense pas qu'une structure organisationnelle de niveau supérieur dans un système serait bonne à exprimer avec des classes car elles sont conçues pour être instanciées et héritées et des choses comme ça . C'est juste trop gonflé.

J'apprécierais une manière simple et propre de décrire la structure d'ordre supérieur du système, pas de chichi. De préférence, le seul problème étant les contraintes de visibilité directionnelles facultatives. Comme rendre impossible la référence à _MySystem.ClientApplication_ à partir de _MySystem.Infrastructure_ mais bien dans l'inverse. Ensuite, nous commencerions à aller dans un endroit passionnant.

@ Elephant-Vessel merci pour cette clarification. Je conviens que ce serait extrêmement précieux et que les types de classes ne sont pas la bonne approche ici. Je pense que vous avez frappé dans le mille lorsque vous parlez d'instanciation parce que les espaces de noms représentent des choses qui sont conceptuellement des singletons au niveau de la bibliothèque. Bien que cela ne puisse pas être appliqué, il serait utile sur le plan conceptuel d'avoir quelque chose qui n'implique pas plusieurs instanciations.

Je suis d'accord avec @ Elephant-Vessel. Bien qu'il soit facile de confondre TypeScript avec un autre Java, où toutes les contraintes sont exprimées avec une structure de classe unique, TS a un concept de "Shape" beaucoup plus large qui est très puissant et élimine le contortonisme sémantique. Malheureusement, l'incapacité de mettre des contraintes sur le module a tendance à forcer les développeurs à revenir à un modèle de classe pour des choses qui seraient beaucoup mieux exprimées en module.

Par exemple, pour les tests unitaires, il serait très utile de pouvoir exprimer une certaine «forme» (c'est-à-dire des contraintes) sur les modules afin que nous puissions fournir une implémentation alternative pour un contexte d'exécution particulier. Maintenant, il semble que la seule façon de faire cela d'une manière structurée / vérifiée est de revenir à la DI basée sur les classes (comme la Spring) et de faire de tout une classe (et donc instanciable).

Quoi qu'il en soit, je paraphrase @ Elephant-Vessel, mais si j'ai un seul souhait pour TS, ce serait celui-ci.

Un mot sur cet oiseau? J'ai ce problème aussi

soooo, euh, ne serait-ce pas un simple cas de:

export {} as IFooBar;

quel est le problème avec cette syntaxe? Je suppose que la syntaxe a déjà été approuvée, peut-être comme

export implements IFooBar

de toute façon j'ai hâte d'y être

Est-ce que cela a déjà été immatriculé / atterri? ça va être une fonctionnalité intéressante

Comment pouvons-nous progresser? C'est incroyablement puissant. Heureux de vous aider!

un worb sur ce birb? Une question que je me pose pour le moment est de savoir comment déclarer une interface pour l'exportation par défaut. Par exemple:

export default {}

Je suppose que je peux juste faire:

const x: MyInterface = {}
export default x;

cela fonctionnerait pour la plupart des fichiers TS, le problème avec cela, c'est que si vous codez d'abord pour JS et que vous prévoyez de passer à TS plus tard, cela ne fonctionne pas si bien.

Une autre chose à laquelle je pensais, qu'en est-il des espaces de noms qui implémentent? Quelque chose comme:

export namespace Foo implements Bar {

}

Je suppose que Bar serait un espace de noms _abstract_ lol idk

J'ai vu cette question se poser tant de fois, et je pense que nous cherchons tous juste une chose:
Prise en charge des membres statiques dans une interface.
Si cela se produit, vous pouvez simplement utiliser une classe avec des membres statiques et une interface, ce qui est presque la même chose que vous essayez de faire ici, non?

Dans tous les cas, ajoutez une prise en charge statique aux interfaces OU ajoutez une prise en charge d'interface pour les modules.

@shiapetel nah pas comme ça.

nous pouvons le faire:

export default <T>{
  foo: Foo,
  bar: Bar
}

mais ce n'est pas ce que nous recherchons. nous recherchons spécifiquement:

export const foo : Foo = {};
export const bar : Bar = {};

mais il n'y a actuellement aucun mécanisme pour forcer le module à exporter foo et bar. Et en fait, il n'y a pas non plus de mécanisme pour forcer le module à exporter la bonne valeur par défaut.

Si les interfaces prennent en charge les membres statiques, vous pouvez utiliser une classe avec un toto / bar statique hérité de:
Interface ILoveFooBar {
statique foo: FooType;
barre statique
}

Droite?
C'est ce que je voulais dire, je pense que cela aiderait dans votre situation - je sais que cela aiderait certainement dans la mienne.

Les membres statiques

Ce problème attend-il simplement que quelqu'un essaie de le mettre en œuvre?

Un cas d'utilisation serait pour les frameworks et les outils qui analysent un répertoire pour les modules au démarrage de l'application, en s'attendant à ce que ces modules exportent tous une certaine forme.

Par exemple, Next.js scanne ./pages/**/*.{ts,tsx} pour vos modules de page, générant des itinéraires basés sur vos noms de fichiers. C'est à vous de vous assurer que chaque module exporte les bonnes choses (un NextPage comme exportation par défaut, et un optionnel PageConfig export nommé config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Ce serait bien si vous pouviez à la place déclarer la forme d'exportation du module entier en une seule ligne près du haut, comme implements NextPageModule<Props> .

Une autre pensée: il serait intéressant qu'il y ait un moyen de spécifier dans une configuration TypeScript que tous les fichiers correspondant à un certain modèle (comme ./pages/**/*.{ts,tsx} ) doivent implémenter une certaine forme d'exportation, donc un module pourrait avoir son type d'export. vérifié uniquement parce qu'il se trouve dans le répertoire pages par exemple. Mais je ne sais pas s'il existe un précédent pour cette approche, et cela pourrait prêter à confusion.

Je trouve que je suis souvent tenté de créer une classe Singleton lorsqu'un simple module qui implémente une interface est tout ce dont j'ai besoin. Des conseils sur la meilleure façon de résoudre ce problème?

@RyanCavanaugh @DanielRosenwasser
Je veux travailler sur cette question. Pouvez-vous s'il vous plaît me donner quelques conseils pour la solution ou où chercher?

En y réfléchissant dans une perspective 2020, je me demande si au lieu de export implements Showable nous réutilisons type et autorisons export comme identifiant? Aujourd'hui, c'est une syntaxe invalide , il est donc peu probable que vous utilisiez la base de code existante de quiconque.

Ensuite, nous obtenons la syntaxe d'importation:

// Can re-use the import syntax
type export = import("webpack").Config

Les déclarations sont alors faciles à rédiger:

// Can use normal literals
type export = { test: () => string, description: string }

// Generics are easy
type export = (props: any) => React.SFC<MyCustomModule>

Cela vaut également la peine de penser à ce que devrait être l'équivalent JSDoc, peut-être:

/** <strong i="17">@typedef</strong> {import ("webpack").Config} export */

Il y a quelques notes dans ^ - une chose intéressante qui est ressortie de la réunion était l'idée que nous pourrions construire un outil plus générique dont c'est un cas d'utilisation, plutôt que la seule chose qu'il fait.

Par exemple, si nous avions un opérateur d'assertion de type pour la compatibilité de type, cela pourrait être utilisé à la fois pour les exportations de module et pour vérifier de manière générique que les types correspondent à ce que vous voulez. Par exemple:

type assert is import("webpack").Config

const path = require('path');

export default {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

Là où l'absence de cible signifie l'appliquer au niveau le plus élevé. Cela peut être utilisé pour fournir une saisie contextuelle (par exemple, vous obtiendrez une saisie semi-automatique dans le export default { en|

Mais peut également être utile pour valider vos propres types:

import {someFunction} from "./example"

type assert ReturnType<typeof someFunction> is string

Cela vaut également la peine de penser à ce que devrait être l'équivalent JSDoc, peut-être:
js /** <strong i="7">@typedef</strong> {import ("webpack").Config} export */

Je pense que @module serait l'équivalent de JSDoc. Le haut du fichier doit avoir:
js /** <strong i="12">@module</strong> {import("webpack").Config} moduleName */

Voir: https://jsdoc.app/tags-module.html

Storybook v6 a changé pour une approche basée sur des modules structurés qu'ils ont appelés Component Story Format . Tous les modules .stories.js/ts dans une base de code doivent inclure une exportation par défaut de type Meta .

Le fait de n'avoir aucun moyen d'exprimer cette attente de manière globale, combiné à la carence existante dans la saisie des exportations par défaut, rend l'utilisation de Storybook v6 avec TypeScript une expérience beaucoup moins fluide qu'elle ne pourrait l'être.

Pour ajouter aux points de default qui est d'un certain type qui réplique un module entraînera des problèmes de tremblement des arbres.

Webpack n'a aucun problème à secouer l'arbre import * as Foo . Mais lorsque vous essayez de faire de même avec un export default const = {} ou un export default class ModuleName { avec tous les membres statiques, les importations inutilisées ne sont pas supprimées.

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

Questions connexes

kyasbal-1994 picture kyasbal-1994  ·  3Commentaires

MartynasZilinskas picture MartynasZilinskas  ·  3Commentaires

fwanicka picture fwanicka  ·  3Commentaires

wmaurer picture wmaurer  ·  3Commentaires

siddjain picture siddjain  ·  3Commentaires