为什么我不能将 Clojurescript 函数作为回调传递给 Javascript?
Why can't I pass Clojurescript functions as callbacks to Javascript?
我正在尝试在 Clojurescript/Reagent SPA 中使用 Google 的验证码,如下面的代码所示。
(ns myapp.captcha
(:require [reagent.core :as r]
[cljs.core.async :refer [<! >! chan]])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]))
(def captcha-ch (chan))
(defn ^:export data-callback [human-proof]
(go (>! captcha-ch {:captcha-data human-proof})))
(defn ^:export data-expired-callback []
(go (>! captcha-ch {:captcha-expired true})))
(defn captcha [site-key]
(let [grecaptcha-script (doto (.createElement js/document "script")
(.setAttribute "id" "grecaptcha-script")
(.setAttribute "src" "https://www.google.com/recaptcha/api.js"))
out-ch (chan)
comp (r/create-class
{:component-did-mount (fn [this]
(.appendChild (.-body js/document)
grecaptcha-script))
:component-will-unmount (fn [this]
(.removeChild (.-body js/document)
(.getElementById js/document "grecaptcha-script"))
(go (>! captcha-ch {:exit true})))
:reagent-render (fn [this]
[:div.g-recaptcha
{:data-sitekey site-key
:data-callback "myapp.captcha.data_callback"
:data-expired-callback "myapp.captcha.data_expired_callback"}])})]
(go-loop []
(let [msg (<! captcha-ch)]
(if-not (:exit msg)
(>! out-ch msg)
(recur))))
{:chan out-ch :comp comp}))
当验证码被解决并且应该调用数据回调时,我收到一条错误消息:
ReCAPTCHA couldn't find user-provided function:
myapp.captcha.data_callback
另一方面,如果我从浏览器的调试器控制台调用 myapp.captcha.data_callback,该函数是可见的并正确执行。
PS:暂时请忽略global chan,这是另外一回事。为了解决这个问题,我必须显式调用验证码渲染,这使我处于某些显然与脚本加载顺序相关的竞争条件中。我承认这可能是一种更简洁的方法,但现在看看这里的问题是什么很有趣。
我有一个解决方法,但有点麻烦。
我在 recaptcha 脚本之前添加了一个脚本元素。在这个脚本元素中,我定义了将调用转发到我的 clojurescript 函数的回调。
查看下面的代码。
如果能理解为什么我不能直接使用我的 Clojurescript 回调会更好。
(defn captcha [handler]
(let [callback-hooks (let [s (.createElement js/document "script")]
(.setAttribute s "id" "captcha-callbacks")
(set! (.-text s)
(str "var captcha_data_callback = function(x) { myapp.captcha.data_callback(x)};"
"var captcha_data_expired_callback = function() { myapp.captcha.data_expired_callback()};"))
s)
grecaptcha-script (doto (.createElement js/document "script")
(.setAttribute "id" "grecaptcha-script")
(.setAttribute "src" "https://www.google.com/recaptcha/api.js"))
captcha-div [:div.g-recaptcha
{:data-sitekey config/grecaptcha-client-key
:data-callback "captcha_data_callback"
:data-expired-callback "captcha_data_expired_callback"}]]
(go-loop []
(let [msg (<! captcha-ch)]
(handler msg)
(if-not (:end msg)
(recur))))
(r/create-class
{:component-did-mount (fn [this]
(doto (.-body js/document)
(.appendChild callback-hooks)
(.appendChild grecaptcha-script)))
:component-will-unmount (fn [this]
(doto (.-body js/document)
(.removeChild (.getElementById js/document "captcha-callbacks"))
(.removeChild (.getElementById js/document "grecaptcha-script")))
(go (>! captcha-ch {:end true})))
:reagent-render (fn [this] captcha-div)})))
我和你的情况一样,只能按照你列出的方式解决它——通过提供一个将函数调用传递给 CLJS 的 JS 脚本。
但是,我发现如果将回调函数定义为对象 属性(在 JS 中)Recaptcha 仍然无法找到该函数,即使该函数确实存在。
我在 index.html
的 <head>
中定义了一个脚本标签
<script type="text/javascript">
// This function will be called correctly when the captcha is loaded
var onloadCallback = function() { myapp.captcha.onloadCallback() };
// This function will not be found by recaptcha.js
var testObject = { onDataCallback: function(x) { myapp.captcha.onDataCallback(x) };
</script>
考虑到这一点,问题似乎不是 ClojureScript 问题,而是 recaptcha 问题。如果有一种方法可以将 ClojureScript 函数直接导出到其命名空间之外的全局范围(我不确定这是否可能),那么理论上您应该能够直接从 recaptcha 访问您的 CLJS 回调。
希望对您有所帮助!
这是因为 Closure 编译器在编译期间混淆了您的代码,包括重命名您的函数。最简单的解决方案是首先确保编译器优化尊重您的函数名称(或者简单地禁用优化,例如通过 :optimization :none
with shadow cljs.
接下来,您要确保导出要使用的函数。这是用 ^:export
完成的,例如:
(defn ^:export my-exported-fun [] ...)
最后,在引用函数时传递完整的命名空间,例如myapp.frontend.my-exported-fun
。
希望这对未来的旅行者有帮助:)
我正在尝试在 Clojurescript/Reagent SPA 中使用 Google 的验证码,如下面的代码所示。
(ns myapp.captcha
(:require [reagent.core :as r]
[cljs.core.async :refer [<! >! chan]])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]))
(def captcha-ch (chan))
(defn ^:export data-callback [human-proof]
(go (>! captcha-ch {:captcha-data human-proof})))
(defn ^:export data-expired-callback []
(go (>! captcha-ch {:captcha-expired true})))
(defn captcha [site-key]
(let [grecaptcha-script (doto (.createElement js/document "script")
(.setAttribute "id" "grecaptcha-script")
(.setAttribute "src" "https://www.google.com/recaptcha/api.js"))
out-ch (chan)
comp (r/create-class
{:component-did-mount (fn [this]
(.appendChild (.-body js/document)
grecaptcha-script))
:component-will-unmount (fn [this]
(.removeChild (.-body js/document)
(.getElementById js/document "grecaptcha-script"))
(go (>! captcha-ch {:exit true})))
:reagent-render (fn [this]
[:div.g-recaptcha
{:data-sitekey site-key
:data-callback "myapp.captcha.data_callback"
:data-expired-callback "myapp.captcha.data_expired_callback"}])})]
(go-loop []
(let [msg (<! captcha-ch)]
(if-not (:exit msg)
(>! out-ch msg)
(recur))))
{:chan out-ch :comp comp}))
当验证码被解决并且应该调用数据回调时,我收到一条错误消息:
ReCAPTCHA couldn't find user-provided function: myapp.captcha.data_callback
另一方面,如果我从浏览器的调试器控制台调用 myapp.captcha.data_callback,该函数是可见的并正确执行。
PS:暂时请忽略global chan,这是另外一回事。为了解决这个问题,我必须显式调用验证码渲染,这使我处于某些显然与脚本加载顺序相关的竞争条件中。我承认这可能是一种更简洁的方法,但现在看看这里的问题是什么很有趣。
我有一个解决方法,但有点麻烦。
我在 recaptcha 脚本之前添加了一个脚本元素。在这个脚本元素中,我定义了将调用转发到我的 clojurescript 函数的回调。
查看下面的代码。
如果能理解为什么我不能直接使用我的 Clojurescript 回调会更好。
(defn captcha [handler]
(let [callback-hooks (let [s (.createElement js/document "script")]
(.setAttribute s "id" "captcha-callbacks")
(set! (.-text s)
(str "var captcha_data_callback = function(x) { myapp.captcha.data_callback(x)};"
"var captcha_data_expired_callback = function() { myapp.captcha.data_expired_callback()};"))
s)
grecaptcha-script (doto (.createElement js/document "script")
(.setAttribute "id" "grecaptcha-script")
(.setAttribute "src" "https://www.google.com/recaptcha/api.js"))
captcha-div [:div.g-recaptcha
{:data-sitekey config/grecaptcha-client-key
:data-callback "captcha_data_callback"
:data-expired-callback "captcha_data_expired_callback"}]]
(go-loop []
(let [msg (<! captcha-ch)]
(handler msg)
(if-not (:end msg)
(recur))))
(r/create-class
{:component-did-mount (fn [this]
(doto (.-body js/document)
(.appendChild callback-hooks)
(.appendChild grecaptcha-script)))
:component-will-unmount (fn [this]
(doto (.-body js/document)
(.removeChild (.getElementById js/document "captcha-callbacks"))
(.removeChild (.getElementById js/document "grecaptcha-script")))
(go (>! captcha-ch {:end true})))
:reagent-render (fn [this] captcha-div)})))
我和你的情况一样,只能按照你列出的方式解决它——通过提供一个将函数调用传递给 CLJS 的 JS 脚本。
但是,我发现如果将回调函数定义为对象 属性(在 JS 中)Recaptcha 仍然无法找到该函数,即使该函数确实存在。
我在 index.html
<head>
中定义了一个脚本标签
<script type="text/javascript">
// This function will be called correctly when the captcha is loaded
var onloadCallback = function() { myapp.captcha.onloadCallback() };
// This function will not be found by recaptcha.js
var testObject = { onDataCallback: function(x) { myapp.captcha.onDataCallback(x) };
</script>
考虑到这一点,问题似乎不是 ClojureScript 问题,而是 recaptcha 问题。如果有一种方法可以将 ClojureScript 函数直接导出到其命名空间之外的全局范围(我不确定这是否可能),那么理论上您应该能够直接从 recaptcha 访问您的 CLJS 回调。
希望对您有所帮助!
这是因为 Closure 编译器在编译期间混淆了您的代码,包括重命名您的函数。最简单的解决方案是首先确保编译器优化尊重您的函数名称(或者简单地禁用优化,例如通过 :optimization :none
with shadow cljs.
接下来,您要确保导出要使用的函数。这是用 ^:export
完成的,例如:
(defn ^:export my-exported-fun [] ...)
最后,在引用函数时传递完整的命名空间,例如myapp.frontend.my-exported-fun
。
希望这对未来的旅行者有帮助:)