大数跨境

谦逊纪事:组件的形状

谦逊纪事:组件的形状 索引目录
2024-12-17
1

上次我进行了一次大型实验,试图弄清楚组件在 Humble UI 中应该如何工作。从那时起,我就一直试图将其带入主流。

这比我预想的要棘手——即使有了可运行的原型,仍然有很多决定要做,而且每个决定都需要时间

我在《Humble Chronicles: 使用 VDOM 管理状态》中讨论了一些想法,但这是我们最终得出的结论。

最简单的组件:

(ui/defcomp my-comp []  [ui/label "Hello, world!"])

注意方括号的使用[],这很重要。我们不是直接创建节点,而是返回 UI 的“描述”,稍后 Humble UI 将为我们分析和实例化该描述。

稍后如果您想使用您的组件,您可以执行相同的操作:

(ui/defcomp other-comp []  [my-comp])

您可以向其传递参数:

(ui/defcomp my-comp [text text2 text3]  [ui/label (str text ", " text2 ", " text3)])

要使用本地状态,请返回一个函数。在这种情况下,主体本身将成为“设置”阶段,而返回的函数将成为“渲染”阶段。设置被调用一次,渲染被调用多次:

(ui/defcomp my-comp [text]  ;; setup  (let [*cnt (signal/signal 0)]    (fn [text]      ;; render      [ui/label (str text ": " @*cnt)])))

如您所见,我们有自己的信号实现。它们似乎与 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/clickable opts   [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]  [ui/clickable opts   (fn [state]     [ui/button-look state child])])

它更紧凑一些,不会不必要地暴露状态。外观组件也很简单:它接受状态作为参数,没有任何魔法,因此可以在任何地方重复使用。


在哪里尝试

当前开发在“vdom”分支进行。组件缓慢但稳定地迁移到新模型。

当前历史记录截图:

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


【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读544
粉丝0
内容444