Подтвердить что ты не робот

Почему мульти-методы не работают как функции для Reagent/Re-frame?

В небольшом приложении, которое я создаю для использования Reagent и Re-frame, я использую несколько методов для отправки страницы, которая должна отображаться на основе значения в состоянии приложения:

(defmulti pages :name)

(defn main-panel []
  (let [current-route (re-frame/subscribe [:current-route])]
    (fn []
      ;...
      (pages @current-route))))

а затем у меня есть такие методы, как:

(defmethod layout/pages :register [_] [register-page])

где функция register-page генерирует фактический вид:

(defn register-page []
  (let [registration-form (re-frame/subscribe [:registration-form])]
    (fn []
      [:div
       [:h1 "Register"]
       ;...
       ])))

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

(defmethod layout/pages :register [_]
  (let [registration-form (re-frame/subscribe [:registration-form])]
    (fn []
      [:div
       [:h1 "Register"]
       ;...
       ])))

и это не вызвало никакой страницы. В моей основной панели я изменил вызов на pages на квадратные скобки, чтобы Реагент мог видеть его:

(defn main-panel []
  (let [current-route (re-frame/subscribe [:current-route])]
    (fn []
      ;...
      [pages @current-route])))

и это заставило первую посещаемую страницу работать, но после этого щелчок по ссылкам (что вызывает изменение текущего маршрута) не имеет эффекта.

Все пространства имен, определяющие отдельные методы, требуются в первом загруженном файле, который содержит функцию init, и тот факт, что я могу выбрать любую отдельную страницу и показать ее, доказывает, что код загружается (затем, переключаясь на другая страница не работает):

https://github.com/carouselapps/ninjatools/blob/master/src/cljs/ninjatools/core.cljs#L8-L12

В попытке отладить то, что происходит, я определил два маршрута: :about и :about2, один как функцию и один как метод:

(defn about-page []
  (fn []
    [:div "This is the About Page."]))

(defmethod layout/pages :about [_]
  [about-page])

(defmethod layout/pages :about2 [_]
  (fn []
    [:div "This is the About 2 Page."]))

и сделал макет распечатать результат вызова pages (должен был использовать явный вызов вместо квадратных скобок, конечно). Обернутая функция, которая работает, возвращает:

[#object[ninjatools$pages$about_page "function ninjatools$pages$about_page(){
return (function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About Page."], null);
});
}"]]

в то время как метод возвращает:

#object[Function "function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About 2 Page."], null);
}"]

Если изменить метод:

(defmethod layout/pages :about2 [_]
  [(fn []
     [:div "This is the About 2 Page."])])

то есть, возвращая функцию в вектор, тогда он начинает работать. И если я делаю обратное изменение обернутой функции, он начинает терпеть неудачу так же, как метод:

(defn about-page []
  (fn []
    [:div "This is the About Page."]))

(defmethod layout/pages :about [_]
  about-page)

Имеет немного смысла, поскольку синтаксис Reagent [function], но он должен был автоматически вызвать функцию.

Я также начал выводить @current-route в браузер, как в:

[:main.container
 [alerts/view]
 [pages @current-route]
 [:div (pr-str @current-route)]]

и я проверил @current-route, и он обновляется правильно, а не [pages @current-route].

Полный исходный код для моего приложения можно найти здесь: https://github.com/carouselapps/ninjatools/tree/multi-methods

Обновление: исправлена ​​ясность методов, следующих за ответом Michał Marczyk.

4b9b3361

Ответ 1

У меня нет всех подробностей, но, видимо, когда я делал такие страницы:

[:main.container
 [alerts/view]
 [pages @current-route]]

Реагент не заметил, что pages зависит от значения @current-route. плагин Chrome React помог мне понять это. Я попытался использовать ratom вместо подписки, и это, казалось, сработало. К счастью, говорит Reagent/React ключ к элементу достаточно легко:

[:main.container
 [alerts/view]
 ^{:key @current-route} [pages @current-route]]

Это работает отлично.

Ответ 2

Итак, компонент вроде этого: [pages @some-ratom]
будет изменяться при изменении pages или @some-ratom.

С точки зрения реагентов, pages не изменился с последнего времени, это все тот же самый многомерный метод, который был раньше. Но @some-ratom может измениться, так что это может вызвать перезагрузку.

Но, когда это происходит, это будет сделано с использованием кешированной версии pages. В конце концов, он не кажется реагенту, что pages изменился. Это все тот же самый мультиметод, который был раньше.

Кэш-версия pages, конечно же, будет первой версией pages, которая была отображена - первая версия mutlimethod и not новая версия, которую мы ожидаем увидеть используемой.

Реагент выполняет это кэширование, потому что он должен обрабатывать функции Form-2. Он должен сохранить возвращаемую функцию рендеринга.

Нижняя строка: из-за кэширования многоточечные методы не будут работать очень хорошо, если только вы не найдете способ полностью взорвать компонент и начать заново, что и происходит в настоящее время:  ^{:key @current-route} [pages @current-route]
Разумеется, взорвать компонент и начать снова может иметь свои собственные нежелательные последствия (в зависимости от того, какое локальное состояние удерживается в этом компоненте).

Смутно родственный исход:
https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#appendix-a---lifting-the-lid-slightly
https://github.com/Day8/re-frame/wiki/When-do-components-update%3F

Ответ 3

Первая проблема, которая выпрыгивает на меня, заключается в том, что ваши методы не принимают аргументов:

(defmethod layout/pages :register [] [register-page])
                                  ^ arglist

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

Если вы хотите вызвать этот мультиметод одним аргументом и просто игнорировать его внутри тела метода :register, измените приведенное выше на

(defmethod layout/pages :register [_] [register-page])
                                   ^ argument to be ignored

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


Это может или не может исправить приложение - могут быть и другие проблемы, но он должен начать вас. (Многомерный метод определенно не будет работать с этими пустыми архлистами, если вы передадите любые аргументы.)

Ответ 4

Как насчет того, есть ли у вас функция-обертка pages-component, которая является регулярной функцией, которую можно кэшировать с помощью реагента. Это будет выглядеть так:

(defn pages-component [state]
  (layout/pages @state))