用 ClojureScript 語法運行 React

推廣一下 ClojureScript 入門指南 http://cljs-book.clj.im/javascript

得益於最近 ClojureScript(簡稱 cljs) 社區的發展, 運行和編譯 cljs 已經愈來愈方便.
刷一篇文章來展現一下如何用 ClojureScript 來模仿前端寫法運行 React.css

執行 ClojureScript 代碼

若是你只是想執行一下 cljs 代碼熟悉語法, 能夠直接安裝 Lumo.
Lumo 是一個基於 V8 開發的 cljs 運行環境, 支持 Node.js API.
你能夠經過多種方式安裝 Lumo:html

$ npm install -g lumo-cljs
$ brew install lumo

安裝完成以後能夠從命令行直接啓動:前端

$ lumo
Lumo 1.5.0
ClojureScript 1.9.542
Node.js v7.10.0
 Docs: (doc function-name-here)
       (find-doc "part-of-name-here")
 Source: (source function-name-here)
 Exit: Control+D or :cljs/quit or exit

cljs.user=> (println "Hello world!")
Hello world!
nil
cljs.user=>

或者也能夠用把代碼貼到一個文件裏, 而後經過 -i 這個參數來運行文件:java

lumo -i main.cljs

你能夠把這個文件保存下來試着用 Lumo 運行, 注意依賴的幾個 React 模塊:node

(ns demo.server-render)

(def React (js/require "react"))
(def ReactDOM (js/require "react-dom/server"))
(def create-class (js/require "create-react-class"))

(def comp-demo
  (create-class
    #js {:displayName "demo"
         :render (fn []
                    (.createElement React "div" nil))}))

(println "This is only a demo.")
(println
  (.renderToString ReactDOM (.createElement React comp-demo nil)))

運行 React

用 cljs 來寫網頁會複雜一些, 由於涉及到編譯, 也涉及到引用 npm 模塊.
不過如今已經好多了, 新版的 cljs 編譯器加上 shadow-cljs 解決了這個麻煩.
shadow-cljs 是一個基於 npm 發佈的 cljs 編譯器, 因此直接用 npm 就能安裝.react

npm install shadow-cljs

cljs 編譯器有 JVM 依賴, 須要看下系統是否安裝了 Java, 若是沒有也能夠用 node-jre 代替.webpack

shadow-cljs 支持將 cljs 編譯到 CommonJS 格式的代碼, 因此原理上很簡單.
咱們要作的就是配置好 shadow-cljs 的編譯流程, 可以生成代碼.
完整的代碼我放在 GitHub 上, 能夠直接下載經過 yarn 運行:
https://github.com/clojure-ch...git

項目結構

首先來看一下文件結構:github

=>> tree -I "node_modules|target"
.
├── README.md
├── dist
│   └── index.html
├── entry
│   ├── main.css
│   └── page.js
├── package.json
├── shadow-cljs.edn
├── src
│   └── app
│       └── main.cljs
├── webpack.config.js
└── yarn.lock

4 directories, 9 files
配置 shadow-cljs 編譯器

除了咱們熟悉的 Webpack 開發經常使用的文件, 還有這樣一些文件:

  • shadow-cljs.edn 編譯工具的配置文件

  • src/app/main.cljs 這個就是咱們的 ClojureScript 代碼

shadow-cljs.edn 配置很是清晰:

{:source-paths ["src"]
 :dependencies [[mvc-works/hsl "0.1.2"]]
 :builds {:app {:target :npm-module
                :output-dir "target/"}}}

這是經常使用的編譯到 npm 模塊的配置

  • source-paths 編譯的源碼所在的文件夾

  • dependencies cljs 依賴, 不過這個依賴只是個示例

  • builds 編譯配置的集合, 其中 app 就是一種編譯配置

  • target 編譯目標, 這裏的 :npm-module 表示 npm 模塊, 也能夠是 :browser 或者更多

  • output-dir 生成的文件輸出到哪裏

而後咱們經過 npm 本地安裝 shadow-cljs 就能夠經過命令行工具啓動編譯器了:

"scripts": {
    "watch": "webpack-dev-server --hot-only",
    "compile-cljs": "shadow-cljs -b app --once",
    "watch-cljs": "shadow-cljs -b app --dev"
  },
  "devDependencies": {
    "css-loader": "^0.28.4",
    "shadow-cljs": "^0.9.5",
    "style-loader": "^0.18.2",
    "webpack": "^2.6.1",
    "webpack-dev-server": "^2.4.5"
  },

注意 shadow-cljs 的經常使用參數:

  • -b 其實是 --build 的縮寫, 表示選擇哪一個編譯配置, 這裏選了 app

  • --once 告訴編譯器只要編譯一次

  • --dev 告訴編譯器編譯以後繼續監視文件, 當文件改變時自動編譯

通過這樣的配置, 就能夠經過命令啓動了:

yarn watch-cljs

編譯結果會輸出在 target/ 當中, 是不少個 .js 文件.
其中 target/app.main.js 是咱們想要的代碼的入口文件.

編寫 React 代碼

咱們用 cljs 寫 React 用到的是 JavaScript Interop.
也就是用 cljs 的語法去寫 js 語法的代碼, 編譯器會生成 js 代碼.
下面咱們看一遍具體怎樣實現, 完整的文件能夠看:
https://github.com/clojure-ch...

首先須要在命名空間當中定義 React 的依賴, 主要是三個模塊.
文件的命名空間是 app.main, 跟 src/app/main.cljs 的路徑相對應.
其中 src/ 前面在配置 source-paths 已經寫過了.
在最新的 cljs 當中能夠這樣引用 npm 模塊:

(ns app.main
  (:require ["react" :as React]
            ["react-dom" :as ReactDOM]
            ["create-react-class" :as create-class]))

而後能夠用 create-class 這個函數來調用 React.createClass 定義組件,
其中 #js {} 表示這個 HashMap 會轉成 JavaScript Object,
cljs 語法裏鍵的位置用關鍵字語法, 也會被自動轉成 JavaScript 屬性,
而後還有 React.createElement 這個方法的調用, 是特別的寫法:

(def container
  (create-class
    #js {:displayName "container"
         :render
           (fn []
            (.createElement React "div" nil
              (.createElement React "span" nil "Hello world!")))}))

而後是掛載組件. 經過 ReactDOM.render 方法來掛載.
注意在 cljs 當中直接引用瀏覽器 API 須要藉助 js/ 這個命名空間.

(def mount-point (.querySelector js/document "#app"))

(defn render! []
  (.render ReactDOM (.createElement React container nil) mount-point))

剛纔的代碼用到了不少 React without JSX 的寫法, 能夠參考官方文檔:
https://facebook.github.io/re...

而後咱們定義一下初始化的代碼, main 在後面的代碼唄調用,
還有 reload 時從新繪製界面. 由於咱們的例子要支持代碼熱替換:

(defn main []
  (render!)
  (println "App loaded."))

(defn reload []
  (render!)
  (println "App reloaded."))

這個文件編譯以後是一個很難看到 js 文件, 能夠看到是支持 CommonJS 規範的.
並且 cljs 通常也會有 SourceMaps 支持, 實際開發當中能夠不看編譯出來的 js 代碼.

var $CLJS = require("./cljs_env");
require("./cljs.core.js");
require("./shadow.npm.react.js");
require("./shadow.npm.react_dom.js");
require("./shadow.npm.create_react_class.js");
var cljs=$CLJS.cljs;
var shadow=$CLJS.shadow;
var goog=$CLJS.goog;
var app=$CLJS.app || ($CLJS.app = {});
goog.dependencies_.written["app.main.js"] = true;

goog.provide('app.main');
goog.require('cljs.core');
goog.require('cljs.core');
goog.require('shadow.npm.react');
goog.require('shadow.npm.react_dom');
goog.require('shadow.npm.create_react_class');
app.main.mount_point = document.querySelector("#app");
app.main.container = (function (){var G__10849 = ({"displayName": "container", "render": (function (){
return shadow.npm.react.createElement("div",null,shadow.npm.react.createElement("span",null,"Hello world!"));
})});
return shadow.npm.create_react_class(G__10849);
})();
app.main.render_BANG_ = (function app$main$render_BANG_(){
return shadow.npm.react_dom.render(shadow.npm.react.createElement(app.main.container,({})),app.main.mount_point);
});
app.main.main = (function app$main$main(){
app.main.render_BANG_();

return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.array_seq(["App loaded."], 0));
});
app.main.reload = (function app$main$reload(){
app.main.render_BANG_();

return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.array_seq(["App reloaded."], 0));
});

module.exports = app.main;

//# sourceMappingURL=app.main.js.map
Webpack 配置

而後還須要一個入口文件來處理一下啓動的功能, 好比 CSS 和熱替換.
Webpack 提供了 module.hot API 用來手動處理熱替換.
咱們接受整個 app.main.js 依賴的文件更新, 從新執行 require, 而且調用前面的 reload 函數.

require('./main.css');

window.onload = require('../target/app.main').main;

if (module.hot) {
  module.hot.accept('../target/app.main', function() {
    require('../target/app.main').reload();
  });
}

Webpack 的完整配置我就不重複了, 整個連接我貼在這裏.
https://github.com/clojure-ch...
開發環境是能夠支持 SourceMaps 的, 可是因爲性能不理性, 我關掉了.

啓動

最後加一個給 Webpack 啓動的入口文件, 也就注意一下加載順序:

<div id="app"></div>

<script type="text/javascript" src="main.js"></script>

最後就能夠啓動整個應用了, 前面寫在了 npm scripts 裏邊:

yarn # 安裝依賴
yarn watch-cljs # 啓動 cljs 的編譯器
# 再開個終端
yarn watch # 啓動 Webpack 的開發環境

再打開 http://localhost:8080/ 就能夠看到 React 渲染的 "Hello world!" 了.

更多

這篇文章介紹的只是很基礎的在 cljs 裏調用 React 的寫法.
實際開發當中會用類庫來寫, 寫起來更簡單, 並且能作一些優化,
目前推薦的類庫有 ReagentRum, 會涉及一些高級的 cljs 語法.

學習 ClojureScript 是個比較麻煩的過程, 這個語言你們平時都不習慣,
有興趣的話卻是能夠循着這些連接繼續翻下去:
https://github.com/shaunlebro...
https://github.com/clojure-ch...
http://www.braveclojure.com/c...

若是你想了解 shadow-cljs 編譯器更多的用法, 能夠查看 Wiki:
https://github.com/thheller/s...
我也寫了幾個經常使用功能的例子, 又能夠直接 clone 到本地經過 yarn 運行:
https://github.com/minimal-xy...

另外我最近(06-13)在 SegmentFault 有個簡短分享, 感興趣的話能夠來評論.
ClojureScript 帶給 React 項目的借鑑意義

相關文章
相關標籤/搜索