React Component неправильно отображается с Turbolinks в Rails 5.1

3

У меня очень простое приложение Rails с реагирующим компонентом, который просто отображает "Hello" в существующем элементе div на определенной странице (скажем, на странице показа).

Когда я загружаю связанную страницу с помощью URL-адреса, она работает. Я вижу Hello на странице.

Однако, когда я ранее на другой странице (скажем, на странице индекса, а затем я перехожу на страницу показа с помощью Turbolinks, ну, компонент не отображается, если я не вернусь снова и снова. страницу индекса и вернуться на страницу показа)

Отсюда каждый раз, когда я иду туда и обратно, могу сказать, что представление визуализируется в два раза больше.
Не только дважды, но и вдвое больше времени! (то есть 2 раза, затем 4, затем 6 и т.д.)

Я знаю, что, поскольку в то же время я устанавливал содержимое div, я выводил сообщение на консоль.

На самом деле, я думаю, что возврат на индексную страницу все равно должен запускать код компонента без отображения, так как элемент div не находится на индексной странице. Но почему в совокупности?

Я хочу решить следующие проблемы:

  • Чтобы запустить код по первому запросу страницы показа
  • Чтобы заблокировать запуск кода на других страницах (включая индексную страницу)
  • Чтобы код запускался один раз при последующих запросах страницы показа

Вот точные шаги и код, которые я использовал (я постараюсь быть как можно более кратким).

  • У меня есть приложение Rails 5.1 с разрешением, установленным с помощью:

    rails new myapp --webpack=react
    
  • Затем я создаю простой элементный эскиз, чтобы получить некоторые страницы для воспроизведения:

    rails generate scaffold Item name
    
  • Я просто добавлю следующий элемент div на страницу "Показать" (app/views/items/show.html.erb):

    <div id=hello></div>
    
  • Webpacker уже сгенерировал компонент Hello (hello_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 script в нижней части предыдущего представления (app/views/items/show.html.erb):

    <%= javascript_pack_tag("hello_react") %>
    
  • Затем я запускаю rails server и webpack-dev-server с помощью foreman start (устанавливается путем добавления gem 'foreman' в Gemfile). Вот содержание Procfile, которое я использовал:

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

И вот следующие шаги, чтобы воспроизвести описанное поведение:

  • Загрузите страницу индекса с помощью URL http://localhost:3000/items
  • Нажмите "Новый элемент", чтобы добавить новый элемент. Rails перенаправляет на страницу показа товаров по URL localhost:3000/items/1. Здесь мы видим Hello React! сообщение. Он работает хорошо!
  • Загрузите страницу индекса с помощью URL http://localhost:3000/items. Элемент отображается как ожидалось.
  • Загрузите страницу показа с помощью URL http://localhost:3000/items/1. Сообщение Hello отображается как ожидается с помощью одного сообщения консоли.
  • Загрузите страницу индекса с помощью URL http://localhost:3000/items
  • Нажмите ссылку "Показать" (должно быть выполнено через turbolink). Сообщение не не отображается ни в сообщении консоли.
  • Нажмите ссылку "Назад" (должно выполняться через турболинк), чтобы перейти на страницу индекса.
  • Еще раз нажмите ссылку "Показать" (должно быть выполнено через турболинк). На этот раз сообщение хорошо отображается. Сообщение консоли для его части показано дважды.

Оттуда каждый раз, когда я возвращаюсь к индексу и снова возвращаюсь на страницу показа, каждый раз отображается еще два сообщения на консоли.

Примечание. Вместо использования (и замены) конкретного элемента div, если я разрешаю исходный файл hello_react, который добавляет элемент div, это поведение становится еще более заметным.
Edit: Кроме того, если я изменяю ссылки link_to, включив data: {turbolinks: false}. Это работает хорошо. Так же, как мы загрузили страницы, используя URL-адреса в адресной строке браузера.


Я не знаю, что я делаю неправильно.
Любые идеи?

Изменить: я поставил код в следующем репо, если вам интересно попробовать: https://github.com/sanjibukai/react-turbolinks-test

  • 0
    Вы можете разместить демо на GitHub? Я подозреваю, что проблема является результатом добавления обработчика событий каждый раз, когда загружается страница шоу, а не удаления его при выгрузке. Я мог бы дать более полный ответ, если бы я мог посмотреть на проект в полном объеме.
  • 0
    @DomChristie Ага, я постараюсь сделать полный репо .. Но шаги на самом деле исчерпывающие. Ни меньше, ни больше ..
Показать ещё 1 комментарий
Теги:
turbolinks

2 ответа

3

Это довольно сложная проблема, и я боюсь, что я не думаю, что она имеет прямой ответ. Я объясню, насколько это возможно.

  • Чтобы запустить код по первому запросу страницы показа

Ваш обработчик событий turbolinks:load не запущен, потому что ваш код запускается после запуска события turbolinks:load. Вот поток:

  • Пользователь просматривает страницу
  • turbolinks:load срабатывает
  • Script в выраженном теле

Таким образом, обработчик события turbolinks:load не будет вызываться (и поэтому ваш компонент React не будет отображаться) до загрузки следующей страницы.

Чтобы (частично) решить эту проблему, вы можете удалить прослушиватель событий turbolinks:load и вызвать render напрямую:

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

В качестве альтернативы вы можете использовать <%= content_for … %>/<%= yield %> для вставки тега script в head. например в вашем макете application.html.erb

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

то в вашем show.html.erb:

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

В обоих случаях не стоит ничего, что для любого HTML, который вы добавляете на страницу с JavaScript в блоке turbolinks:load, вы должны удалить его на 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)
})

Даже при этом вы можете столкнуться с проблемами дублирования при повторном просмотре страниц. Я считаю, что это связано с тем, как просматриваются предварительные просмотры, но я не смог исправить это, не отключая предварительный просмотр.

  • Чтобы код запускался один раз при последующих запросах страницы показа
  • Чтобы заблокировать запуск кода на других страницах (включая индексную страницу)

Как я уже упоминал выше, включение скриптов, связанных с страницей, динамически может создавать трудности при использовании Turbolinks. Слушатели событий в приложении Turbolinks ведут себя по-другому, чем без Turbolinks, где каждая страница получает новый document, и поэтому прослушиватели событий удаляются автоматически. Если вы вручную не удалите прослушиватель событий (например, на turbolinks:before-cache), каждое посещение этой страницы добавит еще одного слушателя. Что еще, если Turbolinks кэширует эту страницу, событие turbolinks:load будет срабатывать дважды: один раз для кешированной версии, а другой для новой копии. Вероятно, это потому, что вы видели его в 2, 4, 6 раз.

С учетом этого, мой лучший совет - избегать добавления скриптов, предназначенных для страниц, для запуска кода, специфичного для страницы. Вместо этого включите все свои скрипты в файл манифеста application.js и используйте элементы на своей странице, чтобы определить, монтируется ли компонент. Ваш пример делает что-то подобное в комментариях:

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

Если это включено в ваш application.js, тогда любая страница с элементом #hello получит компонент.

Надеюсь, что это поможет!

  • 0
    Привет @Dom Дом и спасибо; последний фрагмент кода - jsx и я не могу добавить его в файл манифеста application.js . Каков наилучший способ сделать это?
  • 0
    Я боюсь , что веб - пакетов проектирования вне меня, но вы можете попробовать <%= javascript_pack_tag 'application' %> в макете приложения head , а затем import './hello_react' в приложении / JavaScript / пакетах / приложении. Как я уже сказал, я не являюсь экспертом по стратегии веб-пакетов, поэтому я не буду приводить эту часть своего ответа, но надеюсь, что это поможет вам начать работу.
2

После того, что вы сказали, я проверил некоторый код.


Во-первых, я просто вытащил метод ReactDOM.render из вашего слушателя, как вы предложили в своем первом фрагменте.
Это дает большой шаг вперед, поскольку сообщение больше не отображается в другом месте (например, на индексной странице), но только на странице показа, если это требуется.

Но что-то интересное происходит на странице показа. больше нет накопления сообщения в виде добавленного элемента div, что хорошо. Фактически он даже отображал один раз по мере необходимости. Но.. Сообщение консоли отображается дважды!?

Я предполагаю, что здесь происходит что-то, связанное с механизмом кэширования, но поскольку сообщение должно быть добавлено, почему оно не отображается дважды как сообщение консоли?

Отложив эту проблему, кажется, что она работает, и мне интересно, почему это необходимо, прежде всего, для рендеринга React после загрузки страницы (без Turbolinks был прослушиватель событий DOMContentLoaded)?
Я предполагаю, что это связано с неожиданным рендерингом кода javascript, выполняемого, когда некоторые элементы DOM еще не загружены.


Затем я попробовал использовать альтернативный способ, используя <%= content_for … %>/<%= yield %>. И, как вы ожидали, это даст смягчающие результаты и какое-то странное поведение.

Когда я загружаю через URL-страницу индексную страницу, а затем перехожу на страницу показа с помощью Turbolink, она работает!
Сообщение div, а также консольное сообщение отображаются один раз.
Затем, если я вернусь (используя Turbolink), сообщение div исчезнет, ​​и я получил сообщение консоли ".. unmounted.." , если захотите.

Но с тех пор, всякий раз, когда я возвращаюсь на страницу показа, div и консольное сообщение вообще никогда не отображаются.
Единственное сообщение, которое отображается, - это консольное сообщение ".. unmounted.." , когда я возвращаюсь на страницу индекса.

Хуже того, если я загружаю страницу показа с помощью URL-адреса, сообщение div больше не!? Отображается сообщение консоли, но я получил ошибку относительно элемента div (Cannot read property 'appenChild' of null). Я не буду отрицать, что полностью игнорирую то, что происходит здесь.


Наконец, я попробовал ваш последний лучший совет и просто поместил последний фрагмент кода в голове HTML.

Поскольку это код jsx, я не знаю, как его обрабатывать в конвейере/структуре файла Rails, поэтому я помещаю свой javascript_pack_tag в html head. И действительно, это работает хорошо.

На этот раз код выполняется повсюду, поэтому имеет смысл использовать элемент, специфичный для страницы (как ранее предполагалось в прокомментированном коде).

Недостатком является то, что на этот раз код может быть беспорядочным, если я не помещаю весь код, зависящий от страницы, внутри операторов if, которые проверяют наличие определенного для страницы элемента.
Однако, поскольку Rails/Webpack имеет хорошую структуру кода, он должен легко управляться, чтобы поместить код, специфичный для страницы, в файлы jsx для страницы.

Тем не менее, преимущество заключается в том, что на этот раз все части, относящиеся к странице, будут отображаться одновременно с всей страницей, что позволит избежать сбоя отображения, который встречается иначе.

Я не рассматривал эту проблему в первую очередь, но, действительно, хотел бы знать, как получить содержимое страницы, отображаемое одновременно с всей страницей.
Я не знаю, возможно ли это при объединении Turbolink с React (или любой другой структурой).


Но в заключение я оставляю этот вопрос позже.

Спасибо за ваш вклад Dom..

Ещё вопросы

Сообщество Overcoder
Наверх
Меню