这份向导概括 Turbolinks 基本原理、功能和实践。阅读这份向导将会了解:
- Turbolinks 的作用
- 如何移除 Turbolinks
- Turbolinks 提供了什么接口
- 如何与 Assets Pipeline 配合
如有疑问,欢迎留言反馈。
1. 简介
Turbolinks 是 Rails 4.0 以 gem 的形式预装的一个组件,它的工作是加速站内链接的访问速度。
Turbolinks 会接管所有站内链接,用 ajax 请求获得目标页面,然后替换 body 内容。Turbolinks 接管下的网站不用重复解析 js 和 css 文件,这可以达到加速的目的,同时也让整站成为一个单页应用,页面切换不释放之前的页面内容。
借用一张图片说明(来源:Rails 4のturbolinksについて最低でも知っておきたい事):
流程:
- 点击链接
- XHR 请求页面
- 返回页面
- 将返回页面的 title 和 body 部分替换到当前页面,css 和 js 丢弃
这意味着使用 Turbolinks 的网站会让页面切换更快,但也要注意单页环境下产生的 js 陷阱。
2. 如何移除
有些项目希望完全移除 Turbolinks,例如不希望处理单页应用的问题,或者本身就有另一套单页前端逻辑。Turboinks 以 gem 的形式引入,所以它可以像一般的 gem 那样移除。
对于基于 Rails 4.0 的新项目,在 Gemfile 中,移除这一行:
gem 'turbolinks' # 移除
在 app/assets/javascripts/application.js 中,移除这一行:
//= require turbolinks // 移除
这样项目中就不会有 Turbolinks 相关的代码了。
升级到 Rails 4.0 的旧项目,自己不添加的话不会有 Turbolinks 的代码。
3. 事件的改变
由于 Turbolinks 接管了页面切换,页面的DOMContentLoaded
事件是不会触发的,jQuery.ready()
也会失效,所以如果有 js 逻辑绑定了DOMContentLoaded
事件,就需要为 Turbolinks 做兼容。
jQuery 可以通过 jquery.turbolinks 插件兼容 https://github.com/kossnocorp/jquery.turbolinks
取而代之的是,Turbolinks 访问新页面会触发5个事件:
-
page:before-change
开始换页,返回 false 终止访问 -
page:fetch
开始访问新页面 -
page:receive
已经收到服务器返回,但还没处理 -
page:change
已切换到新页面 -
page:load
整个处理完成
而使用浏览器历史返回上一页的时候,会触发2个事件:
-
page:change
已切换到缓存页面 -
page:restore
访问历史记录完毕
通过绑定以上事件可以实现载入进度条等功能。
4. 三个 data-* 属性
Turbolinks 对视图中的三个 data-*
属性进行了特别处理,有必要了解。
4.1 data-turbolinks-track
默认情况下,Turbolinks 忽略返回的 head 部分变化,这个属性的作用就是让 Turbolinks 检查 head 内静态文件是否有改变,如有改变则重载页面。
例如返回的页面头部有以下内容:
<link href="/assets/application-9bd64a86adb3cd9ab3b16e9dca67a33a.css" rel="stylesheet" type="text/css" data-turbolinks-track>
那么 Turbolinks 会检查当前的页面中是否已经记录这个静态文件,如果已记录的静态文件数量和名字跟新页面的不完全相符,就刷新当前页面。
推荐给 head 里面的静态文件链接都加上这个属性,这是为了防止网站的部署更新之后,用户没有刷新页面而一直使用旧的静态文件出现行为或样式异常。
4.2 data-no-turbolink
如果点击链接的标签或者父级标签拥有 data-no-turbolink 属性,那么这个标签的点击事件不会触发 Turbolinks 访问。
例如以下标签:
<a href="/">Home (via Turbolinks)</a>
<div id="some-div" data-no-turbolink>
<a href="/">Home (without Turbolinks)</a>
</div>
前一个链接会发起 Turbolinks 请求,后一个不会。
如果网站按功能区分了不同的区域,各自使用不同的静态文件,不适合全部打包成一个,那么就在切换这些功能区域的链接上加上这个属性。
4.3 data-turbolinks-eval
如果一个 script 标签拥有 data-turbolinks-eval 属性,那么这个 script 标签只有在这个页面是直接访问的页面时才会执行,通过 Turbolinks 访问则不会执行。
例如以下 script 标签存在于 body 之中:
<script type="text/javascript" data-turbolinks-eval="false">
console.log("I'm only run once on the initial page load");
</script>
那么这段代码只有在这个页面是直接访问的时候才会执行。目前还没发现必须使用这个属性的情景。
5. 与 Assets Pipeline 配合
Turbolinks 起效的条件之一是 head 内的静态文件不变,所以需要 Assets Pipeline 这样的静态文件打包工具配合。
5.1 打包成一个
Turbolinks 忽略 head 内的变化,细化静态文件按需载入的做法会失效。如果前端逻辑不是特别复杂,那么最好打包成一个。
打包成一个也不一定全站只有一个静态 js 和 css,可以根据不同的模块区分布局,每个布局有自己的静态文件,布局页面间的链接加上 data-no-turbolink。
例如有两个布局 application 和 admin,那么 js 代码可以这样组织:
app/assets/javascripts/application/
├─ ...
app/assets/javascripts/admin/
├─ ...
app/assets/javascripts/application.js.coffee
app/assets/javascripts/admin.js.coffee
application.js 和 admin.js 是最终编译出来的 js 文件,内容类似:
# in application.js.coffee
#= require jquery
#= require jquery_ujs
#= require jquery.turbolinks
#= require_tree ./application
# in admin.js.coffee
#= require jquery
#= require jquery.turbolinks
#= require_tree ./admin
5.2 页面特定的逻辑
在整站打包一个 js 文件的情况下,可以用以下办法区分页面特定逻辑。
在 layout 中设置页面 ID:
<body id="<%= controller_name%>-<%= action_name %>">
...
</body>
<!--
Example:
<body id="topics-show">
</body>
!--
这样页面特定逻辑就可以根据是否有页面 ID 进行判断(已加载 jquery.turbolinks):
$ ->
if($('#topics-show').length)
# topics show logic
5.3 区域特定的逻辑
对于垮页面的逻辑,可以根据特定元素的 ID 触发。
<div id="sidebar">
...
</div>
$ ->
if($('#sidebar')).length
# sidebar logic
6. 注意事项
Turbolinks 开启后,网站将成为一个单页应用,页面切换不释放 js 逻辑。这意味着编写前端逻辑的时候需要留意内存泄露或者其他持久页面导致的问题。
6.1 避免全局变量
在单页面应用中,js 全局变量将真的成为全局变量——除非刷新,否则变量会进入所有访问页面。
所有 js 逻辑建议放在匿名空间中执行:
// wrong
var foo = 'bar'; // 全局变量
// right
(function() {
var foo = 'bar'; // 匿名空间内的局部变量
})();
如果使用 CoffeeScript,那么所有编译结果都已经包裹在匿名空间内:
# right in coffee
foo = 'bar'
所以推荐使用 CoffeeScript。
6.2 清理定时任务
如果某个页面设置了定时任务,记得加上退出逻辑。
$ ->
if $('articles-index').length
updateInterval = setInterval ->
doSomething()
, 10 * 1000
$(document).one 'page:change', ->
clearInterval updateInterval
6.3 清理没有释放的绑定
绑定在 document 和 window 上的事件是在页面切换后是保留的,记得清理。
$ ->
if $('articles-index').length
$(window).on 'scroll', ->
doSomething()
$(document).one 'page:change', ->
$(window).off 'scroll'
7. 其他资料
原文:http://chloerei.com/2013/07/14/turbolinks-guide/