被朋友问到了关于 turbolinks:load 被各种页面运行了多次的问题 (他再 load 里绑了 ajax, 结果每个页面都请求), 自己好久没写 rails 的前端,还是抽空解答了一下
感觉这个东西还是有部分朋友没弄明白,正好写篇帖子,自己也加强下认知。
这里简化了下朋友的代码,
<head>
...
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= yield "javascript" %>
</head>
之后弄了两个页面,分别是 index 和 dashboard 页面都很简单
index.html.erb
<h1>Welcome#index</h1>
<%= link_to 'index self', welcome_index_path %>
<%= link_to 'dashboard', welcome_dashboard_path %>
<%= content_for "javascript" do %>
<script>
function consoleByRender(){
console.log('from index turbolinks:load');
}
document.addEventListener("turbolinks:load", consoleByRender)
</script>
<%end%>
dashboard.html.erb
<h1>dashboard.html.erb</h1>
<%= link_to 'dashboard self', welcome_dashboard_path %>
<%= link_to 'index', welcome_index_path %>
<%= content_for "javascript" do %>
<script>
document.addEventListener("turbolinks:load", function(){
console.log("from dashboard turbolinks:load" );
})
</script>
<% end %>
之后我们打开 index, 再点击 dashboard, 发现如下的日志输出
我们可以看到,从浏览器的地址栏里输入 url 里访问的时候,index 页面的 turbolinks 被正常加载了,但是当我们点击 dashboard 链接后
我们发现 在进入 dashboard 的 turbolinks:load 前,index.html.erb 中的 turbolinks 也被响应了。
那么这是什么原因呢?其实道理很简单,因为 turbolinks 劫持了 a 属性,改为了在页面提交 ajax, 并替换原来的属性 , 又因为页面实际上没刷新,所以,document 在这个时候没有被释放 这样 原来添加到 turbolinks:load 里的方法 仍然会再次执行
为了验证这个地方,我们再引入一个 about 页面,这个页面什么都不做,
<%= link_to 'about self', welcome_about_path %>
<%= link_to 'dashboard', welcome_dashboard_path %>
<%= link_to 'index', welcome_index_path %>
由 index 先进入 dashboard, 再进入 about , 我们会发现下面的情况
就算 about 里什么 js 都没有,仍然会出发之前的 turbolinks:load
这也就是为何如如果将 ajax 直接绑定在 turbolinks:load 中 任意页面都会去请求的问题
那么如何解决呢?
目前我看到比较多的 有两种做法。分别是幂等法 和 remove 事件
所谓的幂等法,就是仍然让函数执行,只是执行 1 次和 n 次对外表现的效果是一致的即可,比如最常见的方法是在 application.html.erb 中 把 body 定义一个 id
<body id='<%= "#{controller_name}-#{action_name}"%>'>
<%= yield %>
</body>
这样 在具体页面绑定的时候就可以这样写: index 页面
<h1>Welcome#index</h1>
<%= link_to 'index self', welcome_index_path %>
<%= link_to 'dashboard', welcome_dashboard_path %>
<%= link_to 'about', welcome_about_path %>
<%= content_for "javascript" do %>
<script>
function consoleByRender(){
if(document.querySelector("#welcome-index")) {
console.log('from index turbolinks:load');
}
}
document.addEventListener("turbolinks:load", consoleByRender)
</script>
<%end%>
dashboard 页面
<h1>dashboard.html.erb</h1>
<%= link_to 'dashboard self', welcome_dashboard_path %>
<%= link_to 'index', welcome_index_path %>
<%= link_to 'about', welcome_about_path %>
<%= content_for "javascript" do %>
<script>
document.addEventListener("turbolinks:load", function(){
if(document.querySelector("#welcome-dashboard")) {
console.log("from dashboard turbolinks:load" );
}
})
</script>
<% end %>
这样 结果就变成了
并不是他们没有运行,而是在其他页面运行的时候,由于没有办法找到对应的 body , 所以实际函数并没有执行。
其实大多数时候我就喜欢用这种简单粗暴的方式。虽然说会有一点性能损失,但好像还是在接受范围内。
另外一种是 事件注销法,他要求我们在 addEventListener 的时候,传入的是一个 function 进去, 之后再在适当的事件里去 remove 这个方法, 我对这种方式比较排斥,主要是不太好写,自己 js 也挺烂的 也没找到什么太好的例子 不知道有没有朋友能详细说下这种情况的。
之后补充下看到的简单心理的关于 turbolinks 的处理 https://jiandanxinli.github.io/2017-01-17.html
我觉得本质上还是属于第一种幂等法,利用 全局变量的方式来进行的处理
最后吹吹牛,虽然说现在大环境上都玩前后端分离,react 全家桶,但是我觉得在大多数传统项目上,后端控制路由,上 turbolinks, 也挺好的,开发速度快,SEO 效果好,部署也算简单。