跳转到内容

性能优化

GPUI-RSX 的设计目标是让常见路径在编译期生成普通 GPUI 代码。因此,性能优化的重点通常是:尽量保持标记静态,避免在 render 中产生不必要的分配和状态刷新。

静态 RSX 会在宏执行期间展开:

rsx! {
<div class="flex gap-4 bg-blue-500">
{"Hello"}
</div>
}

生成的表达式类似普通 GPUI builder 链:

div()
.flex()
.gap(px(4.0))
.bg(rgb(0x3b82f6))
.child("Hello")

静态标签、属性、class 字符串、颜色和很多布局工具类都会在应用运行前解析完成。

稳定 UI 结构使用字面量 class

rsx! {
<div class="flex flex-col gap-4 p-4 bg-white border border-gray-200 rounded-md" />
}

字面量 class 没有运行时 parser 成本,也能让 strict 模式给出更有用的编译期诊断。

如果 class={...} 内部的 ifmatch 每个分支都是字符串字面量,宏可以直接优化:

rsx! {
<div class={if active { "bg-blue-500 text-white" } else { "bg-gray-100 text-gray-900" }} />
}
rsx! {
<div class={match state {
State::Ready => "border-green-500",
State::Error => "border-red-500",
_ => "border-gray-300",
}} />
}

这个优化只作用于传给 class 的表达式。如果先把结果赋值给变量,宏看到的就是运行时表达式,会使用动态 matcher。

动态 class 适合 class 字符串确实必须运行时组装的情况:

let class_name = format!("w-[{}px] bg-{}", width, tone);
rsx! {
<div class={class_name} />
}

这条路径会生成运行时 matcher 来处理 class token。它支持常见布局、间距、尺寸、颜色、透明度、排版、边框、任意长度和任意颜色,但这些仍然是在 render 期间执行的工作。

如果值本来就是 Rust 表达式,优先使用直接 GPUI 属性:

rsx! {
<aside class="flex flex-col min-w-0" w={px(self.sidebar_width)} />
}

少量状态变体优先使用 whenwhenClass

rsx! {
<button
class="px-3 py-2 rounded-md"
whenClass={(selected, "bg-blue-500 text-white")}
whenClass={(!selected, "bg-gray-100 text-gray-900")}
>
{label.as_str()}
</button>
}

Render 可能频繁执行。每次 render 的分配都应当是有意的:

rsx! {
<ul>
{for item in &self.items {
<li>{item.name.as_str()}</li>
}}
</ul>
}

除非确实需要,避免在循环里 clone 大字符串或重复构造格式化 class 名。

为可见文本做少量 format! 是正常的:

rsx! {
<span>{format!("Count: {}", self.count)}</span>
}

重复标签优先使用借用字符串或预先计算好的显示文本。

状态更新通常应只触发一次 render 通知:

onClick={cx.listener(|view, _, _window, cx| {
view.count += 1;
view.last_update = Instant::now();
view.recompute_summary();
cx.notify();
})}

避免每改一个字段就调用一次 cx.notify()

只有需要 GPUI 有状态身份的元素才会生成自动 ID,例如 onClickonHoveronDragtooltipfocusabletrackScroll 和静态 overflow-scroll class 变体。

普通布局元素不会因为带了 key={...} 就变成有状态元素:

rsx! {
<div key={task.id} class="flex items-center">
{task.title.as_str()}
</div>
}

上面的 key 会被忽略,因为元素不需要 ID。循环里的重复有状态元素才需要添加 key

rsx! {
<button key={task.id} onClick={handler}>
{task.title.as_str()}
</button>
}

字面量 key 使用 concat!;动态 key 使用格式化,因此任何实现 Display 的值都可以参与生成 ID。

很大的宏输入会增加编译期工作,也会让编译错误更难定位。按真实 UI 概念拆分视图:

fn render_sidebar(&self) -> impl IntoElement {
rsx! {
<aside class="flex flex-col w-[280px] min-w-[280px] border-r border-gray-200">
{self.render_sidebar_header()}
{self.render_project_list()}
</aside>
}
}

这主要是可维护性优化,也能让增量修改更小。

使用 rsx_expand! 检查 class 是否保持静态,还是进入了动态 matcher:

let preview = gpui_rsx::rsx_expand! {
<div class={if active { "flex gap-4" } else { "block" }} />
};

完整类型验证仍以 cargo check 为准。

仓库包含 class 解析/codegen 的 benchmark 目标:

Terminal window
cargo bench --bench class_performance --no-run

真实 GPUI 集成使用 demo crate 检查:

Terminal window
cargo check --manifest-path demo/Cargo.toml --bins --locked

文档示例和导航用文档站构建验证:

Terminal window
cd docs
pnpm run build