如何在 Backbone.js 中处理初始化和渲染子视图? [英] How to handle initializing and rendering subviews in Backbone.js?

查看:16
本文介绍了如何在 Backbone.js 中处理初始化和渲染子视图?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<小时>

场景一:

在父级的初始化函数中初始化子级.这样,并非所有内容都卡在渲染中,从而减少渲染阻塞.

初始化:函数(){//父初始化的东西this.child = new Child();},渲染:函数(){this.$el.html(this.template());this.child.render().appendTo(this.$('.container-placeholder');}

问题:

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

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

<小时>

场景二:

仍然在父级的 initialize() 中初始化子级,但不是追加,而是使用 setElement().delegateEvents() 将子级设置为父母模板.

初始化:函数(){//父初始化的东西this.child = new Child();},渲染:函数(){this.$el.html(this.template());this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();}

问题:

  • 这使得 delegateEvents() 现在是必需的,这比它只在第一种情况下的后续调用中是必需的略有负面作用.
<小时>

场景三:

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

初始化:函数(){//父初始化的东西},渲染:函数(){this.$el.html(this.template());this.child = new Child();this.child.appendTo($.('.container-placeholder').render();}

问题:

  • 这意味着渲染函数现在也必须与所有初始化逻辑相关联.

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

<小时>

真的很好奇让你们的人接受这个.你会使用哪种场景?或者有没有第四种神奇的方法可以解决所有这些问题?

您是否曾经跟踪过视图的渲染状态?说一个 renderedBefore 标志?看起来真的很笨拙.

解决方案

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

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

假设 HTML 在渲染后是这样的:

<div id="name">人员:Kevin Peel</div><div id="信息">名字:<span class="first_name">Kevin</span><br/>姓氏:<span class="last_name">Peel</span><br/>

<div>电话号码:</div><div id="电话号码"><div>#1:123-456-7890</div><div>#2:456-789-0123</div>

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

ParentView 包含 2 个子视图,InfoViewPhoneListView 以及一些额外的 div,其中之一,#名称,需要在某个时候设置.PhoneListView 拥有自己的子视图,一个 PhoneView 条目的数组.

接下来是您的实际问题.我根据视图类型以不同的方式处理初始化和渲染.我将视图分为两种类型,Parent 视图和 Child 视图.

它们之间的区别很简单,Parent 视图持有子视图,而 Child 视图没有.所以在我的例子中,ParentViewPhoneListViewParent 视图,而 InfoViewPhoneView 条目是 Child 视图.

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

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

  1. 初始化我自己的视图
  2. 呈现我自己的观点
  3. 创建并初始化所有子视图.
  4. 在我的视图中为每个子视图分配一个元素(例如,InfoView 将分配给 #info).

第 1 步是不言自明的.

第 2 步渲染已经完成,因此在我尝试分配它们之前,子视图所依赖的任何元素都已经存在.通过这样做,我知道所有子 events 都将被正确设置,并且我可以根据需要多次重新渲染它们的块,而不必担心必须重新委托任何东西.我实际上并没有在此处渲染任何子视图,我允许他们在自己的初始化中执行此操作.

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

对于渲染,我尽量让 Parent 视图非常简单.我希望 render 函数只渲染父视图.没有事件委托,没有子视图的呈现,什么都没有.只是一个简单的渲染.

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

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

  1. 初始化我的视图
  2. 设置绑定监听我关心的模型的任何更改
  3. 渲染我的视图

Child 视图渲染也很简单,只需要渲染和设置我的el 的内容即可.同样,不要搞乱委托或类似的事情.

以下是我的 ParentView 的一些示例代码:

var ParentView = Backbone.View.extend({el: "#parent",初始化:函数(){//第 1 步,(init)我想知道名字改变的任何时候this.model.bind("change:first_name", this.subRender, this);this.model.bind("change:last_name", this.subRender, this);//第 2 步,渲染我自己的视图this.render();//步骤 3/4,创建子元素并分配元素this.infoView = new InfoView({el: "#info", model: this.model});this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});},渲染:函数(){//渲染我的模板this.$el.html(this.template());//渲染名称this.subRender();},子渲染:函数(){//设置我们的名字块,只有我们的名字块$("#name").html("人物:" + this.model.first_name + " " + this.model.last_name);}});

您可以在此处查看我对 subRender 的实现.通过将更改绑定到 subRender 而不是 render,我不必担心炸掉和重建整个块.

以下是 InfoView 块的示例代码:

var InfoView = Backbone.View.extend({初始化:函数(){//我想重新渲染更改this.model.bind("change", this.render, this);//使成为this.render();},渲染:函数(){//只渲染我的模板this.$el.html(this.template());}});

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

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

PhoneView 几乎与 InfoView 相同,只是监听它关心的模型变化.

希望这对您有所帮助,如果有任何令人困惑或不够详细的地方,请告诉我.

I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:


Scenario One:

Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

The problems:

  • The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's $.html() works.) This could be mitigated by calling this.child.delegateEvents().render().appendTo(this.$el); instead, but then the first, and the most often case, you're doing more work unnecessarily.

  • By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.


Scenario Two:

Initialize the children in the parent's initialize() still, but instead of appending, use setElement().delegateEvents() to set the child to an element in the parents template.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Problems:

  • This makes the delegateEvents() necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.

Scenario Three:

Initialize the children in the parent's render() method instead.

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

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

Problems:

  • This means that the render function now has to be tied down with all of the initialization logic as well.

  • If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.


Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?

Have you ever kept track of a rendered state for a View? Say a renderedBefore flag? Seems really janky.

解决方案

This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.

I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:

Say the HTML is, after being rendered, something like this:

<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>

Hopefully it's pretty obvious how the HTML matches up with the diagram.

The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.

So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.

The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.

Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.

In a little more detail, for Parent views I like my initialize functions to do a few things:

  1. Initialize my own view
  2. Render my own view
  3. Create and initialize any child views.
  4. Assign each child view an element within my view (e.g. the InfoView would be assigned #info).

Step 1 is pretty self explanatory.

Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render any child views here, I allow them to do that within their own initialization.

Steps 3 and 4 are actually handled at the same time as I pass el in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.

For rendering, I try to keep it pretty simple for Parent views. I want the render function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.

Sometimes this doesn't always work though. For instance in my example above, the #name element will need to be updated any time the name within the model changes. However, this block is part of the ParentView template and not handled by a dedicated Child view, so I work around that. I will create some sort of subRender function that only replaces the content of the #name element, and not have to trash the whole #parent element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child view (similar to the InfoView) that would handle the #name block.

Now for Child views, the initialization is pretty similar to Parent views, just without the creation of any further Child views. So:

  1. Initialize my view
  2. Setup binds listening for any changes to the model I care about
  3. Render my view

Child view rendering is also very simple, just render and set the content of my el. Again, no messing with delegation or anything like that.

Here is some example code of what my ParentView may look like:

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);
    }
});

You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.

Here's example code for the InfoView block:

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());
    }
});

The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.

The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.

The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.

Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.

这篇关于如何在 Backbone.js 中处理初始化和渲染子视图?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆