在本文中,我们简要介绍了使用 VisualStudio Code、Shadow-cljs、Reagent 和 Clava 进行 ClojureScript 编码的基础知识。每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。我喜欢 ClojureScript。当我输入代码来制作我的 React 组件时的语言、外观和感觉让我彻夜难眠。实际上,昨天当我尝试找一个朋友建立并回到现代环境进行编码时,它确实做到了。
让我们通过前几个步骤在ClojureScript中使用 React 计数器进行设置,所有这些都通过从VSCode重新加载实时代码。
大多数 Clojure 人员都是资深的编码人员,并且大多使用 emacs 来完成他们的工作。
我将介绍一个使用 VSCode 的设置以及用于轻松编码的最小设置。
使用 Clava 设置 VSCode
Clava 需要在你的机器上安装clojure-lsp,安装过程在此处描述。
作为旁注,为了使事情顺利(并避免这种情况),我必须全局安装npx:
npm install -g npx --force
使用 Shadow-cljs 设置新项目
Shadow CLJS 用户指南将是此设置的核心。感谢我亲爱的朋友 Tokoma,我昨天才拿起它,我喜欢它的速度、易用性以及与标准 npm 世界的完全集成。
假设您安装了npx,如果没有,让我们这样做。
npm install -g npx
我们将首先以与shadow-cljs中的教程相同的方式创建一个新的 acme 应用程序:
npx create-cljs-project acme-app
我有点吃惊;这个新生成的应用程序带有默认设置,但是:
没有构建指令
没有任何 ClojureScript 代码
因此,您确实已经准备好运行REPL :
npx shadow-cljs node-repl
# or
npx shadow-cljs browser-repl
但其他不多。我们所拥有的是一组具有以下结构的文件(仅保留重要文件):
├── package.json
├── package-lock.json
├── shadow-cljs.edn
└── src
├── main
└── test
来自标准nodejs世界的文件 package.json 仅包含对 ShadowCLJS 的 Javascript 部分的开发依赖项:
{
"name": "bunny-app",
"version": "0.0.1",
"private": true,
"devDependencies": {
"shadow-cljs": "2.20.1"
}
}
第二个重要的文件是 shadow-cljs.edn 文件,它是一个基于 shadow 使用的配置的EDN来发挥它的魔力,在生成时,它非常简单:
;; shadow-cljs configuration
{:source-paths
["src/dev"
"src/main"
"src/test"]
:dependencies
[]
:builds
{}}
因此,首先,我们要托管一个 index.html 文件来进行 JavaScript 编码。我们将:
把它放在 public/index.html 中,包含一些裸露的内容;
我们将添加对js main.js的引用。
main.js 是我们的 ClojureScript 代码将在几秒钟内生成的文件。
这是HTML 文件的源代码。
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>bunny frontend</title>
</head>
<body>
<div id="app"></div>
<script src="/js/main.js"></script>
</body>
</html>
我们需要一些 ClojureScript 代码才能开始,所以在文件中,src main acme frontend app.cljs:
mkdir -p src/main/bunny/frontend
touch src/main/bunny/frontend/app.cljs
让我们编写一些基本的 Clojure 代码:
(ns bunny.frontend.app)
(defn init []
(println "Hello Bunny"))
然后我们更新 shadow-cljs.edn 文件的 :builds 部分:
{:frontend
{:target :browser
:modules {:main {:init-fn acme.frontend.app/init}}
}}
有多个 :targets 可用:
支持各种目标 :browser, :node-script, :npm-module, :react-native, :chrome-extension, ...
我们暂时使用浏览器,待编译模块的函数就是我们上面刚刚写的ClojureScript文件中的init函数。
我们准备好了,让我们运行这个神奇的命令:
npx shadow-cljs watch frontend
看到frontend是我们在 shadow-cljs.edn 中编写的构建定义,所以请确保在上面的watch命令中使用它。
啊……但是我们需要一些东西来托管公共文件夹中的文件并通过 Web 服务器提供这些文件。
让我们回到 shadow-cljs.edn 文件,并添加:
{
;...
:dev-http {8080 "public"}
;...
}
然后我们可以前往:
http://localhost:8080
并打开浏览器控制台,看到我们的兔子出现在浏览器的控制台中:
打开浏览器控制台可以看到我们的兔子出现在浏览器的控制台中。
如果在保存 ClojureScript 文件时,我们可以动态地重新加载代码,那就太好了,其中一种方法是通过函数上的注释。查看^:dev before-load/ 和^:dev after-load/ 是如何被使用的。
(defn init []
(println "Hello Bunny"))
(defn ^:dev/before-load stop []
(js/console.log "stop"))
(defn ^:dev/after-load start []
(js/console.log "start")
(init))
现在保存 ClojureScript 文件,将自动重新加载由 init 函数触发的代码,因此我们现在在控制台中有两个兔子:
ClojureScript 文件将自动重新加载由 init 函数触发的代码,因此我们现在在控制台中有两只兔子。
一些原始 DOM 操作
稍后我们将看到如何使用 React,但现在,让我们看看如何简单地将兔子图片添加到我们的 HTML 文件中。
基本上,我们希望实现以下 JavaScript 代码的等价物:
const img = document.createElement ("img");
img.src = "/bunny-512.webp";
document.body.appendChild (img);
如果你在公共文件夹中有 bunny.webp 图片,下面的代码就可以工作:
(defn init []
(let [img
(doto (.createElement js/document "img")
(set! -src "/bunny-512.webp")
(set! -height 64)
(set! -width 64))]
(.appendChild (.getElementById js/document "app") img)))
恢复使用直接设置只是有点费力! 为了在dom 元素上设置属性,所以在一个单独的 bunny.frontend.utils 命名空间中,让我们创建两个方便的函数:
(ns bunny.frontend.utils)
(defn set-props [o property-map]
(doseq [[k v] property-map]
(.setAttribute o (name k) v)))
(defn create-el [tag-name property-map]
(let [el (.createElement js/document tag-name)]
(set-props el property-map)
el))
让我们使用主应用命名空间中的命名空间:
(ns bunny.frontend.app
(:require [acme.frontend.utils :as u]))
(defn init []
(println “Init”)
(let [img (u/create-el "img"
{:src "/bunny-512.webp" :height 128 :width 128})]
(.appendChild (.getElementById js/document "app") img)))
你应该看到一只漂亮的小兔子。
看到一只漂亮的小兔子。
当然,您可以使用属性映射中的 :height 和 :width 键,并在浏览器中看到更大或更小的新图像。
在浏览器中看到更大或更小的新图像。
做出反应
现在,直接操作 DOM 确实有点像用锤子敲鸡蛋。让我们看看我们如何使用 React,首先没有 ClojureScript 包装器,只需简单的 npm 包。
在撰写本文时,我后来在使用 React v18 Reagent 时遇到了一些故障,所以让我们在本文中坚持使用第 17 版:
npm install react@17.0.2 react-dom@17.0.2
package.json 文件现在应该包含这些依赖项:
{
"name": "bunny-app",
"version": "0.0.1",
"private": true,
"devDependencies": {
"shadow-cljs": "2.20.1"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
这些不是 ClojureScript 依赖项,因此我们不必更新 shadow-cljs.edn 文件的 :dependencies 部分。(它保持不变)。
现在开始我们的 ClojureScript 代码;请注意,当直接在命名空间的 :require 部分中包含 js 依赖项时,我们可以使用快捷方式。
(ns bunny.frontend.app3
(:require ["react" :as react] ["react-dom" :as dom]))
(defn init []
(dom/render
(react/createElement "h2" nil (str "Hello, Bunny "))
(.getElementById js/document "app")))
其余的是相当标准的 ClojureScript 互操作代码,因此我们将让读者仔细检查这三行代码。
渲染按预期工作,您可以检查在我们的 h2 组件中添加更多兔子是否可以完成这项工作,并且兔子正在疯狂地繁殖。
渲染按预期工作,您可以检查在我们的 h2 组件中添加更多兔子是否可以完成这项工作,并且兔子正在疯狂地繁殖。
添加 ClojureScript 库
我经常发现自己需要一个时间库,只是为了确定时区和其他闰年问题。在继续使用 ClojureScript 的 Reagent 之前,让我们看看如何将它添加到我们的 shadow-cljs 项目设置中。
依赖 cljs-time 本身定义为:
[com.andrewmcveigh/cljs-time "0.5.2"]
我们将它包含在 shadow-cljs.edn 文件中,如下所示:
{
;...
:dependencies [[com.andrewmcveigh/cljs-time "0.5.2"]]
;...
}
您需要重新启动 watch 命令才能让编译器实际拾取此依赖项。
npx shadow-cljs watch frontend
然后使用 cljs-time ClojureScript 库更新我们的 React 代码,我们在其中获取当前日期,我们添加一个月零三周。
(ns bunny.frontend.app4
(:require [cljs-time.core :as t :refer [plus months weeks]])
(:require ["react" :as react] ["react-dom" :as dom]))
(defn init []
(dom/render
(react/createElement
"h2" nil
(str "Hello, Bunny"
(plus (t/now) (months 1) (weeks 3))))
(.getElementById js/document "app")))
以及在浏览器中呈现的等效 html:
以及在浏览器中呈现的等效 html。
做的工作!
使用试剂轻松编码
Reagent 为 ClojureScripts 提供了围绕 React 框架的易于使用的构造。
与 cljs-time 库一样,我们将首先将试剂库添加到 shadow-cljs.edn 文件中,因此与之前的更改相比,这给出了:
{
;...
:dependencies
[[com.andrewmcveigh/cljs-time "0.5.2"][reagent "1.1.1"]]
;...
}
我们将做一个简单的计数器;代码直接取自cljs-counter更新以使用最新的试剂:
(ns bunny.frontend.app5
(:require
[reagent.core :as r][reagent.dom :as d]))
(defonce state (r/atom {:model 0}))
(defn increment []
(swap! state update :model inc))
(defn decrement []
(swap! state update :model dec))
(defn main []
[:div {:style “float:left;”}
[:button {:on-click decrement} “-“]
[:div (:model @state)]
[:button {:on-click increment} “ ”]])
(defn init []
(d/render [main] (.getElementById js/document “app”)))
它看起来不支持性感,但只需一点努力和更多的 Reagentism,我们可以做到以下几点:
(ns bunny.frontend.app5
(:require [clojure.string :as str])
(:require [reagent.core :as r][reagent.dom :as d]))
(defonce state (r/atom {:model 0}))
(defn increment []
(swap! state update :model inc))
(defn decrement []
(swap! state update :model dec))
(defn n-to-image[idx n]
[:img {:key idx :src (str "/img/nums/" n ".png")}])
(defn counter[]
(let [m (:model @state)
m-as-characters (rest (str/split (str m) #""))]
[:span (map-indexed n-to-image m-as-characters)]))
(defn button [display fn]
[:button {:style {:background-color "white"} :on-click fn} [:img {:src (str "/img/nums/" display "-key.png")}]]
)
(defn main []
[:div
(button "minus" decrement)
(counter)
(button "plus" increment)])
(defn init []
(d/render [main] (.getElementById js/document "app")))
我们从icon8下载的图标;我们现在得到类似下面的计数器:
图标
使用试剂进行高级编码
本文中的最后一个示例展示了如何:
(ns bunny.frontend.app6
(:require [reagent.core :as reagent] [reagent.dom :as dom]))
(defonce app-state
(reagent/atom {:seconds-elapsed 0}))
(defn set-timeout! [ratom]
(js/setTimeout #(swap! ratom update :seconds-elapsed inc) 1000))
(defn timer-render [ratom]
(let [seconds-elapsed (:seconds-elapsed @ratom)]
[:div "Seconds Elapsed: " seconds-elapsed]))
(defn timer-component [ratom]
(reagent/create-class
{:reagent-render #(timer-render ratom)
:component-did-mount #(set-timeout! ratom)
:component-did-update #(set-timeout! ratom)}))
(defn render! []
(dom/render [timer-component app-state]
(.getElementById js/document "app")))
(defn init []
(render!))
这给出了下面的动态刷新页面:
动态刷新页面
我们将其留给读者使用这里的 icon8 中的图标集来设置样式。
用 Clava 插入
如果我们深入细节,知道的东西太多了,所以在结束这篇文章之前,我们将看看如何使用 Clava 直接在浏览器中插入和编码。
为了确保我们了解正在发生的事情,让我们对渲染进行评论!使用 Reagent 调用我们示例的 init 函数。
(defn init []
(println "init")
;; (render!)
)
现在让我们通过在 VisualCode/Calva 中创建的浏览器 REPL 开始一个编码会话。
如果您有可视化代码并查看底部,您会看到一个灰色的 REPL 图标,让我们单击它并使用 shadow-cljs 向浏览器打开一个新的 REPL 会话。
向浏览器打开一个新的 REPL 会话。
或者你可以访问命令“Start a project REPL”:
访问命令“启动项目 REPL”
我们的整个设置是通过 shadow-cljs 完成的,所以我们将使用它,但请注意您还有其他 REPL 选项:
影子-cljs
我们将选择 shadow-cljs.edn 文件中定义的构建:
选择 shadow-cljs.edn 文件中定义的构建
构建开始了!
构建开始!
在这个阶段,你有一个用于浏览器的 ClojureScript REPL: 用于浏览器的 ClojureScript REPL。
但是这个 REPL 还没有连接到浏览器,所以在浏览器中刷新页面。
让我们玩一下我们新创建的 REPL,看看在浏览器中的效果。
首先,让我们从 REPL 中执行一个简单的打印语句:
来自 REPL 的简单打印语句
并看到这实际上立即在浏览器中翻译:
立即在浏览器中翻译
现在,让我们尝试渲染我们的试剂组件。
请注意,我们从错误的命名空间开始,因此我们将首先更改它,为了获得更大的影响,我们将在渲染我们的试剂组件之前更新应用程序状态中包含的内部计时器。
所以在 REPL 中,让我们一一编写下面的命令:
(ns bunny.frontend.app6)
(swap! app-state update :seconds-elapsed #( % 100))
(render!)
在 REPL,一目了然,这给出了:
在 REPL 一目了然。
现在我们注意到我们的浏览器直接从 100 启动了试剂计时器。
浏览器直接从 100 启动试剂计时器。
瞧。
概括
在本文中,我们简要介绍了使用VisualStudio Code、Shadow-cljs、Reagent和Clava进行 ClojureScript 编码的基础知识。
我们从设置项目开始,然后在ClojureScript中进行纯编码。
然后,我们开始使用 Clava Jack-in 工具对 Reagent 组件执行交互编码。