为什么多重方法不能作为 Reagent/Re-frame 的函数使用?
Why are multi-methods not working as functions for 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"]
;...
])))
我试过 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
的结果(当然必须使用显式调用而不是方括号)。包装函数,有效的函数,returns:
[#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);
});
}"]]
而方法 returns:
#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."])])
也就是把函数返回一个vector,然后,就开始工作了。如果我对包装函数进行反向更改,它会以与方法相同的方式开始失败:
(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.
我突然想到的第一个问题是你的方法没有参数:
(defmethod layout/pages :register [] [register-page])
^ arglist
这里你有一个空的 arglist,但你可能会用一个或两个参数调用这个多方法(因为它的调度函数是一个关键字,关键字可以用一个或两个参数调用)。
如果你想用一个参数调用这个多重方法并且只是在 :register
方法的主体中忽略它,将上面的更改为
(defmethod layout/pages :register [_] [register-page])
^ argument to be ignored
此外,我预计您可能会像以前那样自己调用 pages
(也就是说,将更改恢复为您在问题中提到的方括号)。
这可能会或可能不会修复应用程序 – 可能还有其他问题 – 但它应该让您开始。 (如果你传入任何参数,那么 multimethod 肯定不会使用那些空的 arglists。)
我不了解所有细节,但显然,当我呈现这样的页面时:
[:main.container
[alerts/view]
[pages @current-route]]
试剂未能注意到 pages
取决于 @current-route
的值。 Chrome React plugin helped me figure it out. I tried using a ratom instead of a subscription and that seemed to work fine. Thankfully, telling Reagent/React the key to an element is easy enough:
[:main.container
[alerts/view]
^{:key @current-route} [pages @current-route]]
效果很好。
所以,像这样的组件:[pages @some-ratom]
当 pages
更改或 @some-ratom
更改时将重新呈现。
从试剂的角度来看,pages
和上次没有变化,还是和之前一样的多法。但是 @some-ratom
可能会发生变化,因此可能会触发重新渲染。
但是 发生此重新渲染时,将使用 pages
的缓存版本完成。毕竟,在试剂看来,pages
并没有发生变化。它仍然是以前的多方法。
当然,pages
的缓存版本将是呈现的 pages
的第一个版本 - mutlimethod 的第一个版本 not 我们期望看到使用的新版本。
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
如果你有一个包装器 pages-component
函数,它是一个可以被试剂缓存的常规函数,那会怎样呢?它看起来像这样:
(defn pages-component [state]
(layout/pages @state))
在我构建的一个使用 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
的结果(当然必须使用显式调用而不是方括号)。包装函数,有效的函数,returns:
[#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);
});
}"]]
而方法 returns:
#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."])])
也就是把函数返回一个vector,然后,就开始工作了。如果我对包装函数进行反向更改,它会以与方法相同的方式开始失败:
(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.
我突然想到的第一个问题是你的方法没有参数:
(defmethod layout/pages :register [] [register-page])
^ arglist
这里你有一个空的 arglist,但你可能会用一个或两个参数调用这个多方法(因为它的调度函数是一个关键字,关键字可以用一个或两个参数调用)。
如果你想用一个参数调用这个多重方法并且只是在 :register
方法的主体中忽略它,将上面的更改为
(defmethod layout/pages :register [_] [register-page])
^ argument to be ignored
此外,我预计您可能会像以前那样自己调用 pages
(也就是说,将更改恢复为您在问题中提到的方括号)。
这可能会或可能不会修复应用程序 – 可能还有其他问题 – 但它应该让您开始。 (如果你传入任何参数,那么 multimethod 肯定不会使用那些空的 arglists。)
我不了解所有细节,但显然,当我呈现这样的页面时:
[:main.container
[alerts/view]
[pages @current-route]]
试剂未能注意到 pages
取决于 @current-route
的值。 Chrome React plugin helped me figure it out. I tried using a ratom instead of a subscription and that seemed to work fine. Thankfully, telling Reagent/React the key to an element is easy enough:
[:main.container
[alerts/view]
^{:key @current-route} [pages @current-route]]
效果很好。
所以,像这样的组件:[pages @some-ratom]
当 pages
更改或 @some-ratom
更改时将重新呈现。
从试剂的角度来看,pages
和上次没有变化,还是和之前一样的多法。但是 @some-ratom
可能会发生变化,因此可能会触发重新渲染。
但是 发生此重新渲染时,将使用 pages
的缓存版本完成。毕竟,在试剂看来,pages
并没有发生变化。它仍然是以前的多方法。
当然,pages
的缓存版本将是呈现的 pages
的第一个版本 - mutlimethod 的第一个版本 not 我们期望看到使用的新版本。
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
如果你有一个包装器 pages-component
函数,它是一个可以被试剂缓存的常规函数,那会怎样呢?它看起来像这样:
(defn pages-component [state]
(layout/pages @state))