当前位置: 首页 > 知识库问答 >
问题:

使用Rails 5.1中的TurboLink未正确渲染的React组件

施鸿
2023-03-14

我有一个非常简单的Rails应用程序,它有一个react组件,只在特定页面(比如show页面)的现有div元素中显示“Hello”。

当我用它的网址加载相关页面时,它就工作了。我在页面上看到你好。

但是,当我之前在另一个页面上时(比如说索引页面,然后我使用Turbolinks进入显示页面,那么,组件不会被呈现,除非我来回移动。(返回索引页面并返回显示页面)

从这里开始,每次我来回走动,我可以说视图的渲染时间增加了两倍<不仅两次,而且还有两次!(即2次,然后4次,然后6次等)

我知道,因为在我设置div内容的同时,我向控制台输出了一条消息。

事实上,我想回到索引页仍然应该运行没有显示的组件代码,因为div元素不在索引页上。但是为什么是以累积的方式呢?

我想解决的问题是:

  1. 要在显示页面的第一个请求中运行代码
  2. 阻止代码在其他页面(包括索引页面)中运行的步骤
  3. 使代码在显示页面的后续请求上运行一次

这里是我使用的确切步骤和代码(我会尽量简洁。)

>

  • 我有一个Rails 5.1应用程序,安装了react:

    rails new myapp --webpack=react
    

    然后我创建一个简单的项目支架来获得一些页面来玩:

    rails generate scaffold Item name
    

    我只是在Show页面中添加了以下div元素(app/views/items/Show.html.erb):

    <div id=hello></div>
    

    Webpacker已经生成了一个Hello组件(Hello\u react.jsx),为了使用上面的div元素,我对该组件进行了如下修改。我更改了原始的'DOMContentLoaded'事件:

    document.addEventListener('turbolinks:load', () => {
      console.log("DOM loaded..");
      var element = document.getElementById("hello");
      if(element) {
        ReactDOM.render(<Hello name="React" />, element)
      }
    })
    

    然后,我在上一个视图的底部添加了以下webpack脚本标记(app/view/Item/show.html.erb):

    <%= javascript_pack_tag("hello_react") %>
    

    然后,我使用foreman start(通过在gem文件中添加gem'foreman'安装)运行rails服务器webpack开发服务器)。以下是我使用的Procfile的内容:

    web:     bin/rails server -b 0.0.0.0 -p 3000
    webpack: bin/webpack-dev-server --port 8080 --hot
    

    以下是重现所述行为的步骤:

    1. 使用URLhttp://localhost:3000/items
    2. 加载索引页
    3. 单击新建项以添加新项。Rails在URLlocalhost:3000/items/1处重定向到项目的显示页面。在这里我们可以看到你好反应!消息。它工作得很好!
    4. 使用URLhttp://localhost:3000/items重新加载索引页。项目按预期显示。
    5. 使用URLhttp://localhost:3000/items/1重新加载show页面。Hello消息按预期显示为一条控制台消息。
    6. 使用URL重新加载索引页http://localhost:3000/items
    7. 单击Show链接(应通过turbolink执行)。消息既不显示控制台消息。
    8. 单击返回链接(应该通过turbolink执行)转到索引页。
    9. 再次单击Show链接(应通过turbolink执行)。这一次消息显示得很好。控制台部分的消息显示两次。

    从那里,每次我返回索引并再次返回到show页面时,都会在控制台上显示两条以上的消息。

    注意:如果我让原始的hello\u文件附加一个div元素,而不是使用(并替换)一个特定的div元素,那么这种行为会更加明显
    编辑:另外,如果我通过包含数据{turbolinks:false}链接更改为链接。它工作得很好。正如我们使用浏览器地址栏中的URL加载页面一样。

    我不知道我做错了什么<有什么想法吗?

    编辑:如果有兴趣尝试这个,我把代码放在下面的回购:https://github.com/sanjibukai/react-turbolinks-test


  • 共有3个答案

    长孙弘壮
    2023-03-14

    根据你说的,我测试了一些代码。


    首先,我简单地从侦听器中拉出ReactDOM。渲染方法,就像您在第一个片段中建议的那样。
    这向前迈出了一大步,因为消息不再显示在其他地方(如索引页),而只显示在显示中页面作为通缉。

    但是在展示页面上发生了一些有趣的事情。消息不再作为附加的div元素进行累积,这很好。事实上,它甚至可以根据需要展示一次。但是控制台消息显示两次!?

    我猜这里发生了一些与缓存机制相关的事情,但是既然消息应该被追加,为什么它没有显示为控制台消息的两倍呢?

    撇开这个问题不谈,这似乎是可行的,我想知道为什么首先有必要在页面加载后放置React渲染(没有Turbolink就有DOMContentLoed事件监听器)?
    我猜想,这与一些DOM元素尚未加载时执行的javascript代码的意外渲染有关。


    然后,我尝试了你的替代方式使用

    当我通过URL加载索引页面,然后使用Turbolink转到显示页面时,它工作了
    div消息和控制台消息只显示一次
    然后,如果我返回(使用Turbolink),div消息将消失,我将得到“.unmounted…”控制台消息。

    但是从那时起,每当我返回到show页面时,div和console消息都不会显示
    显示的唯一消息是“.unmounted…”每当我返回索引页时,控制台消息。

    更糟糕的是,如果我使用URL加载显示页面,div消息将不再显示!?控制台消息显示,但我得到了一个关于div元素的错误(不能读取属性'appenchild'的null)。


    最后,我尝试了你最后一个最好的建议,只是把最后一个代码片段放在超文本标记语言的头部。

    因为这是jsx代码,我不知道如何在Rails资产管道/文件结构中处理它,所以我把我的javascript_pack_tag放在htmlhead中。事实上,这很有效。

    这一次,代码在任何地方都被执行,所以使用特定于页面的元素是有意义的(正如之前在注释代码中预期的那样)。

    缺点是,这一次代码可能很混乱,除非我将所有特定于页面的代码放在测试特定于页面的元素是否存在的if语句中
    但是,由于Rails/Webpack具有良好的代码结构,因此将特定于页面的代码放入特定于页面的jsx文件应该是易于管理的。

    尽管如此,好处是这一次所有页面特定部分都与整个页面同时呈现,从而避免了否则会出现的显示故障。

    我一开始并没有提到这个问题,但实际上,我想知道如何在整个页面的同时呈现页面特定的内容
    我不知道将Turbolink与React(或任何其他框架)相结合时,这是否可行。


    但最后,我将这个问题留待以后讨论。

    谢谢你的贡献。。

    蒙勇
    2023-03-14

    我也遇到了类似的问题(link\u tohelper方法正在更改URL,但react内容未加载;必须手动刷新页面才能正确加载)。通过谷歌搜索,我在这个页面上找到了简单的解决方法。

    <%= link_to "Foo", new_rabbit_path(@rabbit), data: { turbolinks: false } %>
    

    由于这会在点击链接时导致整个页面刷新,现在我的反应页面被正确加载。也许你会发现它在你的项目中也很有用:)

    麻鹏鹍
    2023-03-14

    这是一个相当复杂的问题,恐怕我不认为它有一个直截了当的答案。我会尽我所能解释的!

    • 要在show页面的第一个请求上运行代码

    您的turbolink: load事件处理程序未运行,因为您的代码在turbolink: load事件触发后运行。以下是流程:

    1. 用户导航到显示页面
    2. turbolinks:加载已触发
    3. 已计算正文中的脚本

    因此,在下一个页面加载之前,不会调用turbolinks:load事件处理程序(因此不会呈现您的React组件)。

    要(部分)解决此问题,您可以删除turbolinks:load事件侦听器,并直接调用render

    ReactDOM.render(
      <Hello name="React" />,
      document.body.appendChild(document.createElement('div'))
    )
    

    或者,您可以使用

    …
    <head>
      …
      <%= yield :javascript_pack %>
      …
    </head>
    …
    

    然后在你的show.html.erb:

    <%= content_for :javascript_pack, javascript_pack_tag('hello_react') %>
    

    在这两种情况下,对于在turbolinks:load块中使用JavaScript添加到页面的任何HTML,您都应该在turbolinks:before cache上删除它,以防止重新访问页面时出现重复问题,这一点毫无价值。在您的情况下,您可以执行以下操作:

    var div = document.createElement('div')
    
    ReactDOM.render(
      <Hello name="React" />,
      document.body.appendChild(div)
    )
    
    document.addEventListener('turbolinks:before-cache', function () {
      ReactDOM.unmountComponentAtNode(div)
    })
    

    即使如此,您在重新访问页面时仍可能会遇到重复问题。我相信这与预览的渲染方式有关,但我无法在不禁用预览的情况下修复它。

    • 要使代码在显示页面的后续请求中运行一次

    如上所述,在使用TurboLink时,动态地包含特定于页面的脚本可能会造成困难。Turbolinks应用程序中的事件侦听器与没有Turbolinks应用程序的事件侦听器的行为非常不同,在Turbolinks应用程序中,每个页面都会获得一个新的文档,因此事件侦听器会自动删除。除非手动删除事件侦听器(例如在turbolinks:before cache),否则每次访问该页面都会添加另一个侦听器。此外,如果Turbolinks缓存了该页面,则会触发两次Turbolinks:load事件:一次用于缓存版本,另一次用于新副本。这可能就是为什么你看到它被渲染了2、4、6次。

    考虑到这一点,我的最佳建议是避免添加特定于页面的脚本来运行特定于页面的代码。相反,在应用程序中包含所有脚本。js清单文件,并使用页面上的元素确定是否装入组件。您的示例在注释中做了如下操作:

    document.addEventListener('turbolinks:load', () => {
      var element = document.getElementById("hello");
      if(element) {
        ReactDOM.render(<Hello name="React" />, element)
      }
    })
    

    如果这包含在您的application.js中,那么任何带有#Hello元素的页面都将获得该组件。

    希望有帮助!

     类似资料:
    • 我看不到子组件内部的文本。子级呈现为大小为0x0且为空。 那是由路由器引起的吗? 这是我的代码: App.js: REPODetails.js: 这是child committersCard.js: 我用的是4号路由器。

    • 来自导航的代码片段。js 类导航扩展组件{render(){return( ### 使用路由器导出默认值(导航) 我渲染这个组件从App.js 使用路由器导出默认值(应用程序) 下面是我的发票。js 下面是我的index.js 有人能帮我显示发票组件吗。我还没有反应过来。

    • 问题内容: 在React JSX中,似乎无法执行以下操作: 我收到一个解析错误:意外令牌{。这不是React可以处理的吗? 我正在设计此组件,以便在内部隐藏的值将包含有效的HTML元素(h1,p等)。有什么办法可以使这项工作吗? 问题答案: 您不应该将组件子弹放在花括号中: 这是一个有效的小提琴:http : //jsfiddle.net/kb3gN/6668/ 此外,您还可以找到JSX编译器,有

    • 问题内容: 我有一个React组件,其中有许多子组件。我不希望立即渲染子组件,而是要经过一段时间的延迟(对于每个子组件而言,它们是统一的还是不同的)。 我想知道-有没有办法做到这一点? 问题答案: 我认为最直观的方法是给孩子一个“ wait” ,在从父代传递过来的持续时间内隐藏该组件。通过将默认状态设置为隐藏,React仍会立即渲染组件,但是直到状态更改后它才可见。然后,您可以设置为调用一个函数,

    • 我试图渲染父组件子组件与默认渲染组件通过相同的路由。 例如 父母组件可以通过此路由访问/破折号/概述现在在父母组件中,我想呈现一个与父母组件具有相同路由的默认组件。 ParentComponent.js 我想渲染。。它工作正常,但当我将路径更改为时,它不会呈现 我正在使用路由器在路由之间切换,我也在使用

    • 问题内容: 我在渲染中添加了一个条件,突然它停止显示。这是我正在使用的代码。 我只想显示sdata中的4个项目。有人请帮忙。 问题答案: 您忘记了body中的元素,以及 i > = 5*之后的所有条目所需的元素。 * 这样写: 但我建议您使用而不是仅创建5个元素。 如果要使用,则首先使用 slice 并创建5个元素的子元素,然后在其上使用。 像这样: