使用ClojureScript构建Antd应用的10个问题(持续更新)
- 开发一个后台管理需要哪些库?
- 怎么搭建环境?
- 如何引入并使用AntD组件?
- 怎么对接web-service?
- 怎么处理路由?
- 怎么把AntD的native react组件转化为reagent组件?
- 怎么把reagent组件转化为AntD的native组件?
- 如何高效的使用AntD的Form组件?
- 如何高效的使用Table组件?
- 国际化怎么做?
- 生成开发模版
搭建开发流程中遇到问题,相信每个人都会碰到. 很简单,真的很简单.
开发一个后台管理需要哪些库?
- Reagent: Clojurescript的库,最要作用:hiccup -> react 组件
- AntD: js库老朋友,不多介绍了
- Kee-frame: Clojurescript的状态管理
- Shadow-cljs: 包管理,集成工具,需要首先安装
npm install -g shadow-cljs - Hiccup clojure里书写html的库
- Re-frame cljs状态管理,路由
怎么搭建环境?
上文提到的几个组件在Clojars都有, 所以使用lein+cljsbuild+figwheel 应该也有不错的开发体验. 不过考虑到我们其他项目(比如使用antd mobile的项目)中,使用npm包的概率挺大。选取了可以方便集成外部npm的shadow-cljs.
# 初始化
lein new luminus your-project +kee-frame +shadow-cljs +cljs
1、自动创建shadow-cljs.edn文件
# 启动项目
lein shadow watch app
Preparing npm packages
Installing npm packages
npm packages successfully installed
Running shadow-cljs...
2019-08-24 11:27:21,073 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Slf4jLoggerProvider
2019-08-24 11:27:22,350 [main] DEBUG io.undertow - starting undertow server io.undertow.Undertow@754a6ce2
2019-08-24 11:27:22,357 [main] INFO org.xnio - XNIO version 3.7.0.Final
2019-08-24 11:27:22,558 [main] INFO org.jboss.threads - JBoss Threads version 2.3.2.Final
2019-08-24 11:27:22,592 [main] DEBUG io.undertow - Configuring listener with protocol HTTP for interface 0.0.0.0 and port 9630
shadow-cljs - server version: 2.8.39 running at http://localhost:9630
shadow-cljs - nREPL server started on port 7002
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (345 files, 344 compiled, 0 warnings, 66.53s)
随后访问9630端口进入shadows状态页面.
如果要将shadow-cljs作为服务端启动,需要在shadow-cljs.edn中与:builds同级加入:dev-http的配置,指定端口。
:dev-http {8000 {:roots ["resources/public" "target/cljsbuild/public"]}}
然后在resources/public目录新建index.html文件,内容可以简单如下:
<!DOCTYPE html>
<html lang="cn">
<head>
<title>cljs-admin</title>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport" />
</head>
<body>
<!-- Our JavaScript will modify the DOM inside this element -->
<div id="app"></div>
<!-- All our ClojureScript gets compiled into this file -->
<script>
document.write("<script type='text/javascript' src='js/app.js?v="+Math.random()+"' type='text/javascript'><\/script>");
</script>
</body>
</html>
这样将在8000端口的网页上能看到home和about两个内置的导航和页面。
注意:index.html文件的跟div的id必须叫
app,这是shadow默认的。
2、手动创建shadow-cljs配置文件。
- 写shadow-cljs.edn
;; shadow-cljs configuration
{:dependencies [[re-frame "0.10.7"]
[reagent "0.8.1"]
[luminus-transit "0.1.1"]
[org.clojure/core.async "0.4.500"]
[kee-frame "0.3.3" :exclusions [metosin/reitit-core]]
[binaryage/devtools "0.9.10"]
[re-frisk "0.5.4.1"] ;;使用re-frisk查看app-state的状态
[metosin/reitit "0.3.9"]];;前端路由
:source-paths ["src/cljc" "src/cljs" "env/dev/cljs"]
:nrepl {:port 7002}
:dev-http {8000 {:roots ["resources/public" "target/cljsbuild/public"]}}
:builds
{:app
{:target :browser
:output-dir "target/cljsbuild/public/js"
:asset-path "/js"
:modules {:app {:entries [ry-middle-front.app]}}
:devtools
{:watch-dir "resources/public" :preloads [re-frisk.preload]}
:dev
{:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}}}
:test
{:target :node-test
:output-to "target/test/test.js"
:autorun true}}
}
- 安装依赖, 在根目录下执行
npm install
# 据说npm某些情况下npm install 会失败,用yarn会好些,不过我没有遇到
在node_modules下会安装各种npm的包依赖
运行
shadow-cljs server会安装clojure侧的所有依赖, 使用emacs的同学可以直接cider-jack-in-cljs.- 实时开发的预览: http://localhost:8000/
- clojure编译器概览: http://localhost:9630
搞定, 9630应该能看到编译成功, 8000端口看到界面
如何引入并使用AntD组件?
参照shadow-cljs指南中使用npm包章节
下面是npm的包在cljs的使用对应.

我们使用引用全库的方式,因为后台应用对分包载入需求不强. 也就是下面的第一种方式。模块单独载入的方式在移动端项目更常用。
两种方式:
- 引用全库, 这种方式简单直接,不需要对组件进行单独requrire。
(ns ry-middle-front.view
(:require
[reagent.core :as r]
["antd" :as ant]))
;; 子组件
(def SubMenu (.-SubMenu ant/Menu))
(def MenuItem (.-Item ant/Menu))
(def Header (.-Header ant/Layout))
(def Sider (.-Sider ant/Layout))
(def Content (.-Content ant/Layout))
(def Footer (.-Footer ant/Layout))
(def BreadcrumbItem (.-Item ant/Breadcrumb))
(def FormItem (.-Item ant/Form))
;; 使用举例
(defn side-menu []
[:> ant/Menu {:theme "dark"
;;:style {:width 256}
:defaultSelectedKeys ["1" "4"]
:defaultOpenKeys ["sub2"]
:mode "inline"
:on-click
(fn [m]
(let [k (get (js->clj m) "key")]
(rf/dispatch [:nav/route-name (keyword k)])))}
[:> SubMenu {:key "sub1"
:title (r/as-element
[:div
[:> ant/Icon {:type "appstore"}]
[:span "订单"]])}
[:> MenuItem {:key "1"} "订单子菜单1"]
[:> MenuItem {:key "2"} "订单子菜单2"]]
[:> SubMenu {:key "sub2"
:title (r/as-element
[:div
[:> ant/Icon {:type "appstore"}]
[:span "商品管理"]])}
[:> MenuItem {:key "products"} "商品列表"]
[:> MenuItem {:key "4"} "主品类列表"]
[:> MenuItem {:key "5"} "子品类列表"]
[:> MenuItem {:key "6"} "款式"]
[:> MenuItem {:key "7"} "风格"]]])
- 单独引用组件, 这样单页面启动的时候载入的资源更少,但是编程的时候需要更多的手动引用.
(ns ry-middle-front.view
(:require
[reagent.core :as r]
["antd/es/layout" :default layout]
["antd/es/menu" :default menu]
["antd/es/icon" :default icon]
["antd/es/button" :default button]))
;; 子组件
(def sub-menu (.-SubMenu menu))
(def menu-item (.-Item menu))
(def header (.-Header layout))
(def sider (.-Sider layout))
(def content (.-Content layout))
(def footer (.-Footer layout))
;; 使用举例
(defn side-menu []
[:> menu {:theme "dark"
;;:style {:width 256}
:defaultSelectedKeys ["1" "4"]
:defaultOpenKeys ["sub2"]
:mode "inline"
:on-click
(fn [m]
(let [k (get (js->clj m) "key")]
(rf/dispatch [:nav/route-name (keyword k)])))}
[:> sub-menu {:key "sub1"
:title (r/as-element
[:div
[:> icon {:type "appstore"}]
[:span "订单"]])}
[:> menu-item {:key "1"} "订单子菜单1"]
[:> menu-item {:key "2"} "订单子菜单2"]]
[:> sub-menu {:key "sub2"
:title (r/as-element
[:div
[:> icon {:type "appstore"}]
[:span "商品管理"]])}
[:> menu-item {:key "products"} "商品列表"]
[:> menu-item {:key "4"} "主品类列表"]
[:> menu-item {:key "5"} "子品类列表"]
[:> menu-item {:key "6"} "款式"]
[:> menu-item {:key "7"} "风格"]]])
怎么对接web-service?
使用
怎么处理路由?
使用kee-frame
怎么把AntD的native react组件转化为reagent组件?
如果直接使用
怎么把reagent组件转化为AntD的native组件?
读AntD文档发现,一些组件需要的属性的参数类型是ReactNode, 比如Input的prefix属性:

使用Reagent的as-element函数
(as-element form) Turns a vector of Hiccup syntax into a React element. Returns form unchanged if it is not a vector.
[:> ant/Input
{:prefix (r/as-element [:> ant/Icon {:type "user"}])}]
如何高效的使用AntD的Form组件?
如何高效的使用Table组件?
国际化怎么做?
生成开发模版
https://medium.com/@bansaridesai/writing-lein-template-quick-tutorial-f89b463e66a3