Почему мульти-методы не работают как функции для 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.
Ответы
Ответ 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))