在我正在构建的小应用程序中使用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"]
;...
])))
我尝试changing my app so that the methods generated the pages directly,如:
(defmethod layout/pages :register [_]
(let [registration-form (re-frame/subscribe [:registration-form])]
(fn []
[:div
[:h1 "Register"]
;...
])))
并且没有导致任何页面被渲染。在我的主要面板中changed the call to pages
to square brackets,以便Reagent能够看到它:
(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
Update: corrected the arity of the methods following Michał Marczyk's answer.
答案 0 :(得分:13)
所以,像这样的组件:[pages @some-ratom]
会在pages
更改或@some-ratom
更改时重新呈现。
从试剂的角度来看,pages
自上次以来没有变化,它仍然是以前的多方法。但@some-ratom
可能会发生变化,因此可能会触发重新渲染。
但当此重新渲染发生时,将使用pages
的缓存版本完成。毕竟,pages
已发生变化的试剂似乎没有。它仍然是以前的多方法。
pages
的缓存版本当然是pages
的第一个版本,它是第一个版本的mutlimethod和不是新版本我们期待看到二手。
Reagent执行此缓存,因为它必须处理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
答案 1 :(得分:4)
我没有掌握所有细节,但显然,当我渲染这样的网页时:
[:main.container
[alerts/view]
[pages @current-route]]
Reagent没有注意到pages
取决于@current-route
的值。 Chrome React plugin让我明白了。我尝试使用ratom而不是订阅,这似乎工作正常。谢天谢地,telling Reagent/React the key to an element is easy enough:
[:main.container
[alerts/view]
^{:key @current-route} [pages @current-route]]
这很好用。
答案 2 :(得分:2)
跳出来的第一个问题是你的方法没有参数:
(defmethod layout/pages :register [] [register-page])
^ arglist
这里有一个空的arglist,但可能你会用一个或两个参数调用这个多方法(因为它的dispatch函数是一个关键字,关键字可以用一个或两个参数调用)。
如果你想用一个参数调用这个多方法,只需在:register
方法的主体内忽略它,将上面的内容改为
(defmethod layout/pages :register [_] [register-page])
^ argument to be ignored
此外,我希望您可能想要像以前一样自己致电pages
(也就是说,将更改恢复为您在问题中提到的方括号)。
这可能会也可能不会修复应用程序 - 可能还有其他问题 - 但它应该让你开始。 (如果传入任何参数,多方法肯定不适用于那些空的arglists。)
答案 3 :(得分:0)
如果你有一个包装{{1}}函数,这是一个可以被试剂缓存的常规函数,那该怎么办?它看起来像这样:
{{1}}