为什么我不能将 Clojurescript 函数作为回调传递给 Javascript?

问题描述 投票:0回答:4

我正在尝试在 Clojurescript/Reagent SPA 中使用 Google 的 recaptcha,如下面的代码所示。

(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 找不到用户提供的函数: myapp.captcha.data_callback

另一方面,如果我从浏览器的调试器控制台调用 myapp.captcha.data_callback,则该函数可见并正确执行。

PS:现在请忽略全局chan,这是另一回事。为了解决这个问题,我必须显式调用验证码渲染,这使我陷入了一些显然与脚本加载顺序相关的竞争条件。我承认这可能是一种更干净的方法,但现在看看这里的问题是什么很有趣。

javascript recaptcha clojurescript
4个回答
1
投票

这是因为闭包编译器在编译期间混淆了您的代码,包括重命名您的函数。最简单的解决方案是首先确保编译器优化尊重您的函数名称(或者简单地禁用优化,例如通过带有shadow cljs的

:optimization :none

接下来,您要确保导出要使用的函数。这是通过

^:export
完成的,例如:
(defn ^:export my-exported-fun [] ...)

最后,引用函数时传递完整的命名空间,如

myapp.frontend.my-exported-fun

希望这对未来的旅行者有帮助:)


1
投票

我有一个解决方法,但有点麻烦。

我在 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)})))

1
投票

我的情况与您相同,只能按照您列出的方式解决它 - 通过提供将函数调用传递给 CLJS 的 JS 脚本。

但是,我发现如果你将回调函数定义为对象属性(在 JS 中),Recaptcha 仍然无法找到该函数,即使该函数确实存在。

我在我的

<head>
index.html

中定义了一个脚本标签
<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 回调。

希望这有帮助!


0
投票

我刚刚遇到了这个问题并解决了如下。

首先我想确认尼玛的解决方案虽然通常是有用的建议,但在这种特定情况下不起作用。正如 Rory How 所正确观察到的,您根本无法将作为对象属性的函数(我们在 ClojureScript 中定义的任何 var 都是)传递给 Google reCaptcha API。

我的解决方案是采用回调函数并为您的函数设置一个 JavaScript 全局变量。在代码中,它看起来像这样:

(defn data-callback [token]
 ,,,)

(set! js/my-data-callback data-callback)

然后在您的元素中,将“data-callback”属性的值设置为“my-data-callback”而不是“myapp.captcha.data_callback”。这适用于所有优化模式。

© www.soinside.com 2019 - 2024. All rights reserved.