使用 Turbolinks 开发应用程序确实需要一种特殊的方法才能使事情顺利运行。由于页面加载和缓存的方式不同,某些运行脚本的模式在使用 Turbolinks 与不使用 Turbolinks 时的行为方式不同。起初这可能看起来不友好,并且“陷阱”可能令人沮丧,但我发现只要稍微了解一下,它就会鼓励更有条理、更健壮的代码:)
如您所见,重复开关的问题在于插件在同一个元素上被多次调用。这是因为 Turbolinks 会在离开页面之前缓存页面,因此缓存版本包括任何动态添加的 HTML[1],例如通过插件添加的内容。向后/向前导航时,缓存的版本被恢复,并且行为重复:/
那么如何解决这个问题呢?在使用添加 HTML 或事件侦听器的代码时,通常最好在页面缓存之前拆除行为。Turbolinks 事件是turbolinks:before-cache
. 所以你的设置/拆卸可能是:
// app/assets/javascripts/switches.js
$(document)
.on('turbolinks:load', function () {
$('.switch').bootstrapSwitch()
})
.on('turbolinks:before-cache', function () {
$('.switch').bootstrapSwitch('destroy')
})
这有点难以测试,因为所有设置和拆卸都是在事件处理程序中完成的。更重要的是,可能还有更多这样的情况,所以为了防止重复,您可能需要引入自己的“迷你框架”来设置和拆除功能。下面介绍如何创建一个基本框架。
这是我们的目标:window.App.addFunction
使用名称和函数调用会注册要调用的函数。该函数获取元素并调用插件。它返回一个带有destroy
拆卸功能的对象:
// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
var $switches = $('.switch').bootstrapSwitch()
return {
destroy: function () {
$switches.bootstrapSwitch('destroy')
}
}
})
以下 implements addFunction
,将添加的功能存储在functions
属性中:
// app/assets/javascripts/application.js
// …
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
}
}
我们将在应用程序初始化时调用每个函数,并将每个函数调用的结果存储在results
数组中(如果存在):
// app/assets/javascripts/application.js
// …
var results = []
window.App = {
// …
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
}
}
拆除应用程序涉及破坏destroy
任何结果的调用(如果存在):
// app/assets/javascripts/application.js
// …
window.App = {
// …
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
最后我们初始化和拆解应用程序:
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
所以把这一切放在一起:
;(function () {
var results = []
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
},
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
},
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
})()
函数现在独立于调用它们的事件处理程序。这种脱钩有几个好处。首先它更具可测试性:函数在window.App.functions
. 您还可以选择何时调用您的函数。例如,假设您决定不使用 Turbolinks,您唯一需要更改的部分就是何时window.App.init
调用。
[1] 我认为这比默认浏览器行为更好(按“返回”将用户返回到第一次加载时的页面)。Turbolinks“返回”在用户离开页面时将其返回到页面,这可能是用户所期望的。