我有三种不同的方法来初始化和呈现视图及其子视图,每种方法都有不同的问题.我很想知道是否有更好的方法来解决所有的问题:


情景一:

在父级的Initialize函数中初始化子对象.这样,并不是所有内容都卡在渲染中,因此渲染时阻塞较少.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.render().appendTo(this.$('.container-placeholder');
}

问题是:

  • 最大的问题是在父级上第二次调用Render将删除所有的Childs事件绑定.(这是因为jQuery的$.html()的工作方式.)拨打this.child.delegateEvents().render().appendTo(this.$el);可以缓解这种情况,但是第一种情况,也是最常见的一种情况是,你不必要地做了更多的工作.

  • 通过附加子对象,可以强制渲染函数了解父对象的DOM struct ,以便获得所需的顺序.这意味着更改模板可能需要更新视图的渲染功能.


情景二:

仍然初始化父对象initialize()中的子项,但不是追加,而是使用setElement().delegateEvents()将子项设置为父项模板中的元素.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

问题:

  • 这使得delegateEvents()现在是必需的,这比在第一个场景中的后续调用中才需要它略微负一些.

情景三:

而是在父对象的render()方法中初始化子对象.

initialize : function () {

    //parent init stuff
},

render : function () {

    this.$el.html(this.template());

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

问题:

  • 这意味着渲染功能现在也必须与所有初始化逻辑绑定在一起.

  • 如果我编辑其中一个子视图的状态,然后在父视图上调用render,将生成一个全新的子视图,其所有当前状态都将丢失.这似乎也可能导致内存泄漏.


真的很想知道你们的人对这件事的看法.你会使用哪种场景?还是有第四种神奇的方法可以解决所有这些问题?

您是否跟踪过视图的渲染状态?比如说renderedBefore国旗?看起来真的很僵硬.

推荐答案

这是一个很好的问题.主干是很棒的,因为它没有做任何假设,但它确实意味着您必须(决定如何)自己实现这样的事情.看过我自己的东西后,我发现我(有点)混合使用了场景1和场景2.我认为不存在第四个神奇的场景,因为足够简单,你在场景1和场景2中所做的一切都必须要做.

我想用一个例子来解释我喜欢如何处理它是最容易的.假设我将这个简单的页面拆分为指定的视图:

页面分解

假设HTML在呈现后是这样的:

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

希望HTML与图表的匹配非常明显.

ParentView包含两个子视图InfoViewPhoneListView以及几个额外的div,其中#name需要在某个点上设置.PhoneListView保存它自己的子视图,由PhoneView个条目组成的array.

那么继续你的实际问题吧.我根据视图类型以不同的方式处理初始化和呈现.我把我的视图分成两类,Parent个视图和Child个视图.

它们之间的区别很简单,Parent个视图包含子视图,而Child个视图不包含子视图.在我的例子中,ParentViewPhoneListViewParent个视图,而InfoViewPhoneView个条目是Child个视图.

就像我之前提到的,这两个类别之间最大的区别是它们被允许渲染的时间.在一个完美的世界里,我希望Parent个视图只渲染一次.模型更改时,由其子视图处理任何重新渲染.Child个视图,另一方面,我允许随时重新渲染它们,因为它们没有任何其他依赖它们的视图.

更详细地说,对于Parent个视图,我希望我的initialize函数可以做一些事情:

  1. 初始化我自己的视图
  2. 渲染我自己的视图
  3. 创建并初始化所有子视图.
  4. 在我的视图中 for each 子视图指定一个元素(例如,InfoView将被指定为#info).

第一步非常简单.

第2步(呈现)已经完成,这样在我try 分配子视图所依赖的任何元素之前,它们就已经存在了.通过这样做,我知道所有的子events都将被正确设置,并且我可以任意多次地重新渲染它们的块,而不必担心必须重新委托任何东西.我在这里实际上没有render个子元素的观点,我允许他们在他们自己的initialization中这样做.

实际上,在创建子视图时,在我传入el的同时处理步骤3和4.我想在这里传递一个元素,因为我觉得家长应该决定子元素在自己的视图中可以把内容放在哪里.

对于渲染,我试着在Parent个视图中保持相当简单.我希望render函数只渲染父视图.没有事件委托,没有呈现子视图,什么都没有.只是一个简单的渲染.

但有时候这并不总是有效的.例如,在我上面的示例中,当模型中的名称更改时,#name元素将需要随时更新.然而,这个块是ParentView模板的一部分,不是由专用的Child视图处理的,所以我解决了这个问题.我将创建某种subRender函数,only替换#name元素的内容,而不必丢弃整个#parent元素.这看起来像是一种黑客行为,但我真的发现它比担心重新呈现整个DOM和重新连接元素之类的事情更有效.如果我真的想把它弄干净,我会创建一个新的Child视图(类似于InfoView),来处理#name块.

现在,对于Child个视图,initialization相当于Parent个视图,只是没有再创建Child个视图.因此:

  1. 初始化我的视图
  2. 安装程序将监听我关心的模型的任何更改
  3. 渲染我的视图

Child视图渲染也很简单,只需渲染并设置my el的内容.再次强调,不要干扰授权或诸如此类的事情.

下面是我的ParentView可能是什么样子的一些示例代码:

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

你可以在这里看到我对subRender的实现.通过将更改绑定到subRender而不是render,我不必担心爆炸和重建整个街区.

下面是InfoView块的示例代码:

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

绑定是这里的重要部分.通过绑定到我的模型,我永远不用担心自己手动拨打render.如果模型发生更改,该块将在不影响任何其他视图的情况下重新渲染自身.

PhoneListView将类似于ParentView,您只需要在initializationrender函数中多一点逻辑来处理集合.如何处理集合实际上取决于您,但您至少需要监听集合事件并决定如何呈现(追加/删除,或者只是重新呈现整个挡路).我个人喜欢添加新视图和删除旧视图,而不是重新渲染整个视图.

PhoneViewInfoView几乎是一样的,只听它关心的型号变化.

希望这对我有所帮助,如果有什么让人困惑或不够详细,请告诉我.

Javascript相关问答推荐

foreach循环中的Typescript字符串索引

使用print This时, map 容器已在LeafletJS中初始化

materialized - activeIndex返回-1

有条件的悲剧

深嵌套的ng-container元素仍然可以在Angular 布局组件中正确渲染内容吗?

传递一个大对象以在Express布局中呈现

类型脚本中只有字符串或数字键而不是符号键的对象

react—router v6:路由没有路径

如何从一个列表中创建一个2列的表?

未捕获错误:[]:getActivePinia()被调用,但没有活动Pinia.🍍""在调用app.use(pinia)之前,您是否try 使用store ?""

为什么useState触发具有相同值的呈现

从包含数百行的表中获取更改后的值(以表单形式发送到后端)的正确方法是什么?

如何在ASP.NET中使用Google Charts API JavaScript将条形图标签显示为绝对值而不是负值

JavaScript不重定向配置的PATH

如何在DYGRAPS中更改鼠标事件和键盘输入

如果一个字符串前面有点、空格或无字符串,后面有空格、连字符或无字符串,则匹配正则表达式

基于产品ID更新条带产品图像的JavaScript命中错误

Node.js错误: node 不可单击或不是元素

react 路由DOM有条件地呈现元素

Reaction-在同一页内滚动导航(下拉菜单)