如何在Backbone.js中处理初始化和渲染子视图?

Ian*_*lor 198 javascript backbone.js

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


情景一:

在父级的初始化函数中初始化子级.这样,并不是所有东西都会陷入渲染状态,因此渲染时阻塞较少.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

    this.child.render().appendTo(this.$('.container-placeholder');
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 最大的问题是第二次在父级上调用render将删除所有的子事件绑定.(这是因为jQuery的$.html()工作原理.)这可以通过调用this.child.delegateEvents().render().appendTo(this.$el);来减轻,但是第一个,也是最常见的情况是,你正在做更多不必要的工作.

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


情景二:

初始化父级的子级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();
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 这使得delegateEvents()现在变得必要,这只是在第一个场景中的后续调用中必需的一点点负面.

情景三:

render()而是在父方法中初始化子项.

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 这意味着现在必须将渲染函数与所有初始化逻辑联系起来.

  • 如果我编辑其中一个子视图的状态,然后在父视图上调用render,将创建一个全新的子节点,并且它的所有当前状态都将丢失.对于内存泄漏而言,它似乎也会变得冒险.


非常好奇让你的家伙接受这个.你会使用哪种场景?还是有第四个神奇的解决所有这些问题?

你有没有跟踪视图的渲染状态?说renderedBefore一面旗帜?看起来真的很笨拙.

Kev*_*eel 258

这是一个很好的问题.Backbone很棒,因为它缺乏假设,但它确实意味着你必须(决定如何)自己实现这样的事情.在查看了我自己的东西之后,我发现我(有点)使用了场景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>
Run Code Online (Sandbox Code Playgroud)

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

ParentView拥有2个视图,InfoView并且PhoneListView还有一些额外的div,其中之一,#name,需要在某些点进行设置. PhoneListView拥有自己的子视图,一系列PhoneView条目.

那么关于你的实际问题.我根据视图类型处理初始化和渲染.我将我的观点分为两种类型,即Parent观点和Child观点.

它们之间的区别很简单,Parent视图包含子视图而Child视图不包含.所以在我的例子,ParentView并且PhoneListViewParent观点,而InfoViewPhoneView条目Child意见.

就像我之前提到的,这两个类别之间的最大区别在于它们被允许渲染.在一个完美的世界中,我希望Parent视图只渲染一次.当模型发生变化时,由子视图决定是否处理任何重新渲染.Child另一方面,我允许在他们需要的任何时候重新渲染,因为他们没有依赖他们的任何其他观点.

更详细一点,对于Parent视图我喜欢我的initialize功能做一些事情:

  1. 初始化我自己的观点
  2. 渲染我自己的视图
  3. 创建并初始化任何子视图.
  4. 在视图中为每个子视图分配一个元素(例如,InfoView将分配该元素#info).

第1步非常自我解释.

第2步,完成渲染,以便在我尝试分配子视图之前,子视图所依赖的任何元素都已存在.通过这样做,我知道所有孩子events都将被正确设置,并且我可以根据需要重新渲染他们的块,而不必担心必须重新委派任何东西.我实际上并没有render任何儿童观点,我允许他们在自己的观点内做到这一点initialization.

第3步和第4步实际上是el在创建子视图时传入的同时处理的.我喜欢在这里传递一个元素,因为我觉得父母应该在自己的视图中确定允许孩子放置其内容的位置.

对于渲染,我尝试使Parent视图非常简单.我希望该render函数除了呈现父视图之外什么都不做.没有事件委托,没有渲染子视图,没有.只是一个简单的渲染.

有时这并不总是有效.例如,在上面的示例中,#name只要模型中的名称发生更改,就需要更新元素.但是,此块是ParentView模板的一部分,而不是由专用Child视图处理,所以我解决了这个问题.我将创建一些替换元素内容的subRender函数,而不必删除整个元素.这可能看起来像是一个黑客,但我真的发现它比不必担心重新渲染整个DOM和重新附加元素等更好.如果我真的想让它干净,我会创建一个处理块的新视图(类似于).#name#parentChildInfoView#name

现在对于Child视图,initialization它与Parent视图非常相似,只是没有创建任何进一步的Child视图.所以:

  1. 初始化我的观点
  2. 安装程序绑定侦听我关心的模型的任何更改
  3. 渲染我的观点

Child视图渲染也很简单,只需渲染和设置我的内容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);
    }
});
Run Code Online (Sandbox Code Playgroud)

你可以看到我在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());
    }
});
Run Code Online (Sandbox Code Playgroud)

绑定是这里的重要部分.通过绑定到我的模型,我永远不必担心手动调用render自己.如果模型更改,此块将重新呈现自身而不会影响任何其他视图.

PhoneListView会类似ParentView,你只需要在你的都多一点的逻辑initializationrender函数来处理集合.你如何处理集合真的取决于你,但你至少需要听取集合事件并决定你想要渲染的方式(追加/删除,或者只是重新渲染整个块).我个人喜欢添加新视图并删除旧视图,而不是重新渲染整个视图.

PhoneView会是几乎相同的InfoView,只是听模式改变它关心.

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

  • 真的很透彻的解释谢谢.问题但是,我听说并且倾向于同意在`initialize`方法中调用`render`是一种不好的做法,因为它会阻止你在你不想立即渲染的情况下更高效.你怎么看待这件事? (8认同)
  • 不能重新渲染ParentView是一个很大的限制.我有一种情况,我基本上有一个选项卡控件,其中每个选项卡显示不同的ParentView.我想在第二次单击选项卡时重新渲染现有的ParentView,但这样做会导致ChildViews中的事件丢失(我在init中创建子项,并在渲染中渲染它们).我想我要么1)隐藏/显示而不是渲染,2)在ChildViews渲染中委托事件,或者3)在渲染中创建子节点,或者4)不要担心perf.在它出现问题之前每次都重新创建ParentView.Hmmmm .... (5认同)

Der*_*ley 6

我不确定这是否直接回答了你的问题,但我认为这是相关的:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

当然,我设置这篇文章的背景是不同的,但我认为我提供的两个解决方案,以及每个解决方案的优缺点,都应该让你朝着正确的方向前进.


Bru*_*ser 6

对我而言,通过某种标志来区分视图的初始设置和后续设置似乎不是世界上最糟糕的想法.为了使这个干净简单,应该将标志添加到您自己的View中,该View应该扩展Backbone(Base)View.

与Derick相同我不完全确定这是否直接回答了您的问题,但我认为在此背景下至少可能值得一提.

另请参阅:在Backbone中使用Eventbus