被朋友问到了关于 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 效果好, 部署也算简单.