Typescript: Permitir que un módulo implemente una interfaz

Creado en 10 ago. 2014  ·  34Comentarios  ·  Fuente: microsoft/TypeScript

Sería útil cuando un módulo puede implementar una interfaz usando la palabra clave implements . Sintaxis: module MyModule implements MyInterface { ... } .

Ejemplo:

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

Comentario más útil

Un caso de uso sería para marcos y herramientas que escanean un directorio en busca de módulos al iniciar la aplicación, esperando que todos esos módulos exporten una determinada forma.

Por ejemplo, Next.js escanea ./pages/**/*.{ts,tsx} para los módulos de su página, generando rutas basadas en sus nombres de archivo. Depende de usted asegurarse de que cada módulo exporte las cosas correctas (un NextPage como exportación predeterminada y un PageConfig exportación opcional llamado 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 }
}

Sería bueno si en su lugar pudiera declarar la forma de exportación de todo el módulo en una línea cerca de la parte superior, como implements NextPageModule<Props> .

Otro pensamiento: sería interesante si hubiera alguna forma de especificar en una configuración de TypeScript que todos los archivos que coincidan con un patrón determinado (como ./pages/**/*.{ts,tsx} ) deben implementar una determinada forma de exportación, por lo que un módulo podría tener su tipo de exportación- comprobado simplemente porque se encuentra dentro del directorio pages , por ejemplo. Pero no estoy seguro de si hay algún precedente para este enfoque, y podría resultar confuso.

Todos 34 comentarios

¿Cómo funcionaría esto con módulos externos? Es probable que una vez que las personas puedan usarlo para uso interno, también quieran usarlo con externo.

Buena pregunta. No sé qué sintaxis sería la mejor, pero aquí hay algunas sugerencias:

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

Solo debería permitirse en módulos externos que no usen una asignación de exportación, ya que si usa una asignación de exportación, lo que exporta ya puede tener un implements en otro lugar.

Aprobado. Preferimos la sintaxis

export implements Showable;

y acordó que esto es innecesario para las asignaciones de archivos export = .

Algunas preguntas más:

  • Dado que permitimos que los módulos tengan tipos en los sitios de declaración, ¿no deberíamos permitirles tener tipos también en los sitios de uso? p.ej:
declare module "Module" implements Interface { }

import i : Interface = require("Module");
  • ¿Qué se hace con las declaraciones fusionadas? ¿Debería aplicar la interfaz en el agregado de todas las declaraciones? y ¿qué pasa si no coinciden en visibilidad?
    p.ej:
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;

Debe permitirse en módulos externos ambientales. Para estos módulos, en mi opinión, deberían permitirse dos sintaxis:

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.  
}

¿O debería Bar estar en el alcance de una cláusula de implementos antes de la apertura { ?

Agregar información de tipo a una declaración de importación no es realmente útil en mi opinión, ya que puede agregar la información de tipo al módulo en sí.

Y para las declaraciones fusionadas, diría que el bloque de módulo que contiene la cláusula implements debería implementar la interfaz. Eso también evita problemas de visibilidad.

¿Cómo se relacionaría esto con el número 2159? ¿Un espacio de nombres implementa una interfaz?

@jbondc Si tuviéramos esto, también se aplicaría a los espacios de nombres. Debe pensar en los módulos internos y los espacios de nombres como isomórficos.

¿Está seguro de que desea seguir una ruta de implementación donde los "espacios de nombres" puedan implementar interfaces?

Oh, vaya, esto ha sido aprobado por bastante tiempo. @RyanCavanaugh, @DanielRosenwasser, @mhegazy a menos que tenga ningún tipo de dudas o retoques, probablemente implementar esta soonish.

Retiro mi escepticismo anterior, de hecho salí por las nuevas posibilidades estructurales que traería.

De acuerdo con eso, considere aplicar la interfaz del agregado de la interfaz en lugar de solo el bloque que declara la implementación: la naturaleza de los espacios de nombres / módulos debe extenderse y contener muchos componentes no triviales. Me gustaría poder usar esto, pero ciertamente no quiero definir todo mi espacio de nombres / módulo en el mismo archivo. ¿Por qué no usar una clase en ese caso?

@ Elephant-Vessel No estoy seguro de si estamos hablando de módulos, espacios de nombres, paquetes, características o ...

@aluanhaddad ¿Qué quieres decir?

Quiero decir que en el momento en que comenzó esta discusión, el módulo no significaba lo que significa hoy. Ahora usamos el término espacio de nombres para referirnos a lo que se describe en el OP como módulo, mientras que módulo ha adquirido un significado más preciso e incompatible. Entonces, cuando habla de varios archivos que participan en esta implementación, ¿se refiere a espacios de nombres o módulos?

Me refiero a los espacios de nombres. Supongo que solo quería ajustarme a la historia de este hilo, lo siento por no soltarme :) O cuando lo pienso, tal vez tenía el término genérico 'módulo' en mi cabeza, que describe una unidad de nivel superior que consiste en un conjunto de subcomponentes, ensamblados para proporcionar cierta funcionalidad de alto nivel en un sistema. Pero estoy bien con solo ir con 'espacios de nombres'.

Así que quiero poder describir y poner restricciones y expectativas en [_módulos genéricos_] que pueden contener otros [_módulos genéricos_] o clases, aprovechando los espacios de

Mi esperanza es que seamos capaces de expresar mejor las expectativas estructurales de mayor nivel en un sistema. Las clases no escalan bien, están bien como componentes atómicos en un sistema, pero no creo que la estructura organizacional de nivel superior en un sistema sea buena para expresarse con clases, ya que están diseñadas para ser instanciadas y heredadas y cosas así. . Es demasiado hinchado.

Apreciaría una forma simple y limpia de describir la estructura de orden superior del sistema, sin problemas. Preferiblemente con el único problema son las restricciones de visibilidad direccional opcionales. Como hacer que sea imposible hacer referencia a _MySystem.ClientApplication_ de _MySystem.Infrastructure_ pero está bien al revés. Entonces empezaríamos a ir a algún lugar emocionante.

@ Elephant-Vessel gracias por aclarar. Estoy de acuerdo en que esto sería extremadamente valioso y que los tipos de clases no son el enfoque correcto aquí. Creo que ha dado en el clavo cuando se habla de creación de instancias porque los espacios de nombres representan cosas que son conceptualmente únicas a nivel de biblioteca. Aunque esto no se puede hacer cumplir, sería útil conceptualmente tener algo que no _implique_ múltiples instancias.

Estoy de acuerdo con @ Elephant-Vessel. Si bien es fácil confundir TypeScript con otro Java, donde todas las restricciones se expresan con una estructura de clase única, TS tiene un concepto de "Forma" mucho más amplio que es muy poderoso y elimina el contortonismo semántico. Desafortunadamente, la incapacidad de poner restricciones en el módulo tiende a obligar a los desarrolladores a volver a un patrón de clase para cosas que se expresarían mucho mejor como módulo.

Por ejemplo, para las pruebas unitarias, sería muy útil poder expresar alguna "forma" (es decir, restricciones) en los módulos para que podamos proporcionar una implementación alternativa para un contexto de ejecución particular. Ahora, parece que la única forma de hacerlo de una manera estructurada / verificada es volver a la DI basada en clases (como la Spring) y convertir todo en una clase (y por lo tanto instanciable).

De todos modos, estoy parafraseando a @ Elephant-Vessel, pero si tengo un solo deseo de TS, sería este.

¿Alguna noticia sobre este pájaro? Yo también tengo este problema

así que, uhh, ¿no sería un simple caso de:

export {} as IFooBar;

¿Qué pasa con esa sintaxis? Supongo que la sintaxis ya ha sido aprobada, tal vez como

export implements IFooBar

de todos modos deseando que llegue

¿Ya se ha matriculado / aterrizado? esta va a ser una característica interesante

¿Cómo podemos progresar en esto? Es increíblemente poderoso. ¡Feliz de ayudar!

¿Algún trabajo sobre este birb? Una pregunta que tengo por el momento es cómo puedo declarar una interfaz para la exportación predeterminada. Por ejemplo:

export default {}

Supongo que puedo hacer:

const x: MyInterface = {}
export default x;

eso funcionaría para la mayoría de los archivos TS, aunque el problema es que si está codificando para JS primero y planea hacer la transición a TS más tarde, entonces esto no funciona tan bien.

Otra cosa en la que estaba pensando, ¿qué pasa con los espacios de nombres que implementan? Algo como:

export namespace Foo implements Bar {

}

Supongo que Bar sería un espacio de nombres _abstract_ lol idk

He visto esta pregunta surgir tantas veces, y creo que todos solo estamos buscando una cosa:
Admite miembros estáticos en una interfaz.
Si eso sucediera, podría usar una clase con miembros estáticos y una interfaz, que es casi lo mismo que está tratando de hacer aquí, ¿verdad?

De cualquier manera, es muy necesario agregar soporte estático a las interfaces O agregar soporte de interfaz para módulos.

@shiapetel nah no es así.

Podemos hacer esto:

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

pero eso no es lo que estamos buscando. buscamos específicamente:

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

pero actualmente no hay ningún mecanismo para hacer cumplir el módulo para exportar foo y bar. Y, de hecho, tampoco existe ningún mecanismo para exigir que el módulo exporte el valor predeterminado correcto.

Si las interfaces admitían miembros estáticos, podría usar una clase con foo / bar estático que heredara de:
Interfaz ILoveFooBar {
foo estático
barra estática
}

¿Correcto?
Eso es lo que quise decir, creo que ayudaría en tu situación, sé que definitivamente ayudaría en la mía.

Los miembros estáticos de interfaces de

¿Este problema solo está esperando a que alguien intente implementarlo?

Un caso de uso sería para marcos y herramientas que escanean un directorio en busca de módulos al iniciar la aplicación, esperando que todos esos módulos exporten una determinada forma.

Por ejemplo, Next.js escanea ./pages/**/*.{ts,tsx} para los módulos de su página, generando rutas basadas en sus nombres de archivo. Depende de usted asegurarse de que cada módulo exporte las cosas correctas (un NextPage como exportación predeterminada y un PageConfig exportación opcional llamado 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 }
}

Sería bueno si en su lugar pudiera declarar la forma de exportación de todo el módulo en una línea cerca de la parte superior, como implements NextPageModule<Props> .

Otro pensamiento: sería interesante si hubiera alguna forma de especificar en una configuración de TypeScript que todos los archivos que coincidan con un patrón determinado (como ./pages/**/*.{ts,tsx} ) deben implementar una determinada forma de exportación, por lo que un módulo podría tener su tipo de exportación- comprobado simplemente porque se encuentra dentro del directorio pages , por ejemplo. Pero no estoy seguro de si hay algún precedente para este enfoque, y podría resultar confuso.

A menudo me siento tentado a crear una clase Singleton cuando todo lo que necesito es un módulo simple que implemente una interfaz. ¿Algún consejo sobre la mejor manera de abordar esto?

@RyanCavanaugh @DanielRosenwasser
Quiero trabajar en este tema. ¿Puede darme algunos consejos para la solución o dónde buscar?

Pensando en esto desde una perspectiva de 2020, me pregunto si en lugar de export implements Showable reutilizamos type y permitimos export como identificador. Hoy en día, esa sintaxis no es

Luego obtenemos la sintaxis de importación:

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

Las declaraciones son fáciles de escribir:

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

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

También vale la pena pensar cuál debería ser el equivalente de JSDoc, tal vez:

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

Hay algunas notas en ^ - una cosa interesante que surgió de la reunión fue la idea de que podríamos construir una herramienta más genérica de la cual este es un caso de uso, en lugar de lo único que hace.

Por ejemplo, si tuviéramos un operador de aserción de tipos para la compatibilidad de tipos, entonces eso podría usarse tanto para las exportaciones de módulos como, de manera genérica, para verificar que los tipos coinciden como lo desea. Por ejemplo:

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'
  }
};

Donde la falta de un objetivo significa aplicarlo en el ámbito de nivel superior. Esto se puede usar para proporcionar escritura contextual (por ejemplo, obtendría autocompletar en el export default { en|

Pero también puede ser útil para validar sus propios tipos:

import {someFunction} from "./example"

type assert ReturnType<typeof someFunction> is string

También vale la pena pensar cuál debería ser el equivalente de JSDoc, tal vez:
js /** <strong i="7">@typedef</strong> {import ("webpack").Config} export */

Creo que @module sería el equivalente de JSDoc. La parte superior del archivo debe tener:
js /** <strong i="12">@module</strong> {import("webpack").Config} moduleName */

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

Storybook v6 ha cambiado a un enfoque basado en módulos estructurados que llamaron Component Story Format . Se espera que todos los módulos .stories.js/ts en una base de código incluyan una exportación predeterminada con el tipo Meta .

No tener forma de expresar esta expectativa de una manera global, combinado con la deficiencia existente en la escritura de exportaciones predeterminadas, hace que usar Storybook v6 con TypeScript sea una experiencia mucho menos fluida de lo que podría ser.

Para agregar a los puntos de @jonrimmer , exportar un default que sea de cierto type que replica un module dará lugar a problemas con la vibración de árboles.

Webpack no tiene problemas para sacudir los árboles import * as Foo . Pero cuando intenta hacer lo mismo con export default const = {} o export default class ModuleName { con todos los miembros estáticos, las importaciones no utilizadas no se eliminan.

¿Fue útil esta página
0 / 5 - 0 calificaciones