Erzwingen Sie die Ausführung einer berechneten Eigenschaftsfunktion

80

Gegeben eine berechnete Eigenschaft

vm.checkedValueCount = ko.computed(function(){
  var observables = getCurrentValues();  //an array of ko.observable[]
  return _.filter(observables, function(v) { return v() }).length;
});

Angenommen, getCurrentValues ​​() kann verschiedene Sätze von Observablen zurückgeben, die an anderer Stelle im Code geändert wurden (und aus einer komplexeren Struktur stammen als ein ObservableArray).

Ich muss checkedValueCountjederzeit aktualisieren

  • Eine seiner Abhängigkeiten ändert sich
  • getCurrentValues ​​() gibt einen anderen Satz von Observablen zurück.

Das Problem ist, dass ko.computedder zuletzt zurückgegebene Wert scheinbar gespeichert und nur aktualisiert wird, wenn eine Abhängigkeit aktualisiert wird. Dies behandelt den ersten Fall, nicht jedoch den letzteren.

Was ich suche, ist eine Möglichkeit, checkedValueCount zur erneuten Ausführung zu zwingen. Etwas, das ich verwenden kann wie:

changeCurrentValues();
vm.checkeValueCount.recalculate();

Einfach gesagt, vorausgesetzt, ich habe

a = ko.computed(function() { return Math.random() })

Wie kann ich das a()zweimalige Aufrufen erzwingen , um unterschiedliche Werte zurückzugeben?

George Mauer
quelle
Siehe meine aktualisierte "umgeschriebene" Antwort.
Josh

Antworten:

116

Ich habe festgestellt, dass meine erste Antwort Ihren Standpunkt verfehlt hat und Ihr Problem nicht lösen wird.

Das Problem ist, dass ein berechneter Wert nur dann neu bewertet wird, wenn ein beobachtbarer Wert vorhanden ist, der ihn zur Neubewertung zwingt. Es gibt keine native Möglichkeit, einen Computer zur Neubewertung zu zwingen.

Sie können dies jedoch mit etwas Hackery umgehen, indem Sie einen beobachtbaren Dummy-Wert erstellen und dann seinen Abonnenten mitteilen, dass er sich geändert hat.

(function() {

    var vm = function() {
        var $this = this;

        $this.dummy = ko.observable();

        $this.curDate = ko.computed(function() {
            $this.dummy();
            return new Date();
        });

        $this.recalcCurDate = function() {
            $this.dummy.notifySubscribers();
        };        
    };

    ko.applyBindings(new vm());

}());​

Hier ist eine Geige, die diesen Ansatz zeigt

Josh
quelle
Ah, ich habe es versucht, aber ich muss es falsch angeschlossen haben. Das sieht hacky aber gut aus (und gut genug, dass ich ko.computed erweitern kann, damit es funktioniert
George Mauer
Wie immer ist das Einfachere das Beste.
Henry Rodriguez
1
Dies ist hilfreich, wenn Sie ein großes Datenarray ändern, das nicht beobachtbar ist, und nach Änderungen ein Teil der Daten angezeigt werden muss.
Marek Bar
1
Ihre Verwendung von notifySubscribers () ist gut. Besser als das, was ich getan habe, um eine Zufallszahl zu
erstellen
9

Es gibt eine Methode , um die Neuberechnung aller Observablen abhängig von Ihrer zu erzwingen:

getCurrentValues.valueHasMutated()
Rustam
quelle
9
Berechnete haben diese Methode nicht
George Mauer
getCurrentValues(); //an array of ko.observable[]
Rustam
Oh ich verstehe. Sie sagen, finden Sie eine Observable, die eine Abhängigkeit von der berechneten ist, und rufen Sie ihre valueHasMutatedMethode auf. Das unterscheidet sich nicht wirklich grundlegend von Joshs Antwort oben, oder? Tatsächlich zwingt es Sie zu wissen, worauf verwiesen wird, und zu wissen, dass diese Referenzen beobachtbar (und nicht berechnet) sind.
George Mauer
2
Es ist dennoch gut, diese Funktion zu erwähnen, da sie eine Option für Leser bleibt, obwohl möglicherweise jemand die akzeptierte Antwort aktualisieren könnte, um Fälle zu notieren, in denen diese Funktion mit notifySubscribers () ausgetauscht werden kann und wenn es eine Strafe oder einen Vorteil gibt.
Shaun Wilson
4

Diese Antwort ist konzeptionell dieselbe wie die von @josh, wird jedoch als allgemeinerer Wrapper dargestellt. Hinweis: Diese Version ist für eine 'beschreibbare' Berechnung.

Ich verwende Typescript, daher habe ich zuerst die ts.d-Definition eingefügt. Ignorieren Sie diesen ersten Teil, wenn er für Sie nicht relevant ist.

interface KnockoutStatic
{
    notifyingWritableComputed<T>(options: KnockoutComputedDefine<T>, context ?: any): KnockoutComputed<T>;
}

Benachrichtigung-beschreibbar-berechnet

Ein Wrapper für eine beschreibbare Datei observable, der immer dazu führt, dass Abonnenten benachrichtigt werden - auch wenn infolge des writeAufrufs keine Observables aktualisiert wurden

Ersetzen Sie einfach function<T> (options: KnockoutComputedDefine<T>, context)mit , function(options, context)wenn Sie nicht Typoskript verwenden.

ko.notifyingWritableComputed = function<T> (options: KnockoutComputedDefine<T>, context)
{
    var _notifyTrigger = ko.observable(0);
    var originalRead = options.read;
    var originalWrite = options.write;

    // intercept 'read' function provided in options
    options.read = () =>
    {
        // read the dummy observable, which if updated will 
        // force subscribers to receive the new value
        _notifyTrigger();   
        return originalRead();
    };

    // intercept 'write' function
    options.write = (v) =>
    {
        // run logic provided by user
        originalWrite(v);

        // force reevaluation of the notifyingWritableComputed
        // after we have called the original write logic
        _notifyTrigger(_notifyTrigger() + 1);
    };

    // just create computed as normal with all the standard parameters
    return ko.computed(options, context);
}

Der Hauptanwendungsfall hierfür ist, wenn Sie etwas aktualisieren, das sonst keine Änderung in einem Observable auslösen würde, das von der readFunktion "besucht" wird .

Zum Beispiel verwende ich LocalStorage, um einige Werte festzulegen, aber es gibt keine Änderung an beobachtbaren Werten, um eine Neubewertung auszulösen.

hasUserClickedFooButton = ko.notifyingWritableComputed(
{
    read: () => 
    {
        return LocalStorageHelper.getBoolValue('hasUserClickedFooButton');
    },
    write: (v) => 
    {
        LocalStorageHelper.setBoolValue('hasUserClickedFooButton', v);        
    }
});

Beachten Sie, dass alles , was ich brauchte , war Änderung ko.computedzu ko.notifyingWritableComputedund dann alles kümmert sich um sich selbst.

Wenn ich hasUserClickedFooButton(true)anrufe, wird die beobachtbare "Dummy" erhöht, wodurch alle Abonnenten (und ihre Abonnenten) gezwungen werden, den neuen Wert zu erhalten, wenn der Wert in LocalStorage aktualisiert wird.

(Hinweis: Sie denken vielleicht, dass der notify: 'always'Extender hier eine Option ist - aber das ist etwas anderes).


Es gibt eine zusätzliche Lösung für ein berechnetes Observable, das nur lesbar ist:

ko.forcibleComputed = function(readFunc, context, options) {
    var trigger = ko.observable().extend({notify:'always'}),
        target = ko.computed(function() {
            trigger();
            return readFunc.call(context);
        }, null, options);
    target.evaluateImmediate = function() {
        trigger.valueHasMutated();
    };
    return target;
};


myValue.evaluateImmediate();

Von @mbest Kommentar https://github.com/knockout/knockout/issues/1019 .

Simon_Weaver
quelle
Was meinst du damit, dass es keinen Wert gibt? Hat es funktioniert, bevor ich dazu gewechselt bin? Verwenden Sie verzögerte Aktualisierungen (die globale Einstellung)? Ich habe gerade festgestellt, dass möglicherweise ein Konflikt vorliegt, wenn Sie erwarten, dass Sie das Observable aktualisieren und den Wert dann sofort verwenden können. Wenn ja, fügen Sie meiner Methode ko.tasks.runEarly () hinzu.
Simon_Weaver
1

Angenommen, getCurrentValues ​​() kann verschiedene Sätze von Observablen zurückgeben, die an anderer Stelle im Code geändert werden

Ich gehe davon aus, dass getCurrentValues ​​() eine Funktion ist. Wenn Sie es zu einem berechneten Wert machen könnten, würde Ihr checkedValueCount auf magische Weise funktionieren.

Können Sie getCurrentValues ​​als berechnete Funktion anstelle einer Funktion festlegen?

Judah Gabriel Himango
quelle
Ich vereinfache mein tatsächliches Szenario sehr, aber sicher, sagen wir mal, es kann sein (was nicht der Fall wäre, wenn es zum Beispiel erforderlich wäre, Parameter zu übernehmen) - ich sehe nicht, wie das einen Unterschied machen würde. Innerhalb von getCurrentValues ​​() schneide und schneide ich andere Daten, um zu bestimmen, welche Knoten einer Baumansicht zurückgegeben werden müssen. Das Problem ist, dass die Funktion verschiedene beobachtbare Werte zurückgeben kann und ich die berechnete brauche, um sie zu erfassen. Grundsätzlich genau das, worüber die Dokumente mit dynamischen Abhängigkeiten sprechen, aber mit der Komplikation, dass sie im laufenden Betrieb hinzugefügt werden
George Mauer
Es würde einen Unterschied machen, wenn das "Schneiden und Würfeln" auf Observablen basiert. Angenommen, Ihr Slicing und Dicing sind die Knoten mit .IsChecked () == true. Sie hätten dann eine Berechnung mit dem Namen .currentValues ​​(), die alle Knoten auswertet und die mit .IsChecked () zurückgibt. Kann das Schneiden und Würfeln auf beobachtbaren Eigenschaften erfolgen?
Judah Gabriel Himango
Ich bin mir wirklich nicht sicher, was du sagst, Judah, jede einzelne Anrufung kann in meinem Fall kein Ko sein - aber was noch wichtiger ist, ich sehe nicht, wie das möglicherweise von Bedeutung sein könnte, Knockout macht keine Reflexion darüber Welche Schließungen Sie angerufen haben (ich habe nicht überprüft, aber wenn Javascript nicht vollständig ins Lisp-Land gelangt ist, ist dies unmöglich). Alles, was der Computer tun kann, ist bestenfalls alle Observablen zu verfolgen, die aufgerufen werden.
George Mauer
-1

Da es keine einfache Möglichkeit gibt, die Aktualisierung eines berechneten Datums zu erzwingen, habe ich ein Observable mit dem Namen toForceComputedUpdate erstellt und es innerhalb der berechneten Funktion aufgerufen, damit das berechnete Update das Update abhört. Um das Update zu erzwingen, nenne ich es so toForceComputedUpdate (Math .zufällig)

yoel neuman
quelle