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