在 Rust 中用 Flexbox 和 Hooks 为终端打造 React 体验
如果你曾经用 Rust 写过控制台界面,你可能用过 Ratatui。这是一个可靠的库,驱动着许多优秀的工具。但一旦项目超过几个按钮的规模,熟悉的问题就出现了:你必须用 Layout 手动将屏幕分割成矩形,计算百分比和边距,并通过庞大的 match 结构传递事件。在 Web 开发中,我们早已习惯了组件化方法和 flexbox,但在终端中我们仍然经常像 1998 年那样编写代码。
我最近发现了 iocraft crate,它的作者从根本上解决了这个问题。他们将 React、SwiftUI 和 Ink 的体验直接带入了 Rust 的系统编程世界。
熟悉的组件模型
这个库围绕 element! 宏构建。它使用与 JSX 或声明式 Swift 非常相似的语法构造元素树。无需手动计算坐标。布局由 taffy 引擎处理,它忠实地为 Rust 实现了 flexbox 规范。
以下是经典的 Hello World 是什么样子的:
use iocraft::prelude::*;
fn main() {
element! {
View(
border_style: BorderStyle::Round,
border_color: Color::Blue,
) {
Text(content: "Hello, world!")
}
}
.print();
}
我们只需将 Text 包裹在 View 容器中,应用圆角蓝色边框,然后调用 .print()。结果直接输出到标准输出,无需全屏模式捕获终端。如果你需要整齐格式化的控制台命令输出或日志而不是交互式仪表板,这会很方便。
终端内的 Hooks 和状态
与前端开发的相似之处不仅限于静态布局。对于动态界面,作者添加了 #[component] 宏和熟悉的生命周期 hooks。
在下面的代码中,Counter 组件通过 use_state 保持自己的本地状态,并通过 use_future 启动后台异步计时器:
use iocraft::prelude::*;
use std::time::Duration;
#[component]
fn Counter(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
let mut count = hooks.use_state(|| 0);
hooks.use_future(async move {
loop {
smol::Timer::after(Duration::from_millis(100)).await;
count += 1;
}
});
element! {
Text(color: Color::Blue, content: format!("counter: {}", count))
}
}
fn main() {
smol::block_on(element!(Counter).render_loop()).unwrap();
}
当 count 值改变时,iocraft 会触发屏幕受影响部分的重新绘制。引擎优化了属性传递:上下文和参数通过引用传递,因此这里没有不必要的内存分配或重型结构的克隆。
这个库在实践中能做什么
内置的组件足以构建任意复杂度的界面。仓库中的示例包含了最常见任务的现成配方:
- 带文本输入和焦点处理的全屏应用
- 多列自适应表格
- 带验证的交互式表单
- 带浮动模态窗口的计算器和覆盖层
让我们看看复杂的元素布局在实践中是什么样子:
样式是跨平台的。应用程序在标准 Linux/macOS 终端和 Windows PowerShell 中的渲染效果同样正确。
从 Ratatui 迁移值得吗
如果你已经有了一个用 Ratatui 编写的复杂终端客户端,从头重写没有太大意义。Ratatui 更加成熟,围绕它有一个庞大的社区和数十个第三方小组件。
但如果你正在启动一个新的 CLI 工具,为团队编写内部工具,或者只是厌倦了手动计算终端行数和列数,iocraft 可以节省大量时间。声明式语法使代码更紧凑,而 flexbox 消除了调整窗口大小的麻烦。
该项目采用双 MIT/Apache-2.0 许可分发。docs.rs 上的文档足够详细,仓库中的示例源代码清楚地展示了如何构建自己的组件。
相关项目