Можете ли вы динамически расширить тип в typescript?
В javascript вы можете сделать это:
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();
Можете ли вы сделать подобное в typescript?
В частности, меня интересует такой класс:
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.
Компилятор жалуется, что в "Сокетах" нет "аудио" или "видео" и не будет компилироваться. Есть ли способ обойти это без необходимости вручную определять свойства в классе контейнера?
Я знаю, что это некоторые побочные шаги в правилах статического ввода текста, но я считаю, что иногда полезно для удобства API иметь что-то вроде этого.
edit: см. пример ответа, который я разместил ниже; что-то подобное работает. Я по-прежнему соглашусь с любым умным ответом, который позволяет мне каким-то образом скомпилировать что-то полезное для самого базового объекта.
Ответы
Ответ 1
Обновление: похоже, что вы после действительно динамичного поведения во время выполнения... т.е. вы не знаете, что это будет video
, audio
или некоторые другие значения. В этом случае, когда у вас есть динамическое имя канала из какого-то источника, вам придется использовать синтаксис []
, но есть еще некоторые
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();
Предыдущий ответ... полезен для тех, кто читает этот вопрос, кто не после полного динамического поведения (но довольно бесполезный для Дуга, извините!)
Если вы хотите расширить существующий класс, вы можете использовать наследование:
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
Если вы хотите сделать это действительно динамически, например, вы просто хотите добавить что-то в экземпляр, вы тоже можете это сделать, но наследование дает вам гораздо больше за ваши деньги.
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);
Если вы настроены на динамический маршрут, общий шаблон в TypeScript должен представлять структуру с интерфейсом, а не классом. Интерфейсы (и модули и перечисления) открыты, поэтому их можно расширить на несколько блоков. Вам нужно будет обеспечить, чтобы ваш интерфейс и реализация были одинаково расширены. Это метод, который вы используете для расширения встроенных объектов.
// 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');
}
Это дает вам полное автоматическое завершение и проверку типов.
Ответ 2
Вы не расширяете тип в своем примере... вы просто динамически добавляете свойство к объекту. В TypeScript вы не можете динамически добавлять свойство к объекту, содержащему свойства с разными типами - если вы не добавили к any
(чего, на мой взгляд, следует избегать).
Я бы предложил добавить свойство в Sockets, которое ограничено наличием свойств типа Socket
следующим образом:
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();
Поступая таким образом, вам не нужно забывать бросать объект в Socket
, и вы получаете все радости безопасности типов.
Ответ 3
Это пока невозможно, но в настоящее время между сообществом Typescript происходит текущее и активное обсуждение.
https://github.com/Microsoft/TypeScript/issues/9
Ответ 4
// Basic type
type a = {
prop1: string
}
// Extended type
type b = a & {
prop2: number
}
// Demo instance
let myExtendedObject: b = {
prop1: 'text',
prop2: 99
}
Ответ 5
class Blah {
public channels:any = {};
public addChan(name:string):void {
this.channels[name] = new Channel();
}
}
var b = new Blah();
b.channel('hello');
b.channels.hello.read(1024, () => { ... });
По умолчанию кажется, что любой атрибут "any" time также "any", и вы не получаете никакой безопасности в нем без приведения, но если вы пишете библиотеку typescript, чтобы потреблять javascript, это не очень большая сделка.