Búsqueda de sitios web

¿Puedes extender dinámicamente un tipo en TypeScript?


En JavaScript puedes hacer esto:

function Test() {
  this.id = 1;
};

Test.prototype.customize = function(key, callback) {
  this[key] = callback;
};

var callback = function() { alert(this.id); };
var x = new Test();
x.customize('testing', callback);
x.testing();

¿Puedes hacer algo similar en TypeScript?

Particularmente me interesa tener una clase como:

class Socket {
  ...
}

class Sockets {

  public addChannel(name:string):void {
    this[name] = new Socket(); 
  }

  ...
}

data = new Sockets();
data.addChannel('video');
data.addChannel('audio');

...

var audio = data.audio.read();
var video = data.video.read();
etc.

El compilador se queja de que no hay un miembro 'audio' o 'video' en 'Sockets', y no compilará. ¿Hay alguna manera de solucionar eso sin tener que definir manualmente las propiedades en la clase contenedora?

Sé que de alguna manera elude las reglas de tipado estático, pero a veces encuentro útil para la comodidad de la API tener algo así.

editar: Mira el ejemplo de respuesta que publiqué abajo; algo así funciona. Todavía aceptaré cualquier respuesta ingeniosa que me permita de alguna manera compilar algo que haga algo útil sobre el objeto base en sí mismo.

Respuesta #1

Actualización: Parece que buscas un comportamiento realmente dinámico en tiempo de ejecución... es decir, no sabes si será video, audio u otros valores. En este caso, donde tienes un nombre de canal dinámico de alguna fuente, tendrías que usar la sintaxis [] - pero todavía hay algunos

var channel = 'video;'

var data = new Sockets();
data.addChannel(channel);

// Totally dynamic / unchecked
var media = data[channel].read();

// Or

interface IChannel {
    read(): { /* You could type this */ };
}

// Dynamic call, but read method and media variable typed and checked
var media = (<IChannel>data[channel]).read();

Respuesta anterior... útil para cualquiera que lea esta pregunta y no busque un comportamiento completamente dinámico (¡pero bastante inútil para Doug, lo siento!)

Si quieres extender una clase existente, puedes usar herencia:

class ClassOne {
    addOne(input: number) {
        return input + 1;
    }
}

// ...

class ClassTwo extends ClassOne {
    addTwo(input: number) {
        return input + 2;
    }
}

var classTwo = new ClassTwo();
var result = classTwo.addTwo(3); // 5

Si quieres hacer esto de manera realmente dinámica, por ejemplo, si solo quieres agregar algo a una instancia, también puedes hacerlo, pero la herencia te da mucho más por tu dinero.

class ClassOne {
    addOne(input: number) {
        return input + 1;
    }
}

// ...

var classOne = new ClassOne();

classOne['addTwo'] = function (input: number) {
    return input + 2;
};

var result = (<any>classOne).addTwo(3); // 5

//or (nasty 'repeat the magic string version')

result = classOne['addTwo'](3);

Si estás decidido a usar la ruta dinámica, un patrón común en TypeScript es representar la estructura con una interfaz, no con una clase. Las interfaces (y los módulos, y los enumerados) están abiertas, por lo que pueden extenderse a lo largo de múltiples bloques. Necesitarías asegurarte de que tu interfaz y tu implementación se extendieran por igual. Esta es la técnica que usas para extender objetos incorporados.

// NodeList is already declared in lib.d.ts - we are extending it
interface NodeList {
    sayNodeList(): void;
}

NodeList.prototype.sayNodeList = function () {
    alert('I say node, you say list... NODE');
}

Esto le da autocompletado completo y verificación de tipos.

Respuesta #2

No estás extendiendo el tipo en tu ejemplo... solo estás agregando dinámicamente una propiedad al objeto. En TypeScript no puedes agregar dinámicamente una propiedad a un objeto que contiene propiedades con tipos diferentes, a menos que hagas un casting a any (lo cual, en mi opinión, debería evitarse).

Sugeriría añadir una propiedad a Sockets que esté limitada a tener propiedades que sean del tipo Socket de la siguiente manera:

class Sockets {
    channels: { [index: string]: Socket; } = {};
    public addChannel(name:string):void {
        this.channels[name] = new Socket(); 
    }
}

var data = new Sockets();
data.addChannel('audio');

// audio will be the return type of read... so you don't lose type
var audio = data.channels['audio'].read();

Al hacer esto no tienes que recordar convertir el objeto a Socket y obtienes todas las ventajas de la seguridad de tipos.

Artículos relacionados