Backbone.js:重新填充或重新创建视图?

IT技术 javascript templates backbone.js
2021-03-07 03:29:00

在我的 Web 应用程序中,我的左侧表格中有一个用户列表,右侧有一个用户详细信息窗格。当管理员单击表中的用户时,其详细信息应显示在右侧。

我在左边有一个 UserListView 和 UserRowView,在右边有一个 UserDetailView。事情有点工作,但我有一个奇怪的行为。如果我单击左侧的一些用户,然后单击其中一个用户的删除,我会得到所有已显示用户的连续 javascript 确认框。

看起来之前显示的所有视图的事件绑定都没有被移除,这似乎是正常的。我不应该每次都在 UserRowView 上创建一个新的 UserDetailView?我应该维护视图并更改其参考模型吗?我应该跟踪当前视图并在创建新视图之前将其删除吗?我有点迷茫,任何想法都会受到欢迎。谢谢 !

下面是左视图的代码(行显示、点击事件、右视图创建)

window.UserRowView = Backbone.View.extend({
    tagName : "tr",
    events : {
        "click" : "click",
    },
    render : function() {
        $(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
        return this;
    },
    click : function() {
        var view = new UserDetailView({model:this.model})
        view.render()
    }
})

以及右视图的代码(删除按钮)

window.UserDetailView = Backbone.View.extend({
    el : $("#bbBoxUserDetail"),
    events : {
        "click .delete" : "deleteUser"
    },
    initialize : function() {
        this.model.bind('destroy', function(){this.el.hide()}, this);
    },
    render : function() {
        this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
        this.el.show();
    },
    deleteUser : function() {
        if (confirm("Really delete user " + this.model.get("login") + "?")) 
            this.model.destroy();
        return false;
    }
})
6个回答

我总是销毁和创建视图,因为随着我的单页应用程序变得越来越大,将未使用的实时视图保留在内存中以便我可以重新使用它们将变得难以维护。

这是我用来清理视图以避免内存泄漏的技术的简化版本。

我首先创建一个 BaseView,我的所有视图都继承自它。基本思想是我的 View 将保留对其订阅的所有事件的引用,以便在处理 View 时,所有这些绑定将自动解除绑定。这是我的 BaseView 的示例实现:

var BaseView = function (options) {

    this.bindings = [];
    Backbone.View.apply(this, [options]);
};

_.extend(BaseView.prototype, Backbone.View.prototype, {

    bindTo: function (model, ev, callback) {

        model.bind(ev, callback, this);
        this.bindings.push({ model: model, ev: ev, callback: callback });
    },

    unbindFromAll: function () {
        _.each(this.bindings, function (binding) {
            binding.model.unbind(binding.ev, binding.callback);
        });
        this.bindings = [];
    },

    dispose: function () {
        this.unbindFromAll(); // Will unbind all events this view has bound to
        this.unbind();        // This will unbind all listeners to events from 
                              // this view. This is probably not necessary 
                              // because this view will be garbage collected.
        this.remove(); // Uses the default Backbone.View.remove() method which
                       // removes this.el from the DOM and removes DOM events.
    }

});

BaseView.extend = Backbone.View.extend;

每当视图需要绑定到模型或集合上的事件时,我都会使用 bindTo 方法。例如:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

每当我删除一个视图时,我只调用 dispose 方法,它会自动清理所有内容:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

我与正在编写“Backbone.js on Rails”电子书的人分享了这项技术,我相信这是他们在本书中采用的技术。

更新:2014-03-24

从 Backone 0.9.9 开始,使用上述相同的 bindTo 和 unbindFromAll 技术将 listenTo 和 stopListening 添加到事件中。此外,View.remove 会自动调用 stopListening,因此绑定和解除绑定现在就像这样简单:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();
嗨 SunnyRed,我更新了我的答案以更好地反映我破坏视图的原因。使用 Backbone,我认为没有理由在应用程序启动后重新加载页面,因此我的单页面应用程序变得非常大。当用户与我的应用程序交互时,我会不断重新渲染页面的不同部分(例如从详细信息切换到编辑视图),因此我发现始终创建新视图要容易得多,无论该部分之前是否已渲染或不是。另一方面,模型代表业务对象,所以我只会在对象真的改变时修改它们。
2021-04-15 03:29:00
您对如何处理嵌套视图有任何建议吗?现在我正在做类似于 bindTo:gist.github.com/1288947但我想有可能做得更好。
2021-04-17 03:29:00
如果我有打开和关闭相同视图的功能怎么办。我有一个前进和后退按钮。如果我调用 dispose 它将从 DOM 中删除元素。我应该一直将视图保存在内存中吗?
2021-04-18 03:29:00
嗨,fisherwebdev。您也可以将此技术与 Backbone.View.extend 一起使用,但您需要在 BaseView.initialize 方法中初始化 this.bindings。这样做的问题是,如果您继承的视图实现了自己的 initialize 方法,那么它将需要显式调用 BaseView 的 initialize 方法。我在这里更详细地解释了这个问题:stackoverflow.com/a/7736030/188740
2021-05-02 03:29:00
德米特里,我做了一些类似于你正在做的处理嵌套视图的事情。我还没有看到更好的解决方案,但我也很想知道是否有一个。这是另一个涉及此问题的讨论:groups.google.com/forum/# ! topic/backbonejs/3ZFm-lteN-A我注意到在您的解决方案中,您没有考虑直接处理嵌套视图的情况。在这种情况下,即使嵌套视图已被释放,父视图仍将持有对嵌套视图的引用。我不知道你是否需要考虑这个。
2021-05-08 03:29:00

我最近写了一篇关于这个的博客,并展示了我在我的应用程序中为处理这些场景所做的几件事:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

为什么不只delete view在路由器中?
2021-04-25 03:29:00
我赞成您的答案,但如果将博客文章的相关部分包含在答案本身中确实会受益,因为这是这里的目标。
2021-05-02 03:29:00

这是一种常见的情况。如果每次都创建一个新视图,所有旧视图仍将绑定到所有事件。您可以做的一件事是在您的视图上创建一个名为 的函数detatch

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

然后,在创建新视图之前,请确保调用detatch旧视图。

当然,正如您所提到的,您始终可以创建一个“详细信息”视图并且永远不会更改它。您可以绑定到模型上的“更改”事件(从视图中)以重新渲染自己。将此添加到您的初始化程序:

this.model.bind('change', this.render)

这样做将导致详细信息窗格在每次对模型进行更改时重新渲染。您可以通过观察单个属性来获得更精细的粒度:“change:propName”。

当然,这样做需要item View 引用的通用模型以及更高级别的列表视图和详细信息视图。

希望这可以帮助!

嗯,我按照你的建议做了一些事情,但我仍然有问题:例如,这this.model.unbind()对我来说是错误的,因为它解除了这个模型中的所有事件,包括关于同一用户的其他视图的事件。而且,为了调用detach函数,我需要保持对视图的静态引用,我很不喜欢它。我怀疑还有一些我没有理解的东西......
2021-04-30 03:29:00

要多次修复事件绑定,

$("#my_app_container").unbind()
//Instantiate your views here

在从路由实例化新视图之前使用上面的行,解决了我在僵尸视图中遇到的问题。

解除绑定后我似乎无法重新渲染:\
2021-04-26 03:29:00
@FlyingAtom:即使在解除绑定后我也无法重新渲染视图。你有没有找到办法做到这一点?
2021-04-29 03:29:00
view.$el.removeData().unbind();
2021-05-05 03:29:00
这里有很多非常好的、详细的答案。我绝对打算研究一些 ViewManger 建议。然而,这个非常简单,它对我来说非常有效,因为我的视图都是带有 close() 方法的面板,我可以在其中取消绑定事件。谢谢亚山
2021-05-09 03:29:00

我认为大多数人从 Backbone 开始会像你的代码一样创建视图:

var view = new UserDetailView({model:this.model});

这段代码创建了僵尸视图,因为我们可能会在不清理现有视图的情况下不断创建新视图。但是,为应用程序中的所有主干视图调用 view.dispose() 并不方便(特别是如果我们在 for 循环中创建视图)

我认为放置清理代码的最佳时机是在创建新视图之前。我的解决方案是创建一个助手来执行此清理工作:

window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        // Cleanup view
        // Remove all of the view's delegated events
        VM.views[name].undelegateEvents();
        // Remove view from the DOM
        VM.views[name].remove();
        // Removes all callbacks on view
        VM.views[name].off();

        if (typeof VM.views[name].close === 'function') {
            VM.views[name].close();
        }
    }
    VM.views[name] = callback();
    return VM.views[name];
}

VM.reuseView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        return VM.views[name];
    }

    VM.views[name] = callback();
    return VM.views[name];
}

使用 VM 创建视图将有助于清理任何现有视图,而无需调用 view.dispose()。您可以从

var view = new UserDetailView({model:this.model});

var view = VM.createView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

所以如果你想重用视图而不是不断地创建它取决于你,只要视图是干净的,你就不用担心。只需将 createView 更改为重用视图:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

详细代码和归属发布在https://github.com/thomasdao/Backbone-View-Manager

也许是因为他是 Backbone 的专家:)。我认为这种技术非常简单并且使用起来非常安全,我一直在使用它并且到目前为止没有问题:)
2021-05-05 03:29:00
我最近一直在广泛地使用主干,这似乎是在构建或重用视图时处理僵尸视图的最充实的方法。我通常遵循 Derick Bailey 的例子,但在这种情况下,这似乎更灵活。我的问题是,为什么没有更多的人使用这种技术?
2021-05-12 03:29:00