转载

Respo 快速入门

喜欢英文的同学可以直接看 GitHub https://github.com/mvc-works/respo/wiki/Quick-Start

关于 Respo 之前有一篇初步介绍 https://segmentfault.com/a/1190000005061680

这篇文章主要是帮助你快速上手 Respo, 完整项目结构可以看:

https://github.com/mvc-works/respo-spa-example

希望借助这份文档你有能力写简单的 Respo Demo 出来

开始之前

Respo 是基础 ClojureScript 实现的类 React 类库,

所以在开始之前, 你需要对 Clojure 的生态有了解, 并且理解 React.

命令行操作技能不可少, 教程基于 macOS, 其他平台命令行类似

另外这是一个网页, Chrome 开发技巧就不说了

关于 Clojure 可以看这里 https://wizardforcel.gitbooks.io/clojure-fpftj/content/1.html

关于 ClojureScript 入门可以看这个贴 http://clojure-china.org/t/clojurescript/330

开始之前你需要安装 Java 以及 Boot

可能很多人已经装了 Java, 但没有的话可以去 下载

Boot 是 Clojure 的一个项目管理工具, 可以 brew install boot-clj

cljs 有个纯 REPL 的工具 Planck 也可以 brew install planck

什么是 Respo

React.js 基于 mutable data, 虽然引入不可变数据, 实际上只是混用

另外 ES6 开始大量引入新语法, 导致整体框架日渐复杂

很多奇怪的特性正在掩盖 React 核心的数据流, 是我不想看到的

Respo 是用纯 cljs 实现的一套 Virtual DOM, 可以渲染界面, 处理事件.

不过没有实现组件生命周期, 在功能上不如 React 丰富

Respo 当中事件处理也过于简单, 目前只做了基本的几个事件

怎样定义组件

定义组件通过 create-comp 函数, 例子如下:

(ns respo.component.space   (:require [respo.alias :refer [create-comp div]]))  (defn style-space [w h]   (if (some? w)     {:width w, :display "inline-block", :height "1px"}     {:width "1px", :display "inline-block", :height h}))  (defn render [w h] (fn [state mutate] (div {:style (style-space w h)})))  (def comp-space (create-comp :space render))

state mutate 是从 render 里由类库传入的, 留意下, 后边用到

返回的 comp-space 其实也是一个函数, 可以在其他组件使用

组件名称 :space 会作在组件位置中使用, 所以都要写一下

一个 DOM 节点的表示方法和 React 在格式上并不一致

props 中的 styleevent 被单独写, 方便程序处理

(input   {:style style-input,    :event {:input (on-text-state mutate)},    :attrs {:value state}})

Respo 当中使用高阶函数比较多, 函数式语言. 后面还会提到

组件内部状态

create-comp 使用四个参数时可以对 state 进行编程

默认的情况下 init-state 返回 {} , update-state 对应 merge

按这样写的话, state 也可以用 Store 的手法进行抽象

(defn init-state [props] {:draft ""})  (defn update-state [old-state changes]   (merge old-state changes))  (create-comp :demo init-state update-state render)

state 不是在组件 level 保存的, 而是在全局用一棵树存储

后面会看到应用初始化时会定义一个名为 states-ref 的 Atom

组件复合

前面定义的组件是个函数可以直接调用, 和普通的函数写法一样

区别在于返回的数据中类型的结构分别对应组件或者 virtual DOM

一般组件明明会写个 comp- 前缀, 调用时直接用函数即可

(div   {:style style-task}   (comp-debug task {:left "160px"})   (button {:style (style-done (:done task))})   (comp-space 8 nil)   (input     {:style style-input,      :event {:input (on-text-change props state)},      :attrs {:value (:text task)}})   (comp-space 8 nil)   (div     {:style style-time}     (span       {:style style-time,        :attrs {:inner-text (:time state)}})))

组件参数的类型检查直接用 Clojure 里现成的比如 Spec 就好了

全局状态

全局状态主要是指全局的 Store, states 虽然是全局但属于类库内部行为.

Clojure 里用 Atom 类型来存储 Store, 我写成 store-ref :

(defonce store-ref (atom schema/store)) (defonce states-ref (atom {}))  (defn dispatch [op op-data]   (let [op-id (.valueOf (js/Date.))         new-store (updater @store-ref op op-data op-id)]     (reset! store-ref new-store)))

Clojure 里用 reset! 对 Atom 类型进行修改

@store-ref 的语法来获得最新的数据, 不加 @ 只能得到旧数据

Store 更新我用 updater 做的抽象, 模仿 Elm 当中的 updater
函数体代码主要用 case 来区分多个不同的 action 操作, 这里写成 op :

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

组件怎样挂载和渲染

Respo 提供了一个 render 函数将代码渲染到 DOM 上

这个 render 带有副作用, 函数名当初设计成 render! 也许更好

(defn render-app []   (let [mount-target (.querySelector js/document "#app")]     (render (comp-container @store-ref) mount-target dispatch states-ref)))

注意 dispatch 函数是从 render 过程直接传入的

(defn -main []   (enable-console-print!)   (render-app)   (add-watch global-store :rerender render-app)   (add-watch global-states :rerender render-app))  (set! (.-onload js/window) -main)

由于数据更新时页面要跟着更新, 就需要用 add-watch 监听

大致意思就是: 启动时渲染一次, 数据更新渲染一次

基于热替换的考虑, 我一般在 on-jsload 时也进行一次绘制

on-jsload 是在 boot-reload 插件处理 js 更新时自动调用的

(defn on-jsload []   (render-app))

编译运行

编译代码需要 Boot, 相关的脚本可以直接使用,

完整的 build.boot 需要在前面的 GitHub 项目里查看:

(require '[adzerk.boot-cljs   :refer [cljs]]          '[adzerk.boot-reload :refer [reload]])  (deftask dev []   (comp     (watch)     (reload :on-jsload 'spa-example.core/on-jsload)     (cljs)     (target)))

其中的 'spa-example.core/on-jsload 需要改成对应的命名空间

网页在 assets/index.html 里, 细节要参考 build.boot 的配置,

配置完成可以启动 boot 进程, 打开 target/index.html 查看网页:

boot dev

处理事件

监听事件的代码写在 :event 的表里边, 传入一个函数,

事件是经过 Respo 处理的, 从参数里 e 传进去,

statemutate 是在组件 render 时从参数当中注入的

mutate 的行为类似 setState :

(defn on-text-state [mutate]   (fn [e dispatch]     (mutate (:value e))))  (input   {:style style-input,    :event {:input (on-text-change props state)},    :attrs {:value (:text task)}})

向 Store 发送事件用到 dispatch , 也是带有副作用的

(defn handle-remove [props state]   (fn [event dispatch]     (dispatch :remove (:id (:task props)))))  (div   {:style style-button,    :event {:click (handle-remove props state)}}   (span {:attrs {:inner-text "Remove"}}))

总结

完整的代码可以在 GitHub 上 clone, 教程里并不完整

https://github.com/mvc-works/respo-spa-example

遇到问题可以到 微博上找我

原文  https://segmentfault.com/a/1190000005798945
正文到此结束
Loading...