Knockout: Benutzerdefinierte Bindungen - Zustandsübergabe zwischen Init und Update

Erstellt am 10. Feb. 2016  ·  7Kommentare  ·  Quelle: knockout/knockout

Im Moment gibt es in Knockout keine Möglichkeit, den Status zwischen der init -Funktion und der update -Funktion eines Binding-Handlers zu teilen. Zum Beispiel im Folgenden:

ko.bindingHandlers["myViz"] = {
   init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      var bubble = d3.layout.pack().sort(null).padding(1);
   },
   update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      var data = ko.unwrap(valueAccessor()).data;

      var nodes = d3.select(element)
                             .selectAll(".node")
                             .data(bubble.nodes(data));
   }
};

Ich muss in der Lage sein, bubble zwischen Init und Update wiederzuverwenden. Ich möchte bubble nur einmal erstellen und initialisieren, aber ich brauche es jedes Mal, wenn ich ein Datenupdate erhalte. Es wäre wirklich schön, wenn es eine Art zustandsbasiertes Objekt gäbe, das von init (möglicherweise könnte es es zurückgeben) an update übergeben wurde.

Hilfreichster Kommentar

Die Update-Funktion ist nur eine Abkürzung für eine berechnete, sodass Sie den Zustand in der Schließung von init erfassen können.

ko.bindHandlers.myViz = {
    init: function(element, valueAccessor) {
        var bubble =  d3.layout.pack().sort(null).padding(1);
        ko.computed({
            read: function () {
                var data = ko.unwrap(valueAccessor()).data;

                var nodes = d3.select(element)
                    .selectAll(".node")
                    .data(bubble.nodes(data));
            },
            disposeWhenNodeIsRemoved: element
        });
    }
}

Dies ist ein sehr häufiges Muster, daher habe ich eine Binding Handler Factory erstellt, um dies zu handhaben.

function createBindingHandler (obj) {
    return {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            var newObj = Object.create(obj);

            if (obj.dispose) {
                ko.utils.domNodeDisposal.addDisposeCallback(element, obj.dispose.bind(newObj, element, valueAccessor, allBindings, viewModel, bindingContext));
            }

            if (obj.init) {
                obj.init.call(newObj, element, valueAccessor, allBindings, viewModel, bindingContext);
            }

            if (obj.update) {
                ko.computed({
                    read: obj.update.bind(newObj, element, valueAccessor, allBindings, viewModel, bindingContext),
                    disposeWhenNodeIsRemoved: element
                });
            }

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                newObj = null;
            });
        }
    };
}

Was Sie so verwenden können:

ko.bindingHandlers.myViz = createBindingHandler({
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        this.bubble = d3.layout.pack().sort(null).padding(1);
    },

    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var data = ko.unwrap(valueAccessor()).data;

        var nodes = d3.select(element)
            .selectAll(".node")
            .data(this.bubble.nodes(data));
    },

    dispose: function () {
        // cleanup logic
    }
});

Alle 7 Kommentare

Die Update-Funktion ist nur eine Abkürzung für eine berechnete, sodass Sie den Zustand in der Schließung von init erfassen können.

ko.bindHandlers.myViz = {
    init: function(element, valueAccessor) {
        var bubble =  d3.layout.pack().sort(null).padding(1);
        ko.computed({
            read: function () {
                var data = ko.unwrap(valueAccessor()).data;

                var nodes = d3.select(element)
                    .selectAll(".node")
                    .data(bubble.nodes(data));
            },
            disposeWhenNodeIsRemoved: element
        });
    }
}

Dies ist ein sehr häufiges Muster, daher habe ich eine Binding Handler Factory erstellt, um dies zu handhaben.

function createBindingHandler (obj) {
    return {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            var newObj = Object.create(obj);

            if (obj.dispose) {
                ko.utils.domNodeDisposal.addDisposeCallback(element, obj.dispose.bind(newObj, element, valueAccessor, allBindings, viewModel, bindingContext));
            }

            if (obj.init) {
                obj.init.call(newObj, element, valueAccessor, allBindings, viewModel, bindingContext);
            }

            if (obj.update) {
                ko.computed({
                    read: obj.update.bind(newObj, element, valueAccessor, allBindings, viewModel, bindingContext),
                    disposeWhenNodeIsRemoved: element
                });
            }

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                newObj = null;
            });
        }
    };
}

Was Sie so verwenden können:

ko.bindingHandlers.myViz = createBindingHandler({
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        this.bubble = d3.layout.pack().sort(null).padding(1);
    },

    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var data = ko.unwrap(valueAccessor()).data;

        var nodes = d3.select(element)
            .selectAll(".node")
            .data(this.bubble.nodes(data));
    },

    dispose: function () {
        // cleanup logic
    }
});

@kimgronqvist ah das hatte ich nicht bemerkt. Ihr Fabrikmuster ist in diesem Fall wirklich schön (darf ich es verwenden?). Darf ich fragen, was der letzte Aufruf domNodeDisposal verwendet wird, um newObj auf null zu setzen?

Fühlen Sie sich frei, es zu benutzen :) Der domNodeDisposal -Aufruf ist hauptsächlich da, weil ich wegen Speicherlecks paranoid bin, aber er könnte überflüssig sein.

@kimgronqvist OK - Ich teile gerne Paranoia und danke!

Können Sie eine Sache über Ihre Bindung erklären, die nicht über das 'Update' verfügt:

Wie werden die Aktualisierungsaktionen aufgerufen? Ist es so, dass das ko.computed(), das Sie initialisieren, an den Abhängigkeitstracker angehängt wird, sodass, wenn das von valueAccessor() referenzierte Observable eine Änderung signalisiert, das berechnete die 'read'-Funktion aufruft?

@chrisknoll - das ist richtig. ko.computed abonniert Observables, auf die zugegriffen wird, und führt seinen Code bei Änderungen erneut aus. Dies gibt Ihnen das Äquivalent einer "Aktualisierungs"-Funktion.

Beachten Sie, dass es _kein_ rein berechnetes ist.
Um in meinem Code deutlich zu machen, dass eine Berechnung mehr für ihre Nebeneffekte als zur Generierung eines Werts verwendet wird, aliasiere ich ko.computed als ko.sideEffects. Dies macht es IMHO wirklich klar und bedeutet, dass ich ziemlich viel ko.sideEffects und ko.pureComputed in meinem Code habe und die Verwendung von ko.computed direkt in meiner Codebasis ist ein bisschen wie ein Codegeruch oder ein Hinweis auf ein älteres Muster (bevor pureComputed kam).

Und wenn Sie keine Möglichkeit haben, den Computer zu entsorgen, wird er von den Objekten, die er abonniert hat, am Leben erhalten (im Gegensatz zu einem reinen Computer) -> möglicher Speicherverlust!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen