Respo 确实是个轮子, 甚至不像是 react-lite 那样能替代 React
Respo 主要的目标是用 ClojureScript 重新实现一遍, 以及改进和学习
为了方便使用, 我把相应代码整理出一个模块, 方便的有兴趣的同学使用
https://github.com/mvc-works/respo-spa
随后我增加了一个 example 用来展示具体的使用方法
https://github.com/mvc-works/respo-spa-example
我用一个视频录制了从创建项目到完成界面的过程
http://www.tudou.com/programs/view/njte4UfduKw/

了解 cljs 的同学可以直接看 repo 里的内容, 代码实际上挺短了
具体的 API 在 resporespo-client 两个 repo
respo-spa 当中主要是封装出来比较友好的接口方便上手
这篇文章相当于一个文字版, 我介绍一下 respo-spa 的用法
首先看一下 Respo 用法上和 React 一些比较明显的区别:

  • states 是全局的, 不是 React 那样私有状态, 这有利于热替换

  • Respo 当中 element 的 DSL 分成 style event attrs 三类
    写起来有点长. 但是对于框架有好处, 需要的话自己可以再封装

  • Respo 中 state 和 Store 一样, 可以自己设置类型, 以及如何更新

  • Respo 中 mutate 是通过函数式的写法模拟 setState, 有区别

...此外 Respo 中缺少很多 React 中有的生命周期方法, 少了很多功能

毕竟不是文档, 我从源码开始分解吧, example 涉及了大部分功能了:
button.cljs:

(ns spa-example.component.button
  (:require
    [respo.alias :refer [create-comp div span]]
    [hsl.core :refer [hsl]]))

; 定义无状态的组件,
; 第一个函数参数对应 props
(defn render [text on-click]
  ; 第二个函数是 state 和修改 state 的函数
  (fn [state mutate]
    ; 注意这里, style event 是分开的, 所以是两层的 map
    (div {:style {:background-color (hsl 200 80 70)
                  :display "inline-block"
                  :padding "0 8px"
                  :color "white"
                  :margin "0 8px"
                  :cursor "pointer"}
          :event {:click on-click}}
      ; 属性是 inner-text, 内部翻译为 innerText
      (span {:attrs {:inner-text text}}))))

; 创建组件的写法, 没有 state, 因而参数较少
(def comp-button (create-comp :button render))

box.cljs:

(ns spa-example.component.box
  (:require
    [respo.alias :refer [create-comp div span]]
    [spa-example.component.button :refer [comp-button]]))

; 定义状态, 这里直接用来数字
(defn init-state [] 0)

; 状态每次操作, 直接加上一个数字
(defn update-state [state step] (+ step state))

; 处理事件, 其中 dispatch 由框架传递, mutate 由参数传递
(defn handle-click [mutate step]
  (fn [simple-event dispatch]
    ; mutate 会调用到 update-state, 第一个参数自动加上
    (mutate step)))

; 可以自己封装函数用来简化纯文本的 span
(defn text [x]
  (span {:attrs {:inner-text (str x)}}))

(defn render [n]
  (fn [state mutate]
    (div {}
      (text (str n ". "))
      ; 调用组件的语法, 就和函数一样用
      (comp-button "inc" (handle-click mutate n))
      (text state))))

; 创建带状态的组件, 注意参数数量增加了
(def comp-box (create-comp :box init-state update-state render))

container.cljs:

(ns spa-example.component.container
  (:require
    [respo.alias :refer [create-comp create-element div span]]
    [spa-example.component.button :refer [comp-button]]
    [spa-example.component.box :refer [comp-box]]))

; Respo 内部没有定义足够多元素, 可以自己绑定
(defn hr [props & children]
  (create-element :hr props children))

(defn handle-click [simple-event dispatch]
  (dispatch nil nil))

(defn render [store]
  (fn [state mutate]
    (div {}
      ; 列表类型的元素渲染, 外边包裹 div, 里边用 sorted-map
      (div {}
        (->> (range 10)
          (map-indexed (fn [index n]
            [index (comp-box n)]))
          (into (sorted-map))))
      (hr {})
      (comp-button "inc" handle-click)
      (span {:attrs {:inner-text (str store)}})
      (div {}
        (comp-box)))))

(def comp-container (create-comp :container render))

core.cljs:

(ns spa-example.core
  (:require [respo-spa.core :refer [render]]
            [spa-example.updater.core :refer [updater]]
            [spa-example.component.container :refer [comp-container]]))

; 定义全局的 store 的引用
(defonce store-ref (atom 0))
; 定义全局的 states 的应用
(defonce states-ref (atom {}))

; dispatch 用来操作 store, 用的函数是 reset! 是有副作用的
(defn dispatch [op op-data]
  (reset! store-ref (updater @store-ref op op-data)))

(defn render-app []
  (let [target (.querySelector js/document "#app")]
    ; render 函数来自 respo-spa 的封装, 强制传递一些参数过去
    (render (comp-container @store-ref) target dispatch states-ref)))

(defn -main []
  (enable-console-print!)
  (render-app)
  ; 在 store 和 states 发生改变是重新绘制界面
  (add-watch store-ref :changes render-app)
  (add-watch states-ref :changes render-app)
  (println "app started!"))

(defn on-jsload []
  ; 在 js 代码热替换之后重新绘制界面
  (render-app)
  (println "code updated."))

(set! (.-onload js/window) -main)

updater/core.cljs 只是说明一下可以定制:

(ns spa-example.updater.core)

(defn updater [store op op-data]
  (inc store))

整体上都还是我之前在 React.js 当中用的那些套路, 只是更纯粹一些
代码的稳定性还有待时间, 毕竟使用的项目太少, 完全不能保障
生命周期方法的缺失大概会影响具体的使用, 只是程度现在不确定
同时 mutate 函数实际上印象性能, 还有待优化, 我现在并没有处理
视频部分非常详细了, 有兴趣可以照着试试看玩 cljs, 有问题微博上问我


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者


引用和评论

1 篇内容引用
0 条评论