Rails 关于 turbolinks:load 重复执行问题的研究

jicheng1014 · 2019年05月18日 · 最后由 Rei 回复于 2019年05月19日 · 671 次阅读

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

共收到 1 条回复

不要依赖 turbolinks:load 了,https://stimulusjs.org/ 是最适合 Turbolinks 的做法。

https://github.com/turbolinks/turbolinks#attaching-behavior-with-stimulus

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册