У меня очень простое приложение 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
И вот следующие шаги, чтобы воспроизвести описанное поведение:
http://localhost:3000/items
localhost:3000/items/1
. Здесь мы видим Hello React! сообщение. Он работает хорошо!http://localhost:3000/items
. Элемент отображается как ожидалось.http://localhost:3000/items/1
. Сообщение Hello отображается как ожидается с помощью одного сообщения консоли.http://localhost:3000/items
Оттуда каждый раз, когда я возвращаюсь к индексу и снова возвращаюсь на страницу показа, каждый раз отображается еще два сообщения на консоли.
Примечание. Вместо использования (и замены) конкретного элемента div, если я разрешаю исходный файл hello_react
, который добавляет элемент div, это поведение становится еще более заметным.
Edit: Кроме того, если я изменяю ссылки link_to
, включив data: {turbolinks: false}
. Это работает хорошо. Так же, как мы загрузили страницы, используя URL-адреса в адресной строке браузера.
Я не знаю, что я делаю неправильно.
Любые идеи?
Изменить: я поставил код в следующем репо, если вам интересно попробовать: https://github.com/sanjibukai/react-turbolinks-test
Это довольно сложная проблема, и я боюсь, что я не думаю, что она имеет прямой ответ. Я объясню, насколько это возможно.
- Чтобы запустить код по первому запросу страницы показа
Ваш обработчик событий turbolinks:load
не запущен, потому что ваш код запускается после запуска события turbolinks:load
. Вот поток:
turbolinks:load
срабатываетТаким образом, обработчик события 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
получит компонент.
Надеюсь, что это поможет!
jsx
и я не могу добавить его в файл манифеста application.js . Каков наилучший способ сделать это?
<%= javascript_pack_tag 'application' %>
в макете приложения head
, а затем import './hello_react'
в приложении / JavaScript / пакетах / приложении. Как я уже сказал, я не являюсь экспертом по стратегии веб-пакетов, поэтому я не буду приводить эту часть своего ответа, но надеюсь, что это поможет вам начать работу.
После того, что вы сказали, я проверил некоторый код.
Во-первых, я просто вытащил метод 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..