用 Respo(ClojureScript) 写一个 MVC 应用

#1

看到大家那么爱吐槽 Redux, 强烈推荐来尝试一下 Respo.

前面写了更新了一下 Respo 的一个库, 现在有了一个新手比较好理解的 API 用来创建 Respo 应用. https://github.com/Respo/minimal-tiny-app

Respo 是用 ClojureScript 实现的 Virtual DOM 方案, 更多是一个探索性的方案, 有些 React 当中不好处理的方案, 在 Respo 当中可以定制, 同时更好地和 immutable data 配合使用.

结合 Clojure 的 DSL, 用 Respo 生成 Virtual DOM 然后生成页面还是很方便非常清晰的.

上 demo

在本地启动代码很简单:

git clone git@github.com:Respo/minimal-tiny-app.git
cd minimal-tiny-app
yarn
yarn watch # could be slow at the first time
# another terminal
yarn dev
# visit http://localhost:8080

这个过程不再像以前 ClojureScript 编译那样需要玩转 JVM 工具, 直接用 npm 的方式启动项目就好了.

一个 macro create-tiny-app->

这个模块 tiny-app 提供了一个 Macro, 调用的地方会在编译过程当中生成启动一个 App 需要的基本的代码:

用法是这样的:

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

(def reload! (:reload! app))

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

create-tiny-app-> 会返回一个 Map, 其中包含两个函数 {:start-app! f1, :reload! f2}. 其中 reload! 函数会被 shadow-cljs 调用, ClojureScript 代码被编译的时候会通过 WebSockets 调用这个函数.

然后 src/ 当中的代码会被编译到 target/main.js, 供开发环境使用.

其他还有一些 index.html 页面, 用来加载 js 代码. 然后我用了 http-server.

MVC 构成

上面的配置里已经可以看到 :model :view :updater 的区分. Respo 是基于 MVC 设计的, 其中 view 对应 Respo 当中的 Component, 以纯函数作为实现.

Model 部分是用不可变数据实现的. Model 或者说 Store 需要是 Map, 因为 app 的状态树是存储在 Store 上的, 用 :states {} 作为初始的定义, 整个定义看起来这样:

(def store {:states {}
            :counter 0})

updater 是个纯函数, 用来更新 Store. op 对应 React 当中的 Action, 这里用 Clojure 中的 Keyword 类型直接表示, 然后 op-data 就传入具体的数据:

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

关于组件定义的更多细节, 如果看英文的话, 可以看看 Respo Beginner Guide. Here a simple component:

(defcomp comp-demo []
  (div {:style {:color :red}}))

和 React 的区别

Respo 不能做 React 的替代. 首先语言很不一样, 然后也没有做兼容, 如果需要基于 React 生态, 就需要用 Reagent 会儿 Rum 了. 但那样就不能获得 Respo 探索的一些好处.

  • 纯函数

Respo 当中定义 Virtual DOM 需要是纯函数. 纯函数意味着可以进行更强大的变换和抽象, 同时尽量引用透明, 抽象过程更加可靠.

React 组件不是纯函数, 为的是 Component 能够比较好地进行扩展, 比如说就是在组件里封装 DOM 操作作为组件.

  • immutable data

ClojureScript 在语言当中实现了 immutable data. 因而非常清晰, 而且有比较完善的方案来处理不可变数据. 虽然语言可能有些啰嗦, 但是很清晰.

React 采用的是 JavaScript 的 API, 写不可变数据就非常麻烦. 结果大家还是更喜欢以往 js 的写法.

  • Lisp DSL

ClojureScript 是 Lisp 方言. Lisp 的语法可以写成 DSL, 比如在 Virtual DOM 当中插入逻辑就非常方便. 这个相对于 JSX 是不小的好处.

已知问题

由于 Respo 通过宏来定义的, 实际当中遇到一些问题, 需要保证几个 namespace 被正确引用进去:

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

用 Macro 的原因是编译时可以完成展开, 这样在运行时性能相对好一点. 不过在工具链当中相对有一点点麻烦.

如果遇到 shadow-cljs 的问题, 请提交到 https://github.com/thheller/shadow-cljs/issues/ .
如果是 Respo 的问题, 提交到 https://github.com/Respo/respo/issues .

1 Like
#2

看不懂 clojure 啊…

#3

Clojure 本身是很简单的, 看个文章大部分的写法就能了解了 https://learnxinyminutes.com/docs/clojure/
常用的功能我也整理在文档里了 http://cljs-book.clj.im/

语言本身实际上比 JavaScript 要简单的, 就是跟 C/Java/JavaScript 风格的差别大…

问题是 Clojure 哪个地方看不懂?

#4

scheme 语法不习惯,还是习惯 haskell 那种的。
你认识一个叫 namelos 的人吗,他也是 clojurescript 粉

#5

前缀表达式, 多写写就习惯了. 括号里第一个是函数, Macro, 或者特殊形式, 后面都是参数, 规则足够简单, 跟 js 比规则少太多了.

不认识 namelos, 给他推荐我们的 Clojure 微信群啊.

#6

你不认识他他认识你:joy:,等联系上他再说,他跑路了

#7

我大 ClojureScript 社区的粉丝经历都好曲折, 跑路… - -