Backbone: 重新渲染中事件丢失

38

我有一个名为super-View的视图,负责呈现sub-Views。当我重新渲染super-View时,所有sub-Views中的事件都会丢失。

这是一个例子:

var SubView = Backbone.View.extend({
    events: {
        "click": "click"
    },

    click: function(){
        console.log( "click!" );
    },

    render: function(){
        this.$el.html( "click me" );
        return this;
    }
});

var Composer = Backbone.View.extend({
    initialize: function(){
        this.subView = new SubView();
    },

    render: function(){
        this.$el.html( this.subView.render().el );             
    }
});


var composer = new Composer({el: $('#composer')});
composer.render();
当我在
点击我
中点击时,事件被触发。如果我再次执行composer.render(),一切看起来都很相似,但是点击事件不再被触发。
请查看可工作的jsFiddle示例
3个回答

66

当你这样做时:

this.$el.html( this.subView.render().el );
你实际上是在说这个:
this.$el.empty();
this.$el.append( this.subView.render().el );

empty会删除this.$el内部的所有元素以及与它们相关的数据和事件处理函数,以避免内存泄漏。

所以在这种情况下,您将失去在this.subView上绑定事件的delegate调用,而且SubView#render不会重新绑定它们。

您需要在this.$el.html()之后,添加一个this.subView.delegateEvents()调用。您可以按照以下方式实现:

render: function(){
    console.log( "Composer.render" );
    this.$el.empty();
    this.subView.delegateEvents();
    this.$el.append( this.subView.render().el );             
    return this;
}

示例: http://jsfiddle.net/ambiguous/57maA/1/

或者像这样:

render: function(){
    console.log( "Composer.render" );
    this.$el.html( this.subView.render().el );             
    this.subView.delegateEvents();
    return this;
}

演示:http://jsfiddle.net/ambiguous/4qrRa/

或者您可以使用remove并在渲染时重新创建this.subView来规避这个问题(但这可能会引起其他问题...)。


我自己永远找不到它。这有点疯狂 :/。感谢@mu。 - fguillen
@fguillen:我曾经不得不追踪类似的东西,我认为这需要通过jQuery源代码进行一次旅行和大量的实验。这个过程已经永久地烙印在我的记忆中了。 - mu is too short
这让我朝着正确的方向解决了我的问题。谢谢@muistooshort。 - Amiga500
1
@muistooshort 有类似的问题,但是 delegateEvents 并没有起到帮助作用。http://jsfiddle.net/ismusidhu/kt9zr5z9/7/ - IsmailS
@IsmailS:你的render调用对视图的this.img属性做了什么?这里有一个提示:如果你使用this.$('#abc')而不是尝试缓存this.img,它会起作用。在你的点击处理程序中加入console.log('clicked'),你会发现你没有事件问题。 - mu is too short

11

这里有一个更简单的解决方案,不需要一开始就清除事件注册:jQuery.detach()

http://jsfiddle.net/ypG8U/1/

this.subView.render().$el.detach().appendTo( this.$el );   

出于性能考虑,这种变体可能更可取:

http://jsfiddle.net/ypG8U/2/

this.subView.$el.detach();

this.subView.render().$el.appendTo( this.$el );

// or

this.$el.append( this.subView.render().el );

显然,这只是一个匹配示例的简化,其中子视图是父视图的唯一内容。如果真是这种情况,你可以重新呈现子视图。如果有其他内容,您可以采取以下措施:

var children = array[];

this.$el.children().detach();

children.push( subView.render().el );

// ...

this.$el.append( children );

或者

_( this.subViews ).each( function ( subView ) {

  subView.$el.detach();

} );

// ...

另外,在您的原始代码中并在@mu的答案中重复,一个DOM对象被传递给jQuery.html(),但该方法只被记录为接受HTML字符串:

此外,在您的原始代码和@mu的回答中,将DOM对象传递给jQuery.html(),但该方法仅记录为接受HTML字符串:

this.$el.html( this.subView.render().el );

jQuery.html()的文档签名:

.html( htmlString )

http://api.jquery.com/html/#html2


1
非常有趣,"更简单的解决方案" 不是我会使用的词汇,但知道这个真的很好。谢谢。 - fguillen
我认为从编写视图类的角度来看,是否更“简单”是有争议的。但我认为这样做可能更有效率,因为您保留了事件监听器的注册,而不是每次重新应用它们。如果您还可能通过jQuery.data()将数据存储在元素中,并希望在重新渲染时保留它们,那么这也是需要考虑的,因为detach()将在不需要额外代码的情况下保留数据。 - JMM

0

使用$(el).empty()时,它会删除所选元素中的所有子元素并删除与所选元素(el)内的任何(子)元素绑定的所有事件(和数据)。

为了保留绑定在子元素上的事件,但仍然删除子元素,请使用:

$(el).children().detach();而不是$(.el).empty();

这将允许您的视图成功重新呈现,并且事件仍然绑定并正常工作。


请参考以下链接以获取更多信息:https://dev59.com/KGs05IYBdhLWcg3wQvuq#7417078 和 http://www.jquerybyexample.net/2012/05/empty-vs-remove-vs-detach-jquery.html - AmpT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接