如何处理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');
}

问题:

  • 最大的问题是第二次调用父级渲染会删除所有子级事件绑定。 (这是因为jQuery的$.html()是如何工作的。)这可以通过调用this.child.delegateEvents().render().appendTo(this.$el);来缓解this.child.delegateEvents().render().appendTo(this.$el); 相反,但第一次,也是最常见的情况是,你不必要地做更多的工作。

  • 通过追加孩子,您可以强制渲染功能掌握父母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();
    }
    

    问题:

  • 这使得现在必须使用delegateEvents() ,这对第一种情况下的后续调用而言只是必要的。

  • 情景三:

    请改为在父级的render()方法中初始化子级。

    initialize : function () {
    
        //parent init stuff
    },
    
    render : function () {
    
        this.$el.html(this.template());
    
        this.child = new Child();
    
        this.child.appendTo($.('.container-placeholder').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条目数组。

    因此,你的实际问题。 我根据视图类型处理初始化和渲染。 我将视图分为两种类型,即Parent视图和Child视图。

    它们之间的区别很简单, Parent视图可以保持子视图,而Child视图不会。 因此,在我的示例中, ParentViewPhoneListViewParent视图,而InfoViewPhoneView条目是Child视图。

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

    再详细一点,对于Parent视图,我喜欢我的initialize函数来做一些事情:

  • 初始化我自己的观点
  • 呈现我自己的观点
  • 创建并初始化任何子视图。
  • 为我的视图中的每个子视图分配一个元素(例如, InfoView将被分配#info )。
  • 第一步非常自我解释。

    第2步,渲染完成后,我试图分配它们之前,子视图依赖的任何元素都已存在。 通过这样做,我知道所有的小孩events都会正确设置,并且我可以根据需要多次重新渲染自己的块,而不必担心需要重新分配任何东西。 我实际上并没有在这里render任何子视图,我允许他们在他们自己的initialization这样做。

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

    为了渲染,我尽量保持Parent视图非常简单。 我希望render函数只能渲染父视图。 没有事件代表团,没有渲染子视图,什么都没有。 只是一个简单的渲染。

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

    现在对于Child视图, initializationParent视图非常相似,只是没有创建任何其他Child视图。 所以:

  • 初始化我的观点
  • 安装程序绑定侦听对我关心的模型的任何更改
  • 呈现我的视图
  • 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);
        }
    });
    

    你可以在这里看到我的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几乎完全相同,只是倾听其关心的型号变化。

    希望这有所帮助,请让我知道是否有任何混淆或不够详细。


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

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

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


    对我来说,似乎并不是世界上最糟糕的想法,通过某种标志来区分初始设置和后续的视图设置。 为了使这个干净和容易,该标志应该被添加到你自己的视图中,这应该扩展主干(基础)视图。

    与Derick相同我不完全确定这是否直接回答您的问题,但我认为在这方面至少值得一提。

    另请参阅:在骨干中使用Eventbus

    链接地址: http://www.djcxy.com/p/35355.html

    上一篇: How to handle initializing and rendering subviews in Backbone.js?

    下一篇: I know its bad to store data in the DOM, but why?