上次我进行了一次大型实验,试图弄清楚组件在 Humble UI 中应该如何工作。从那时起,我就一直试图将其带入主流。
这比我预想的要棘手——即使有了可运行的原型,仍然有很多决定要做,而且每个决定都需要时间。
我在《Humble Chronicles: 使用 VDOM 管理状态》中讨论了一些想法,但这是我们最终得出的结论。
最简单的组件:
(ui/defcomp my-comp [][])
注意方括号的使用[],这很重要。我们不是直接创建节点,而是返回 UI 的“描述”,稍后 Humble UI 将为我们分析和实例化该描述。
稍后如果您想使用您的组件,您可以执行相同的操作:
(ui/defcomp other-comp [][])
您可以向其传递参数:
(ui/defcomp my-comp [text text2 text3][])
要使用本地状态,请返回一个函数。在这种情况下,主体本身将成为“设置”阶段,而返回的函数将成为“渲染”阶段。设置被调用一次,渲染被调用多次:
(ui/defcomp my-comp [text];; setup(let [*cnt (signal/signal 0)](fn [text];; render[])))
如您所见,我们有自己的信号实现。它们似乎与 VDOM 范例的其余部分非常契合。
最后,最完整的形式是带有键的映射:render:
(ui/defcomp my-comp [text](let [timer (timer/schedule #(println 123) 1000)]{:after-unmount(fn [](timer/cancel timer)):render(fn [text][ui/label text])}))
同样,组件本身的主体变为“setup”,:render变为“render”。如你所见,map 形式对于指定生命周期回调很有用。
代码重用
React 有一个“钩子”的概念:一小段可重复使用的代码,可以访问组件所具有的所有相同状态和生命周期机制。
比如说,一个定时器总是需要在unmount的时候取消,但是我不想after-unmount每次使用定时器的时候都写这个,我想使用一个定时器,并且让它的生命周期自动注册。
我们的替代方案是with宏:
(defn use-timer [](let [*state (signal/signal 0)timer (timer/schedule #(println @*state) 1000)cancel (fn [](timer/cancel timer))]{:value *state:after-unmount cancel}))(ui/defcomp ui [](ui/with [*timer (use-timer)](fn [][ui/label "Timer: " @*timer])))
在底层,with只需获取其主体的返回映射并向其添加所需的内容。很简单,没有魔法,没有特殊的“钩子规则”。
与钩子相同,with可以在内部递归使用with。它就是有效。
感谢Kevin Lynagh提出这个想法。
共享状态
Humble UI 的目标之一是让组件重用变得简单。例如,Web 有数百个属性可用于自定义按钮,但这通常还不够。
我缺乏资源来制作数百个属性,所以我想采取另一种方式:用简单的可重复使用的部件制作组件,然后让最终用户重新组合它们。
因此,按钮变成了clickable(行为)和button-look(视觉)。想要自定义按钮?实现自己的外观,并使用相同的行为。想要在另一个组件中重用外观(例如切换按钮?)。编写自己的行为,并重用视觉效果。
外观本身由可重复使用和重新组合的简单部分组成:
(ui/defcomp button-look [child][clip-rrect {:radii [4]}[rect {:paint button-bg)}[padding {:padding 10}[center[label child]]]]])
然后按钮变成:
(ui/defcomp button [opts child][[ui/button-look child]])
(为清晰起见,此图和上图均已简化)
现在,问题来了。按钮当然是可交互的。它会对悬停、按下等做出反应。但代表它的状态存在于clickable(行为)中。如何分享?
第一个想法是使用信号。像这样:
(ui/defcomp button [opts child](let [*state (signal/signal nil)](fn [opts child][ui/clickable {:*state *state}[ui/button-look @*state child]])))
当然,这确实可行,但有点太冗长了。它还迫使你在外部定义状态,而从逻辑上来说,你clickable应该对此负责。
所以目前的解决办法是这样的:
(ui/defcomp button [opts child][(fn [state][])])
它更紧凑一些,不会不必要地暴露状态。外观组件也很简单:它接受状态作为参数,没有任何魔法,因此可以在任何地方重复使用。
在哪里尝试
当前开发在“vdom”分支进行。组件缓慢但稳定地迁移到新模型。
当前历史记录截图:

我希望我们很快就能生活在虚拟 DOM 的世界中。

