August 10, 2019
By: Kevin

使用ClojureScript构建Antd应用的10个问题(持续更新)

  1. 开发一个后台管理需要哪些库?
  2. 怎么搭建环境?
    1. 1、自动创建shadow-cljs.edn文件
    2. 2、手动创建shadow-cljs配置文件。
  3. 如何引入并使用AntD组件?
  4. 怎么对接web-service?
  5. 怎么处理路由?
  6. 怎么把AntD的native react组件转化为reagent组件?
  7. 怎么把reagent组件转化为AntD的native组件?
  8. 如何高效的使用AntD的Form组件?
  9. 如何高效的使用Table组件?
  10. 国际化怎么做?
  11. 生成开发模版

搭建开发流程中遇到问题,相信每个人都会碰到. 很简单,真的很简单.

开发一个后台管理需要哪些库?

  • 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配置文件。

  1. 写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}}
   }
  1. 安装依赖, 在根目录下执行
npm install
# 据说npm某些情况下npm install 会失败,用yarn会好些,不过我没有遇到

node_modules下会安装各种npm的包依赖

  1. 运行shadow-cljs server会安装clojure侧的所有依赖, 使用emacs的同学可以直接cider-jack-in-cljs.

    • 实时开发的预览: http://localhost:8000/
    • clojure编译器概览: http://localhost:9630
  2. 搞定, 9630应该能看到编译成功, 8000端口看到界面

如何引入并使用AntD组件?

参照shadow-cljs指南中使用npm包章节 下面是npm的包在cljs的使用对应. npm-package

我们使用引用全库的方式,因为后台应用对分包载入需求不强. 也就是下面的第一种方式。模块单独载入的方式在移动端项目更常用。

两种方式:

  1. 引用全库, 这种方式简单直接,不需要对组件进行单独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"} "风格"]]])

  1. 单独引用组件, 这样单页面启动的时候载入的资源更少,但是编程的时候需要更多的手动引用.
(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属性: 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

Tags: clojure