knockout.js - (TypeScript) Utöka KnockoutObservableArray
Jag vill skapa en TypeScript-klass som är löst MyKOArray<T> extends KnockoutObservableArray<T>. Är detta ens möjligt? Om så är fallet, finns det ett kompakt sätt att göra det på?
- Jag läste Knockouts avsnitt om extenders, men det verkar inte tillämpligt eftersom jag försöker (bland annat) lägga till ett
state-attribut -- inte påverka den befintliga subscribable. Jag skulle kunna lista de saker jag har försökt och de sätt de har gått fel på (t.ex. TS2507, TS2322, etc.) men jag tvivlar på att de skulle vara informativa. Som en grundnivå tror jag att några av de väsentliga detaljerna är:
KnockoutObservableArrayär ett gränssnitt så jag behöver verkligen "utöka"ko.observableArray("fabriks"-funktionen).
- Signaturen för
ko.observableArrayär inte korrekt (dvs. ingennewoch den hanterar det initiala värdet i funktionen istället för enconstructor-metod). - Jag skulle kunna
return ko.observableArray(initial)från en konstruktor, men har inte lyckats få prototypkedjan rätt (t.ex. metoder är inte tillgängliga). - Jag skulle kunna skriva över
@@createi JS för attreturn ko.observableArray(initial), men jag har haft problem med att hitta ett JS-exempel (för att inte tala om en giltig TS-implementering).
ko.utils.setPrototypeOfOrExtend) antyder att denna slutsats kan vara föråldrad.===
REDIGERA: Varför?
Jag försöker koppla bort mitt system med följande mönster (dokumenterar endast det "intressanta fallet"):
- ViewModel gör en begäran till en datatjänst (indirekt via en fabrikmetod på en klass). Datatjänsten returnerar en instans omedelbart (
MyClassellerKnockoutObservableArray<MyClass>för en Singleton respektive Collection). - Asynkront laddar/populerar tjänsten (och hanterar realtidsuppdateringar för) den returnerade instansen. Detta kräver en prenumeration på en WebSocket för händelser och REST-anrop för att ladda data (inklusive möjligheten till försök igen).
- Tjänsten förmedlar sin framsteg till instansen genom att anropa metoder på instansen ("callback" och "errback" i mitt mönster).
- Dessa metoder uppdaterar ett
state-attribut (d.v.s. observerbart) och kan uppdatera något annat (t.ex. fylla dess värden eller fylla i ett observerbart felmeddelande). - Gränssnittet frågar
state-observablet för att kommunicera laddningsstatus (och andra aktiviteter) till användaren.
Detta fungerar utmärkt på mina enskilda instanser. state-metoden frågas (och uppdateras) flitigt för att säkerställa giltiga åtgärder.
Jag skulle vilja duplicera mönstret på mina KOArrays. På det sättet kan jag skilja mellan en tom array som ännu inte har laddats och en laddad array som råkar vara tom. Jag kan också lägga till en state och error_message till KOArray för att spegla beteendet hos mina instanser.
Svar #1
Kort sagt, det är inte möjligt på det sätt du beskrev.
Längre berättelse
Knockout är inte implementerat på ett objektorienterat sätt. Så i princip är nästan allt det exponerar funktioner eller vanliga objekt, etc. Prototyp-hanteringen används där för att förlänga funktionsinstansen som returneras av till exempel ko.observable. Så i princip är arv från någon form av Knockout-grejer inte möjligt i en OOP-sinne.
Om du behöver arv för att lägga till funktionalitet kan du lägga till dina nya metoder till ko.observableArray.fn-objektet. Då kommer denna funktion vara anropbar på alla observableArray-instanser efteråt. Ett snabbt exempel finns här.
ko.observableArray.fn.hasElements = function() {
return this().length > 0;
}
...
if (myModel.selectedItems.hasElements()) {
...
}
Om du behöver ärvningen för att begränsa vissa saker, kan du till exempel lägga till din egen fabriksfunktion så här.
ko.myRestrictedObservableArray = function(initialValue) {
if (!initialValue || !initialValue.length) {
throw new "You can create an observable array only with a non-empty initial value";
}
return ko.observableArray(initialValue);
}
Du kan till och med skriva över den ursprungliga funktionen om du vill, till exempel så här. Men detta är inte en bra praxis i de flesta fall.
var _original = ko.observableArray;
ko.observableArray = function(initialValue) {
// ... do your custom things
var array = _original.call(this, initialValue);
// ... do your custom things
return array;
}
Om du har ytterligare ett krav, vänligen beskriv det mer detaljerat i din fråga.
Svar #2
Baserat på @Zoltan:s feedback slutade jag med följande. Jag är säker på att det finns buggar och möjligheter att optimera, men det lät mig skriva allt i en Typescript-klass (med lämplig typkontroll åt båda hållen):
I ren JS (t.ex. en <script> i HTML-filen eller separat JS-fil):
function RestCollection(initialValue) {
const fn = ko.observableArray(initialValue);
// Static properties
ko.utils.extend(fn.constructor, RestCollectionMixin);
// Methods (already constructed so must attach directly, not to prototype)
const methods = Object.getOwnPropertyNames(RestCollectionMixin.prototype);
for (let i=0; i<methods.length; i++) {
if (methods[i] == "constructor") continue; // don't move over the constructor
fn[methods[i]] = RestCollectionMixin.prototype[methods[i]].bind(fn)
}
// Constructed properties
ko.utils.extend(fn, new RestCollectionMixin());
return fn;
} {}
// RestCollectionMixin needs this; don't return or it will break the constructor
function KnockoutObservableArrayClass() {}
I en TS-fil:
// abstract is not passed to JS so this prevents misuse without breaking the RestCollection constructor
abstract class RestCollectionMixin<T> extends KnockoutObservableArrayClass<T> { // extends handles parent method access
static FSM = class {
static STATE_INIT = 'initializing';
static STATE_READY = 'ready';
static STATE_SAVING = 'save_requested';
static STATE_DELETING = 'delete_requested';
static STATE_DELETED = 'deleted';
static STATE_ERROR = 'error';
};
public states() { // convenience accessor
return this.constructor.FSM
}
public state:KnockoutObservable<string> = ko.observable(this.states().STATE_INIT);
public processRestError(results) {
this.state(this.states().STATE_ERROR)
...
}
...
}
Slutligen, en definitionsfil för RestCollection och KnockoutObservableArrayClass så att TS-kontroll fungerar:
// provide KnockoutObservableArray properties and methods to the Mixin
interface KnockoutObservableArrayClass<T> extends KnockoutObservableArray<T> {
new<T>(initialValue): KnockoutObservableArray<T>
}
declare const KnockoutObservableArrayClass:KnockoutObservableArrayClass<any>;
// Define the focal class, capturing all extensions in RestCollectionMixin
interface RestCollection<T> extends RestCollectionMixin<T> {
new<T>(initialValue): RestCollection<T>
}
declare const RestCollection:RestCollection<any>;
