看到大家那么爱吐槽 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 .