Knockout: カスタムバインディング-initとupdateの間で状態を渡します

作成日 2016年02月10日  ·  7コメント  ·  ソース: knockout/knockout

現時点では、バインディングハンドラーのinit関数とupdate関数の間で状態を共有するためのKnockoutに組み込まれた方法はありません。 たとえば、次のようになります。

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));
   }
};

initとupdateの間にbubbleを再利用できるようにする必要があります。 bubbleを一度だけ作成して初期化したいのですが、データの更新を受け取るたびに必要になります。 initから渡されて(おそらくそれを返すことができる)更新するために、ある種の状態ベースのオブジェクトがあれば、それは本当に素晴らしいことです。

最も参考になるコメント

更新関数は計算のショートカットにすぎないため、initのクロージャーで状態をキャプチャできます。

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
        });
    }
}

これは非常に一般的なパターンであるため、これを処理するためにバインディングハンドラーファクトリを作成しました。

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;
            });
        }
    };
}

あなたはそのように使うことができます:

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
    }
});

全てのコメント7件

更新関数は計算のショートカットにすぎないため、initのクロージャーで状態をキャプチャできます。

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
        });
    }
}

これは非常に一般的なパターンであるため、これを処理するためにバインディングハンドラーファクトリを作成しました。

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;
            });
        }
    };
}

あなたはそのように使うことができます:

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ああ私はそれを認識していませんでした。 その場合、ファクトリパターンは本当に素晴らしいです(私がそれを使用するかどうか気になりますか?)。 newObjをnullに設定するために、最後のdomNodeDisposal呼び出しが何に使用されるかを尋ねることはできますか?

お気軽にご利用ください:) domNodeDisposalの呼び出しは、メモリリークについて妄想しているため、主にありますが、不要な場合があります。

@kimgronqvist OK-パラノイアで共有できてうれしいです。ありがとう!

'update'がないバインディングについて1つ説明できますか?

更新アクションはどのように呼び出されますか? 初期化するko.computed()が依存関係トラッカーにアタッチされ、valueAccessor()によって参照されるオブザーバブルが変更を通知すると、計算された関数が「読み取り」関数を呼び出しますか?

@ chrisknoll-そうです。 ko.computedは、アクセスされたオブザーバブルをサブスクライブし、変更時にそのコードを再実行します。 これにより、「更新」機能と同等の機能が得られます。

純粋に計算されたものではないことに注意してください。
私のコードでは、計算されたものが値を生成するよりも副作用のために使用されているという事実を理解するために、ko.computedをko.sideEffectsとしてエイリアスします。 これにより、IMHOが非常に明確になり、コードにko.sideEffectsとko.pureComputedがほとんど含まれていることを意味します。また、コードベースでko.computedを直接使用すると、コードの臭いが少し発生するか、古いパターンが示されます。 (pureComputedが登場する前)。

そして、計算されたものを破棄する方法がない限り、(純粋な計算されたものとは異なり)サブスクライブしたオブジェクトによって存続します->メモリリークの可能性があります!

このページは役に立ちましたか?
0 / 5 - 0 評価