我有一个使用Marionette的Web应用程序,经常需要渲染大型嵌套视图树。我希望通过确保在父容器放入DOM之前呈现子元素并将其放置在父容器中来避免尽可能多的重排。如何渲染这些子元素以实现此目的?
我目前正在onRender
函数中呈现所有子视图,据我所知,它们与父级同时显示,但我无法找到任何视图文档告诉我这是它的预期用途。我也考虑过onBeforeShow
的使用,我知道在将视图放入DOM之前就已经调用了它,但它使重新渲染变得更加复杂,因为我不能只调用{ {1}}然后。
根据要求,这里有一些代码可以更好地了解我的情况:
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);
}
});
答案 0 :(得分:2)
当视图el
已经附加到DOM时,渲染会立即将视图附加到DOM,因此顶级视图将立即附加到DOM。这意味着如果你在onRender中有你的回调,它们将按顺序连接,你将有回流。
如果您将子视图显示放入onBeforeShow,则会构建一个将显示一次的视图树,假设顶部视图已调用show
。所以问题是在哪里调用顶级show
。我看到2个选项
最简单的方法是让您的顶级LayoutView在onRender
中显示其子项,然后所有后续子视图都会在onBeforeShow
中显示其子项。这可能会导致一些油漆(顶层,然后是它所显示的每个区域的一个),但不是它们的巨大级联,并且可能不是性能问题(当然听起来你没有可见的性能问题。)
如果你真的只需要对模型更改进行一次重新绘制,那么你应该按照问题中的描述创建一个包装视图,但不要让子视图直接引用它,确保包装View有对模型的引用,并在模型更改时重新显示您的布局。然后,您的所有回调都可以是onBeforeShow
,并且更改将导致一次绘制。视图之间没有连接,所有内容都将在同一个回调中一致显示。
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);
}
});
答案 1 :(得分:1)
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节点。
回到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。
在CompositeView中,渲染的子项将附加到CompositeView.attachBuffer()
中的父视图(如果它是集合的第一个渲染,或collection.reset()
)或CompositeView._insertBefore
/ {{1 (可排序/不可分类的孩子)。
所有这些最终都会实现某种形式的_insertAfter
。
在您的场景中,您提到父级尚未加入DOM,然后这些都附加到仅存在于内存中的collectionView.$el.append(childView.el)
。
Marionette用于渲染和注入儿童视图的方式的美妙之处在于,您可以忘记时来渲染孩子。只要您从头开始正确设置嵌套种子,该过程将是递归的,并且在呈现最后一个子项之前,顶部视图将不会返回。这意味着在将节点附加到DOM之前,所有子节点都将附加到树上。
如果您对如何为嵌套视图设定种子有特定问题,请发布相关代码,我可以为您提供更多指示。
答案 2 :(得分:1)
LayoutView.render()
根据评论,在问题澄清后,我提出了这个解决方案:
LayoutView.render()
执行一些内务处理,然后只需拨打ItemView.render()
。我建议您覆盖父LayoutView.render()
中的LayoutView
,如下所示
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;
},
我还没有对此进行测试(过去没有尝试过),但它足够疯狂以至于它可能会起作用。