Recently, there have been two commits to enyo with regards to deferring reflows.
I've been working on similar strategies to reduce reflows at my current project and ran into a few issues I'd like to share here. Perhaps they might be of use to the team and enyo in general.
First off, I think it is a great idea to defer reflows for hidden components. It can confirm from experience that hiding a view and deferring reflows can vastly improve performance compared to tearing down and re-rendering or even destroying and re-creating it.
I did notice a few things about the current implmentation though. First off, I think the current implementation entangles concerns that should be dealt with separately.
Originally, the reflow method had one purpose:
- To execute any code required to ensure the component is displayed properly in the browser. Preferably, this code would only deal with issues that cannot be solved with CSS alone.
Now that the reflow method is able to defer itself though, it has two purposes:
- To determine if a reflow should take place at all.
- To execute the any code required to ensure the component is displayed properly in the browser, assuming it is *always* contained within a layout component.
The responsibilities of the reflow method on layout kinds have changed in a similar fashion.
While this doesn't necessarily need to cause problems, uncareful usage of the new reflow method will.
Given the fact that reflows often require only very small and simple changes to the DOM, I think it's safe to assume many users simply replaced or extended the default reflow method and added layout code directly to the reflow method rather than than implementing a completely separate layout component. I know I do this on occation and I doubt I' m the only one.
Now that the reflow method has two responsibilities though, this strategy becomes problematic. In cases where the reflow method had previously been extended/replaced, the reflow of the component tree could end up getting partially deferred and partially executed immediately.
In this case, a component sub-tree's components are no longer guaranteed to reflow in the correct order. This does not *always* result in a broken layout, but when it does, it can be very difficult to work out exactly what's going wrong and why. Especially when unaware that the reflow is now expected to able to defer itself.
Instead, I would propose the reflow method gets split into two methods:
- One that decides if a reflow should take place or should be deferred.
- And another that executes the reflow, unaware if its execution was ever deferred at all.
If it is required to let a layout component decide whether or not to trigger a reflow éven if it's container is already showing, the layout component's reflow method should similarly be split in two:
/**
* Determines if a reflow should take place or if it should be deferred instead.
* Additional logic can be added to the layoutKind if needed.
*
* @private
*/
enyo.Control.shouldReflow: function() {
if (this.layout) {
this._needsReflow = this.showing ? this.layout.shouldReflow() : true;
}
return !this._needsReflow;
}
/**
* Immediately executes any logic required to properly reflow and layout the rendered component.
* This method can be replaced or extended freely.
*
* @public
*/
enyo.Control.reflow: function() {
if (this.layout) {
this.layout.reflow();
}
}
// Example immediate reflow
if (this.shouldReflow()) {
this.reflow();
this._needsReflow = false;
}
// Example deferred reflow
if (this._needsReflow) {
this.reflow();
}
Second, I ran into an issue with a plain unextended enyo.DataRepeater. In my current application, there appears to be a scenario where the repeater can destroy one of it's child components and trigger a reflow on it *after* the child's node has been removed from the DOM, but *before* the child has been marked as destroyed. I have been unable to track down the exact cause as it turned out to be exceedingly complicated to debug and it's solution was much easy to work out without knowing the exact cause. Anyways, I ended up adding the following
code to my application:
enyo.Control.extend({
destroy: enyo.inherit(function(sup) {
this._needsReflow = false;
sup.apply(this, arguments);
});
});
I'm unsure whether this problem is specific to my application or inherent to the way enyo.DataRepeater works, but it might be worth exploring if the problem occurs with any of the various repeater implementations currently available in v2.6*.