我有一个基于Backbone.Marionette的应用程序,它有一个CollectionView,可以实例化许多CompositeViews,这些视图呈现树结构。
我已经通读了僵尸视图(僵尸上的贝利)以及视图和区域文档。但是,如果在阅读时一切看起来都很简单,那么执行就完全是一个不同的问题。
当我点击任何路线时,我的键盘快捷键最终会被多次触发。我找到了一个解决方法,但是此解决方法会导致视图中呈现更改的其他问题。
以下是多次触发键盘快捷键的实际代码。在Snippet A
中,我添加了我能想到的任何关闭视图的方法,尽管通常关闭视图只需要App.contentRegion.currentView.treeRegion.close()
showContentView: (tree) ->
if @treeView?
App.contentRegion.currentView.treeRegion.reset()
App.contentRegion.currentView.treeRegion.close()
@treeView.close()
delete @treeView
@treeView = new App.Note.TreeView(collection: tree)
App.contentRegion.currentView.treeRegion.show @treeView
下面的Snippet B
修复了键盘快捷键问题。但是,它会导致额外创建的模型 (复合视图) 未呈现给用户的问题。
showContentView: (tree) ->
if @treeView?
@treeView.collection = tree
@treeView.render()
else
@treeView = new App.Note.TreeView(collection: tree)
App.contentRegion.currentView.treeRegion.show @treeView
这是我初始化 CollectionView 的地方,它反过来渲染 CompositeViews。
initialize: -> # collectionView
@listenTo @collection, "sort", @render
@listenTo @collection, "destroy", @addDefaultNote
Note.eventManager.on 'createNote', @createNote, this
Note.eventManager.on 'change', @dispatchFunction, this
@drag = undefined
initialize: -> # compositeView
@collection = @model.descendants
@bindKeyboardShortcuts()
@listenTo @collection, "sort", @render
Note.eventManager.on "setCursor:#{@model.get('guid')}", @setCursor, @
Note.eventManager.on "render:#{@model.get('guid')}", @render, @
Note.eventManager.on "setTitle:#{@model.get('guid')}", @setNoteTitle, @
这就是我在复合视图中绑定键盘快捷键的方式
bindKeyboardShortcuts: ->
@.$el.on 'keydown', null, 'ctrl+shift+backspace', @triggerShortcut 'deleteNote'
@.$el.on 'keydown', null, 'tab', @triggerShortcut 'tabNote'
@.$el.on 'keydown', null, 'shift+tab', @triggerShortcut 'unTabNote'
@.$el.on 'keydown', null, 'alt+right', @triggerShortcut 'tabNote'
@.$el.on 'keydown', null, 'alt+left', @triggerShortcut 'unTabNote'
@.$el.on 'keydown', null, 'alt+up', @triggerShortcut 'jumpPositionUp'
@.$el.on 'keydown', null, 'alt+down', @triggerShortcut 'jumpPositionDown'
@.$el.on 'keydown', null, 'up', @triggerShortcut 'jumpFocusUp'
@.$el.on 'keydown', null, 'down', @triggerShortcut 'jumpFocusDown'
@.$el.on 'keydown', null, 'alt+ctrl+left', @triggerShortcut 'zoomOut'
@.$el.on 'keydown', null, 'alt+ctrl+right', @triggerShortcut 'zoomIn'
以及我如何触发它们
triggerShortcut: (event) -> (e) =>
e.preventDefault()
e.stopPropagation()
@triggerEvent(event).apply(@, Note.sliceArgs arguments)
triggerEvent: (event) ->
(e) =>
@updateNote()
args = ['change', event, @model].concat(Note.sliceArgs arguments, 0)
Note.eventManager.trigger.apply(Note.eventManager, args)
最后,为了确保一切正常,我在onBeforeClose中解绑了每个快捷方式。我还取消绑定了事件管理器的任何侦听器。
onBeforeClose: ->
console.log "view being closed", @
@$el.off()
Note.eventManager.off "setCursor:#{@model.get('guid')}"
Note.eventManager.off "render:#{@model.get('guid')}"
Note.eventManager.off "setTitle:#{@model.get('guid')}"
Note.eventManager.off "timeoutUpdate:#{@model.get('guid')}"
我知道问题来自@treeView = new App.Note.TreeView(collection: tree)
.如果我在每个@showContentView
(片段 A)上创建一个新的 TreeView,则每个添加的模型都会正确呈现到视图中,但快捷方式会变得疯狂。
另一方面,如果我创建一个 TreeView 并交换它的集合(代码段 B),我会在视图中遇到渲染问题,但快捷方式很好!
我试图包含您需要的所有内容,仅此而已(它已经是相当多的代码..),但如果你们需要其他任何东西,请询问!
希望我能说得足够清楚..
[编辑]我已经尝试了许多不同的组合来摆脱快捷方式错误,但是如果我在每个showContentView上创建一个新的TreeView,似乎没有什么可以正确关闭视图。我认为这是来自更深层次的内存泄漏问题。我可能会在这方面写另一个StackOverflow问题,并链接到这个问题以获取更多信息。
谢谢!
我想出了这里的问题。
使用Snippet A
和chrome devtool的剖析器,我可以追踪泄漏。我在问题中提供的 onClose 方法来自 CompositeView,其中绑定了键盘快捷键。
问题是CollectionView没有被垃圾收集,因为使用了Note.eventManager.on
,它保留了对视图的引用。所以我在树视图(CollectionView)中添加了一个onBeforeClose方法。
onBeforeClose: ->
Note.eventManager.off('createNote', @createNote, this)
Note.eventManager.off('change', @dispatchFunction, this)
@drag = undefined
有了这个onBeforeClose,视图现在被正确关闭,这反过来又允许子视图也关闭,并停止收听正在触发的快捷方式。
我想这很明显,一旦我发现,但我想添加这个答案,以便它清楚地表明您在没有@listenTo的情况下设置的任何事件侦听器都不会被木偶清除,需要妥善处理。
[编辑]
为了跟进评论,从一开始就会是一个更好的解决方案:
取代
initialize: -> # compositeView
/* ... */
Note.eventManager.on "setCursor:#{@model.get('guid')}", @setCursor, @
Note.eventManager.on "render:#{@model.get('guid')}", @render, @
Note.eventManager.on "setTitle:#{@model.get('guid')}", @setNoteTitle, @
和
initialize: -> # collectionView
/* ... */
Note.eventManager.on 'createNote', @createNote, this
Note.eventManager.on 'change', @dispatchFunction, this
跟
initialize: -> # compositeView
/* ... */
@listenTo Note.eventManager, "setCursor:#{@model.get('guid')}", @setCursor
@listenTo Note.eventManager, "render:#{@model.get('guid')}", @render
@listenTo Note.eventManager, "setTitle:#{@model.get('guid')}", @setNoteTitle
/* ... */
initialize: -> # collectionView
/* ... */
@listenTo Note.eventManager, 'createNote', @createNote, this
@listenTo Note.eventManager, 'change', @dispatchFunction, this
这样使用 listenTo 语法可以首先防止内存泄漏。因此,可以完全删除onBeforeClose块!