【问题标题】:Minimizing reflow with Marionette使用 Marionette 最大限度地减少回流
【发布时间】:2015-01-13 15:12:34
【问题描述】:

我有一个使用 Marionette 的 Web 应用程序,它经常需要渲染大型嵌套视图树。我想通过确保在将父容器放入 DOM 之前渲染子元素并将其放置在父容器中来避免尽可能多的重排。如何渲染这些子元素来实现这一点?

我目前正在 onRender 函数中呈现所有子视图,据我所知,它们与父视图同时显示,但我找不到任何文档告诉我这是它的预期用途是什么。我还考虑过onBeforeShow 的使用,我知道它肯定会在视图放入 DOM 之前调用,但它使重新渲染变得更加复杂,因为我不能只调用 render

根据要求,这里有一些代码可以更好地了解我的情况:

var Layout = LayoutView.extend({
    //...

    initialize: function() {
        listenTo(this.model, "some event indicating that data changed", this.render);
    },

    onRender: function() {
        //Will this new view render at the appropriate time to minimize page reflows?
        this.someRegion.show(new view(new model()));
    }
});

如果我选择使用onBeforeShow,那么我可以传递对容器区域的引用

var Layout = LayoutView.extend({
    //...

    initialize: function() {
        listenTo(this.model, "some event indicating that data changed", this.callback);
    },

    callback: function() {
        this.container.show(this, {forceShow: true});
    },

    onBeforeShow: function() {
        //Does not cause a page reflow, but this solution isn't pretty.
        this.someRegion.show(new View(new Model()));
    }
});

var LayoutContainer = LayoutView.extend({
    //...

    onRender: function() {
        var model = new LayoutModel();
        var layout = new Layout({
            model: model,
            container: this.containerRegion
        });
        this.containerRegion.show(layout);
    }
});

或者让父对象监听并告诉视图何时渲染

var Layout = LayoutView.extend({
    //...

    onBeforeShow: function() {
        //Does not cause a page reflow, but this solution isn't pretty.
        this.someRegion.show(new View(new Model()));
    }
});

var LayoutContainer = LayoutView.extend({
    //...

    onRender: function() {
        var model = new LayoutModel();
        this.layout = new Layout({ model: model });
        listenTo(model, "some event indicating that data changed", this.rerenderSubview);
        this.containerRegion.show(this.layout);
    },

    rerenderSubview: function() {
        this.containerRegion.show(this.layout, {forceShow: true});
    }
});

【问题讨论】:

    标签: javascript marionette


    【解决方案1】:

    在您需要采取行动的水平上聆听您的模型

    当视图 el 已经附加到 DOM 时,渲染会立即将视图附加到 DOM,因此顶级视图将立即附加到 DOM。这意味着如果您在 onRender 中有回调,它们将按顺序附加并且您将进行重排。

    如果您改为将子视图显示放到 onBeforeShow 中,您将构建一个仅显示一次的视图树,假设顶部视图已调用 show。所以问题是在哪里调用顶级show。我看到了 2 个选项

    简单的选项

    最简单的做法是让您的顶级 LayoutView 在onRender 中显示它的子视图,然后所有后续子视图在onBeforeShow 中显示它们的子视图。这可能会导致一些油漆(顶层,然后是它所显示的每个区域的一个),但不会导致它们的巨大级联,并且可能不是性能问题(当然听起来你没有可见的性能问题。)

    稍微复杂一点的选项

    如果您真的只想在模型更改时重绘一次,那么您应该按照问题中的描述创建一个包装视图,而不是让子视图直接引用它,确保包装视图具有对模型的引用,并在模型更改时重新显示您的布局。然后你所有的回调都可以是onBeforeShow,并且更改将导致一次绘制。 View 之间不会有任何联系,所有内容都会在同一个回调中一致地显示。

    var LayoutContainer = LayoutView.extend({
        initialize: function() {
            this.listenTo(this.model,'change',this.reshowLayout);
        },
    
        reshowLayout: function() {
            var layout = new Layout({
                model: this.model,
                container: this.containerRegion
            });
            this.containerRegion.show(layout);
        }
    });
    

    【讨论】:

      【解决方案2】:

      Marionette 插入 DOM 最后

      Marionette 实际上不会将 Collection/CompositeView 的子代附加到 DOM,直到 所有内容 被渲染(父节点和父节点下的子节点)。看看这是如何发生的:

      Region.show()

      show: function () {
        // ...
        if (_shouldShowView) {
          view.render();
          // ...
          this.attachHtml(view);
        }      
        // ...
      }
      

      region.show() 将调用view.render(),后者将调用CompositeView.render()

      render: function() {
        //...
        this._renderRoot();
        this._renderChildren();
        //...
        return this;
      }
      

      并注意在该函数中首先调用view_renderRoot(),然后调用view._renderChildren()。这些函数将呈现的视图附加到 DOM。它们只是填充模板并将 HTML 节点加载到内存中。

      终于在DOM中

      回到Regions.show()region.attachHtml() 是真正将视图插入到 DOM 中:

      attachHtml: function(view) {
        // empty the node and append new view
        this.el.innerHTML='';
        this.el.appendChild(view.el);
      },
      

      如果您不使用区域管理器,并且自己使用 .render() 您的视图,您仍然需要像 region.attachHtml() 那样将视图附加到 DOM。

      在插入 DOM 之前,所有子级都附加到父级

      在 CompositeView 中,渲染的子视图在 CompositeView.attachBuffer()(如果它是集合的第一次渲染,或在 collection.reset())或 CompositeView._insertBefore/_insertAfter(可排序/不可排序的子视图)中附加到父视图.

      所有这些最终都实现了某种形式的collectionView.$el.append(childView.el)

      在您的场景中,您提到父级尚未在 DOM 中,那么这些都附加到仅存在于内存中的 collectionView.$el

      忘记事件

      Marionette 用于渲染和注入子视图的美妙之处在于,您可以忘记何时 渲染子视图。只要您从一开始就正确设置了嵌套种子,该过程将是递归的,并且在呈现最后一个孩子之前,顶视图将不会返回。这意味着您的所有孩子将在您将节点附加到 DOM 之前附加到树。

      如果您对如何为嵌套视图播种有具体问题,请发布您的相关代码,我可以为您提供更多指导。

      【讨论】:

      • 您已发送至CollectionViewCompositeView,但LayoutView 呢?您还假设视图在渲染时已经知道它的所有子级。我什么时候添加子项(使用show())以便它们按所需顺序呈现?
      • 您必须向我展示您的代码。具体来说,为什么在子视图onBeforeShow 上调用渲染很复杂
      • 视图监听其模型的变化并相应地重新渲染。我想将这种逻辑限制在所讨论的观点中。如果在onBeforeShow 中添加子视图,则视图必须保留对包含区域的引用才能调用show(),或者检查模型更改的逻辑必须在@ 987654349@ 持有该观点。我用一些代码编辑了我的问题。
      【解决方案3】:

      覆盖LayoutView.render()

      基于cmets,问题明确后提出这个解决方案:

      LayoutView.render() 会做一些家务,然后只打电话给ItemView.render()。我建议您在父级LayoutView 中覆盖LayoutView.render(),如下所示

      render: function() {
        this._ensureViewIsIntact();
      
        if (this._firstRender) {
          // if this is the first render, don't do anything to
          // reset the regions
          this._firstRender = false;
        } else {
          // If this is not the first render call, then we need to
          // re-initialize the `el` for each region
          this._reInitializeRegions();
        }
      
        var layout = Marionette.ItemView.prototype.render.apply(this, arguments);
      
        // After the parent rendered render and attach the child
        this.someRegion.show(new view(new model()));
      
        return layout;
      
      },
      

      我没有测试过这个(过去没有尝试过),但它足够疯狂,它可能会起作用。

      【讨论】:

      • 所以 Marionette 没有提供任何本地方式来执行此操作?
      • 据我所知。考虑到将子视图附加到布局的唯一方法是使用onBeforeShow 事件,在我看来,Marionette 并不太关心在插入父视图之前将子视图附加到其父视图。我认为这种想法是深层层次结构可能会使用基于CompositionView 的方法,其中子注入在内存中进行了优化。尽管如此,我还是会四处打听。
      • 上面的答案非常有精神,如果不是木偶,至少是骨干。在我们对 Marionette 感到满意之前(相信我, 有),重写我们自己的渲染函数是 SOP。它甚至与 Marionette 一致,后者旨在以尽可能小的包提供通用用例工具。这就是为什么只要你避开 Marionette 内部方法,覆盖就完全没问题。
      猜你喜欢
      • 1970-01-01
      • 2014-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-01
      相关资源
      最近更新 更多