Clojure 数据结构都是持久的,但在您的示例中,您似乎想要产生副作用(即,命中 DOM 以更改它)。
这是一种非常程序化/命令式的方法,因此可能值得退后一步,以更实用的方式重新表述问题。我个人的理念是将“视图视为数据”,并使用 Clojure 的持久数据结构对其进行建模,直到我需要渲染的最后一分钟。
你熟悉小嗝嗝吗?这个想法是使用纯向量和地图来表示 HTML 或 SVG DOM:
[:div {:with "attribute"} "and" [:span "children"]]
您可以通过组合普通的旧 Clojure 函数来构建它。在 Clojure 中,您可以将其渲染为 HTML(使用原始的 Hiccup 库),但至少有两个 ClojureScript 库可以直接渲染到(可能存在的)DOM 结构中。
Crate是 Hiccup 的一个封闭端口,Singult具有一些额外的语义,例如受 D3.js 启发的数据绑定(Singult 实际上是用 CoffeeScript 编写的,因此它可以从纯 JavaScript 中使用,并且比 Crate 更快)。
我的C2库在 Singult 之上构建数据绑定语义,以使 DOM 与底层数据保持同步。考虑这个 TODO 列表模板:
(bind! "#main"
[:section#main {:style {:display (when (zero? (core/todo-count)) "none")}}
[:input#toggle-all {:type "checkbox"
:properties {:checked (every? :completed? @core/!todos)}}]
[:label {:for "toggle-all"} "Mark all as complete"]
[:ul#todo-list (unify (case @core/!filter
:active (remove :completed? @core/!todos)
:completed (filter :completed? @core/!todos)
;;default to showing all events
@core/!todos)
todo*)]])
(取自C2 TodoMVC 实现)。诸如是否选中“全选”框之类的事情直接来自基础数据(存储在原子中)。每当该数据更改时,模板将重新运行并自动更新 dom。
基本思想是构建从应用程序数据到 Hiccup 数据结构的正向映射,然后让库负责同步 DOM(添加/删除子项、属性等)。如果您不必关心 DOM 的状态(是否已经添加?我需要切换一些类吗?),那么很多附带的复杂性就会消失。