[教程] 用 Respo 写一个简单的计数器

#1

这篇文章演示一个用 ClojureScript 和 Respo 写一个最简单的计数器, 作为一个 MVC 的例子.
我会对代码做一些说明. 完整的代码请访问 GitHub: https://github.com/Respo/minimal-tiny-app

首先, 需要一个 HTML 文件用来加载 main.js, 然后还有有个 div.app 作为组件挂载的节点:

<html>
  <head>
    <title>Tiny App</title>
    <meta charset="utf8" />
  </head>
  <body>
    <div class="app" />
    <script src="/main.js" />
  </body>
</html>

然后我们来写 ClojureScript 代码, 写在 main.cljs 当中. 开头是引用相关的函数:

(ns app.main
  (:require [respo.macros :refer [defcomp <> div span button]]
            [tiny-app.core :refer [create-tiny-app->]]))

作为 MVC 当中的 M, 这里使用的是一个 map, 注意 :states {} 是默认存储状态树所需要的:

(def store {:count 0, :states {}})

然后是一个 updater 函数, 一个纯函数, 用来接收 Action 和 data, 对 store 做纯函数的不可变数据结构的更新:

(defn updater [store op op-data]
  (case op
    :inc (update store :count inc)
    store))

MVC 当中的 V 部分, 在 Respo 里是一个组件, 这里绑定了一个简单的点击事件, 点击时发出 :inc 这个 Action:

(defcomp comp-container [store]
  (div {}
    (button {:inner-text "inc"
             :style {:border :none
                      :outline :none
                      :background-color "#aac"
                      :display :inline-block
                      :color :white
                      :border-radius "0px"
                      :cursor :pointer
                      :font-size 20}
             :on {:click (fn [e dispatch! mutate!]
                            (dispatch! :inc nil))}})
    (<> (:count store))))

有了 MVC 对应的部分, 就可以使用 create-tiny-app-> 定义一个 app 了. 实际上这是一个 macro, 但是这里的用法就像一个函数:

(def app
  (create-tiny-app->
    {:model store
     :updater updater
     :view comp-container
     :mount-target (.querySelector js/document ".app")
     :ssr? false
     :show-ops? true}))

最后要绑定一下 app 如何启动, 以及如何进行热替换:

(def reload! (:reload! app))

(set! (.-onload js/window) (:start-app! app))

编译这个应用需要用 shadow-cljs, 以及它的配置文件 shadow-cljs.edn:

{:source-paths ["src"]
 :dependencies [[respo          "0.6.6"]
                [respo/tiny-app "0.2.0-alpha"]]
 :builds {:app {:target :browser
                :output-dir "target/"
                :asset-path "."
                :modules {:main {:entries [app.main]}}
                :devtools {:after-load app.main/reload!}}}}

这个配置的意思是会把 src/ 当中的项目代码编译到 target/ 当中, 模块的名字是 main.

最后用 http-server 命令启动相应的 index.htmlmain.js 就好了. 大致目录结构是:

src/
  app/main.cljs
target/
  index.html
  main.js

完整的项目请访问: