javascript - clojurescript 相当于 javascript 的模块模式

标签 javascript clojurescript

我刚刚开始在 node-js 端使用 clojurescript。我用它来构建将在节点上运行的命令行工具。 现在我已经设置了我的概念验证并且或多或少地做了我想做的事情,是时候更好地组织代码了。

在 JS 上,当我需要类似于已配置的 http 客户端的东西时,我通常会导出一个函数,该函数接受基本参数并返回一个对象,该对象具有绑定(bind)到该参数的方法(通常使用显示模块模式)。类似于在 OOP 上创建新实例的东西。 这是我如何在 JS 上执行此操作的一个小示例:

const request = require('request')
module.exports = (user, pass, baseUrl) => {
   const client = request.defaults({baseUrl, auth: {user, pass}})

   const getSomething = (name) => client.get('resources/' + name)
   const createSomething = (name, options) => client.post('resources', {...})
   return { getSomething, createSomething }
}

但是在 clojurescript 上我找不到合适的方法。所有定义都是在编译时计算的顶级声明,并且制作上述结构需要使用客户端参数声明我的所有函数,然后部分应用它们并在逻辑主体上使用它们。这可能是这样的:

(ns some-client [:require ["request" :as request]])
(defn get-something [client, name] 
    (.get client (str "resources/" name)))
(defn create-something [client, name, options] 
    (.post client (str "resources") {:name name :data: options}))

(defn make-client [usr, pass, baseUrl] 
    (let [client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})]
        {:get-something (partial get-something client)
         :create-something (partial create-something client)}))

这可能看起来还不错,但是一旦您需要在另一个所有功能都需要此类客户端的地方使用它,事情就会开始变得困惑。您将需要在所有功能上接受客户端,如果其他命名空间只是您需要在另一个地方使用的功能集合,您将被迫遵循返回客户端创建者的相同模式,接受客户端您依赖并确保将其传递给可能需要它的每个功能。我可以变得像这样可怕:

(ns other-helper)

(defn trivial-stuff [client name bla] 
    (let [get-something (client :get-something)]
     (get-something name))) ; make things like filtering and that


(defn more-trivial-stuff [client name bla] 
    (let [get-something (client :get-something)])
    (get-something name)) ; make things like filtering and that


(defn non-trivial-stuff [client name bla]
  (->>
    (trivial-stuff client name bla)
    (more-trivial-stuff client name)))

(defn more-non-trivial-stuff [client name bla]
    (->>
        (trivial-stuff client name bla)
        (more-trivial-stuff client name)))

(defn compile-utils [client]
 {:more-non-trivial (partial more-non-trivial-stuff client)
  :non-trivial (partial non-trivial-stuff client)})

我无法为客户端制作任何 def,因为我将在运行时需要凭据,所以我必须接受所有这些东西作为参数并绑定(bind)结果,对我来说这看起来像大量样板代码和重复代码根本无法维护。

clojurians 有更好的方法吗?在这方面有任何风格指南吗? 这是我第二次接触 clojurescript,起初它看起来非常吸引人,但一旦你开始构建重要的东西,它就会开始变得困惑。

注意:为了简单起见,我没有管理任何 js 互操作或使用 channel 进行异步处理。我只是将 js 对象声明为普通的 cljs 映射并将所有内容都视为同步的,但是包括 js 互操作和所有这些东西会使事情变得更糟。

编辑(澄清):

我的问题不是这在 clojure 上是否可行,我知道这是可能的,因为 CLJS 和 JS 共享所需的功能集以使其成为可能。然而,在完全不同的语言上使用相同的模式不仅感觉不对,而且由于 lisp 语法,它看起来也很难看。我能想到的其他替代方案看起来也很丑陋,并且涉及大量重复,因为它需要让客户使用它并在每个函数上传递它,这会导致非常重复和分散注意力的代码。

为了弄清楚我将如何在 js 上使用它,它会像这样

const makeClient = require('./my-http')
const client = makeClient('my-user','my-pass','http://google.es')
client.getSomething('dude')

如您所见,我可以根据需要创建具有不同设置的客户端,我什至可以进行一些解构并仅选择我需要的方法,因为它们根本不依赖于它们的绑定(bind)。

最佳答案

注意:我还没有“愤怒地”使用过 Clojure/Script,所以这对我来说也是一种学习经历 :) 不幸的是,我还没有在 REPL 中验证代码。

如果我没理解错的话,JS模块模式就是一个函数,返回两个函数的字典。在你的代码中的某个时刻,你“创建”了这个模块,也许给它一个名字,然后你在你的代码中传递它,就像这样:

let client = require("mymodule")("user", "pass", "http://example.com");
client.getSomething("foo")

您可以使用 ClojureScript 做同样的事情:

 (defn create-client [user pass base]
    (let [get-something (fn [name] ...)
          create-something (fn [name other] ...)]
       {:get get-something :create create-something}))

 (let [client (create-client "user" "pass" "http://example.com")]
    ((client :get) "foo"))

现在可以说这可能看起来有点笨拙,但它是完全相同的代码:关闭几个变量,将两个函数粘贴在一个映射中,从映射中获取一个函数并调用它。


你的问题的第二部分看起来是关于全局状态的——你必须随身携带 client 对象,而且感觉很笨重。我不认为它在 Javascript 中看起来更好?

let client = require("mymodule")("user", "pass", "http://example.com");

let trivialStuff = (client, name, blah) => { client.getSomething(name); ... };

let moreTrivialStuff = (client, name, blah) => { client.getSomething(name); ... };        

let nonTrivialStuff = (client, name, blah) => {

   let result = trivialStuff(client, name, blah)
   return moreTrivialStuff(client, name, result)
}

即您仍在传递 client。您可以在初始化后使其成为模块级变量,但随后您将失去在运行时创建两个不同客户端的能力。

您是说使用揭示模块模式还会公开 nonTrivialStuff,因此您可以执行 client.nonTrivialStuff()


如何创建一个 namespace ,其中包含所有需要client 的函数(它可能只是一个包含 JS requests 客户端的普通映射),并直接使用它们?

例如

(ns some-client [:require ["request" :as request]])

(defn make-client [usr pass base-url] 
  {:client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})}) ;; you might want to use #js here, since you usually cannot pass CLJS maps to JS directly

(defn get-something [client name] 
  (.get (client :client) (str "resources/" name)))

(defn create-something [client name options] 
  (.post (client :client) (str "resources") {:name name :data options}))

然后在其他命名空间中:

(ns other-code [:require [some-client :as client]])

(def c (atom nil)) 

;; get user and pass from runtime, then call
(reset! c (client/make-client user pass base-url))

;; use like so
(client/get-something @c "name")

我选择将 JS 客户端对象放入 CLJS 映射中以实现灵 active ——将来您可能希望将更多数据添加到该映射中。当然,客户端代码不会更改,因为它应该将其视为不透明值。

关于javascript - clojurescript 相当于 javascript 的模块模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49611955/

相关文章:

macros - 如何处理来自 Clojurescript 宏所需的 Clojurescript 代码?

javascript - 将 this 绑定(bind)到 React 中的嵌套函数

javascript - jQuery.each 中的反转对象

clojure - 为什么 Clojure 不允许我们无序地编写函数?

clojure - 在 Clojure 中重新定义变量(脚本)

ClojureScript bRepl 根本不响应

javascript - 如何在多个文件中组织 JS 函数

javascript - d3 - mouseover 和 mouseout 事件无法正常工作

javascript - 模拟 onChange 事件在 HTML 输入框上触发

javascript - 如何使用 clojurescript 或 om-next 在 dom 元素上切换类