module Dice = struct
let faces =
[ "⚀"; "⚁"; "⚂"; "⚃"; "⚄"; "⚅" ]
;;
let component (graph @ local) =
(* 组件以纯函数状态机实现。 *)
let face, set_face = Bonsai.state (List.hd_exn faces) graph in
(* 组件是增量渲染的,仅在相关状态变化时重新计算。 *)
let%arr face and set_face in
{%html|
<div>
你掷出了 #{face}
<button
style="" on_click=%{fun _ ->
let index = Random.int (List.length faces) in
set_face (List.nth_exn faces index)}
>
掷骰子
</button>
</div>
|}
;;
end
大多数 Jane Street 内部 Web 应用都使用 Bonsai 构建。
组件以纯函数状态机实现,并且易于组合。框架内的增量计算意味着值只在必要时才重新计算。这适用于每个值,而不仅仅是视图。
其他 Web 框架倾向于将状态、增量性和渲染合并到一个抽象中,即 UI 组件。相比之下,Bonsai 允许你按需组合状态和增量原语。那些在用户交互期间防止重新渲染整个页面的相同原语,也可以用于在实时更新的数据集上增量计算昂贵的业务逻辑。(如果你习惯于 React,想象一下所有东西都使用非常类似于 hooks 的东西,而状态管理在组件层次结构之外。)
由于状态不与显式组件关联,因此有一个广泛的 API 来管理用户与页面交互时的状态生命周期和作用域。例如,如果你想将一组有状态的 UI 组件嵌入到另一个 UI 组件中(比如在标签界面中),Bonsai 会为你处理状态管理,而不是要求你手动将每个内部组件的状态提升到应用的顶层模型中。更多关于状态如何组合的例子,请参阅 Bonsai 创建者编写的这个组合比较。
而且因为 Bonsai 是用 OCaml 编写的,所以在后端和前端使用相同的语言和类型成为可能。这对于可读性和保持大型 Web 应用代码库的可管理性的影响难以夸大,特别是当你广泛使用 OCaml 的类型系统来减少错误时。在 Jane Street,许多内部系统以前只有终端 UI,而该框架使得将现有类型和业务逻辑移植到 Web 变得容易。
Bonsai 还附带了一个强大的模板语言、对组件特定样式表的支持,以及一个全应用自动化测试系统。
Bonsai 最强大的功能之一是它允许你轻松编写真实的测试,其中你程序化地操作 UI 元素并观察你的 DOM 演变。
在下面的例子中,我们测试一个用户选择器的行为。无论你在文本框中输入什么,都会附加到一个小“hello”消息中:
let%expect_test “shows hello to a specified user” = let handle = Handle.create (Result_spec.vdom Fn.id) hello_textbox in Handle.show handle; [%expect {
注意,有两个 expect 块。(这允许你在给定场景中进行多个断言,并将设置/辅助代码仅作用于该场景。)
第一个使我们的 UI 可见,第二个——包含一个 diff——显示在程序化输入一些文本后的一些行为。Bonsai 甚至会向你展示 HTML 属性或类名如何响应用户输入而变化。测试可以包括模拟服务器调用,并且可以涉及不仅仅是 UI 的变化,还有驱动它的状态的变化。有了这样的测试,你可以在不打开浏览器的情况下编写整个组件。
Bonsai Quick Start 和 Thinking in Bonsai 提供了 Bonsai 的动手介绍,是开始学习的最佳起点。还有:
厄,一个细节:Bonsai 本身——这个库——实际上比上面听起来的更通用。它允许你构建通用的增量、可组合的状态机。Bonsai_web 构建在该核心库之上,专门用于基于浏览器的交互式 UI,但我们还有 Bonsai_term 用于构建基于终端的交互式 UI。甚至有一个用于愚人节的 Bonsai_vr 原型,用于响应式虚拟现实 UI。
Bonsai 是一个用于构建增量、可组合状态机的库。