Knockout components or templates performance gains

2020-05-02 23:52发布

问题:

I have an observable array. For every array element I generate some html form, very extended, as the observable array items are large objects with observables in turn:

var records = ko.observableArray([
  {
    p1: ko.observable("initProp1"),
    p2: ko.observable("initProp2"),
        // and so on
    pN: ko.observable("initPropN")
  },
  //...
]);

The html can be large, complex and dynamic, changing on the basis of some of the properties themselves: to achieve this I make use of ko:if bindings, which are known to be expensive computationally speaking (http://www.knockmeout.net/2012/03/knockoutjs-performance-gotcha-1ifwith.html), especially for large html conditionally rendered. Performance is starting suffering, especially on IEs.

Noted the repetitive, even if dynamic, structure, I am thinking about use templates or components intead of binding data directly in the html. I would use different templates/components for each dynamic configuration.

Generally speaking, can be possible that using components, or templates, will give a performance gain, or internally Ko does exactly as I would do without using them? And there is difference in performance between rendering templates and components?

Otherwise, I am considering generating HTML via JQuery every record, and then dynamically bind observables with ko.applyBindingsToNode() - could this provide performance gains?

I did some (reduced) tests, but I need some cross-browser generic evaluation of the problem. Tests seem to be discordant depending on which browser I use and even on my dataSet, and anyway do not reflect properly the complexity I have. Testing directly on the application would mean too much work, possibly useless, which I can't afford, so general guidelines would be precious to have at least an hint on which solution to use for real-life implementation and test.

回答1:

I made a version of your fiddle that uses components to supply the input fields. The component used is named 'my-' plus whatever the type field is (this was necessary to distinguish my components from input and select tags). I don't know how well this will perform, but it's simple enough that you should be able to do some testing and see.

ko.components.register('my-input', {
  viewModel: InputModel,
  template: {
    element: 'input-template'
  }
});
ko.components.register('my-select', {
  viewModel: InputModel,
  template: {
    element: 'select-template'
  }
});
ko.components.register('my-mu', {
  viewModel: InputModel,
  template: {
    element: 'mu-template'
  }
});

function InputModel(params) {
  return params;
}


function Model() {
  records = ko.observableArray([
    [{
      type: "input",
      id: "Date",
      size: "100px",
      value: ko.observable()
    }, {
      type: "select",
      id: "Weather",
      size: "100px",
      value: ko.observable(),
      options: [{
        optId: "w1",
        optTxt: "Cloudy"
      }, {
        optId: "w2",
        optTxt: "Sunny"
      }, {
        optId: "w3",
        optTxt: "Rainy"
      }, {
        optId: "w4",
        optTxt: "Snowy"
      }, {
        optId: "w5",
        optTxt: "Foggy"
      }]
    }, {
      type: "input",
      id: "Lat",
      size: "80px",
      value: ko.observable()
    }, {
      type: "input",
      id: "Long",
      size: "80px",
      value: ko.observable()
    }],
    [{
      type: "input",
      id: "Date",
      size: "100px",
      value: ko.observable()
    }, {
      type: "select",
      id: "Temperature",
      size: "120px",
      value: ko.observable(),
      options: [{
        optId: "t0",
        optTxt: "<-10"
      }, {
        optId: "t1",
        optTxt: "]-10 : 0]"
      }, {
        optId: "t2",
        optTxt: "]0 : 20]"
      }, {
        optId: "t3",
        optTxt: "]20 : 40]"
      }, {
        optId: "t4",
        optTxt: ">40"
      }]
    }, {
      type: "select",
      id: "Wind",
      size: "70px",
      value: ko.observable(),
      options: [{
        optId: "wind1",
        optTxt: "Strong"
      }, {
        optId: "wind2",
        optTxt: "Weak"
      }]
    }],
    [{
      type: "input",
      id: "Date",
      size: "100px",
      value: ko.observable()
    }, {
      type: "select",
      id: "Weather",
      size: "100px",
      value: ko.observable(),
      options: [{
        optId: "w1",
        optTxt: "Cloudy"
      }, {
        optId: "w2",
        optTxt: "Sunny"
      }, {
        optId: "w3",
        optTxt: "Rainy"
      }, {
        optId: "w4",
        optTxt: "Snowy"
      }, {
        optId: "w5",
        optTxt: "Foggy"
      }]
    }, {
      type: "input",
      id: "Lat",
      size: "80px",
      value: ko.observable()
    }, {
      type: "input",
      id: "Long",
      size: "80px",
      value: ko.observable()
    }],
    [{
        type: "input",
        id: "Date",
        size: "100px",
        value: ko.observable()
      }, {
        type: "select",
        id: "Temperature",
        size: "120px",
        value: ko.observable(),
        options: [{
          optId: "t0",
          optTxt: "<-10"
        }, {
          optId: "t1",
          optTxt: "]-10 : 0]"
        }, {
          optId: "t2",
          optTxt: "]0 : 20]"
        }, {
          optId: "t3",
          optTxt: "]20 : 40]"
        }, {
          optId: "t4",
          optTxt: ">40"
        }]
      }, {
        type: "input",
        id: "Humidity",
        size: "70px",
        value: ko.observable(),
        options: [{
          optId: "wind1",
          optTxt: "Strong"
        }, {
          optId: "wind2",
          optTxt: "Weak"
        }]
      }, {
        type: "mu",
        id: null,
        value: '%'
      }

    ]
  ]);

}
var myModel = new Model();
ko.applyBindings(myModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<template id="select-template">
  <select data-bind="style: {width: size}, value: value, options: options, optionsText: 'optTxt', optionsValue: 'optId'"></select>
</template>
<template id="input-template">
  <input type="text" data-bind="style: {width: size}, value: value" />
</template>
<template id="mu-template"> <span data-bind="text: value"></span>

</template>
<div data-bind="foreach: records">
  <div data-bind="foreach: $data">
    <label data-bind="text: id"></label>
    <!-- ko component:{name:'my-'+type, params:$data} -->
    <!-- /ko -->
  </div>
</div>



回答2:

From your Fiddle, I see that you render each field as an individual item. You could bypass all the if bindings if each item were presented the same, as a custom-bound text item that, when clicked, transforms into its editable field type.

If you want to preserve tabbing, the default presentation could be input, but upon focus, the appropriate field type displays.

Update: it occurs to me that you could even have your custom binding insert the appropriate input type into a bound div, so your presentation would be exactly as it is, but all the logic is in the custom binding.

I've made a Fiddle that dynamically inserts the inputs of appropriate types. It's a little rough, but gives the general idea of the approach. I don't know whether it's a performance advantage over the if bindings; it might just be doing the same things the hard way. Certainly the above suggestions of rendering as text or simple input and changing one field at a time when the user wants to edit would be faster on initial load.