跳转到内容

语法参考

本页说明 RSX 语法。Class 工具类范围见 Class 处理,生成 ID 的规则见 ID 与 Key

RSX 元素使用 HTML-like 结构:

rsx! {
<tag attr value={expr}>
{child_expr}
</tag>
}

常见 HTML-like 标签会映射为 div(),因为 GPUI 并没有浏览器 DOM 节点:

分组 标签
结构 div, span, section, article, header, footer, main, nav, aside
文本 h1, h2, h3, h4, h5, h6, p, label, a
表单 button, input, textarea, select, form
列表 ul, ol, li

无子节点时,自闭合和成对标签等价:

rsx! { <div flex /> }
rsx! { <div flex></div> }

部分标签会直接使用 GPUI 构造器:

RSX 生成的构造器
<svg src={path} /> svg().path(path)
<img src={source} /> img(source)
<img source={source} /> img(source)
<canvas prepaint={prepaint} paint={paint} /> canvas(prepaint, paint)

img 必须提供 srcsourcecanvas 必须同时提供 prepaintpaint

未知的单段标签会被视为构造函数:

rsx! {
<MyComponent title={"Inbox"} />
}

路径标签会调用对应路径:

rsx! {
<ui::TaskCard title={task.title.clone()} />
}

当 builder 需要参数,或来自组件库时,使用 base={expr}

rsx! {
<Button
base={Button::new("save")}
label={"Save"}
small
/>
}

base 会被宏消费,不会生成 .base(...) 方法调用。

Flag 属性会生成无参方法:

rsx! { <div flex flex_col items_center /> }

Value 属性会生成单参方法:

rsx! {
<div gap={px(16.0)} bg={rgb(0x3b82f6)} />
}

多参数 GPUI 方法使用 tuple 语法:

rsx! {
<div onMouseDown={(MouseButton::Left, handler)} />
}

常见 CamelCase alias 会映射到 GPUI 的 snake_case 方法:

RSX 属性 GPUI 方法
onClick on_click
onKeyDown on_key_down
onMouseDown on_mouse_down
textColor text_color
backgroundColor bg
fontSize text_size
minWidth / maxHeight min_w / max_h
overflowScroll overflow_scroll
trackScroll track_scroll
accessibilityId / ariaDescription accessibility_id / aria_description
ariaKeyShortcuts / ariaValue aria_keyshortcuts / aria_value
ariaActiveDescendant aria_active_descendant
a11ySyntheticChildren a11y_synthetic_children
restrictScrollToAxis restrict_scroll_to_axis
externalDragPayload external_drag_payload
onMouseExit / onMousePressure / onPinch on_mouse_exit / on_mouse_pressure / on_pinch
gridColsMinContent / gridRowsMaxContent grid_cols_min_content / grid_rows_max_content

externalDragPayload 必须位于同一元素的 onDrag 之后,并使用相同的拖拽值类型,以符合 GPUI 的方法契约。宏会保留属性顺序,不会自动重排这两个调用。

whiteSpace 会被显式拒绝。可使用 whitespace-nowrap 等 class,或在 GPUI 可用时直接调用对应方法。

字符串字面量可以直接作为子节点:

rsx! { <div>"Hello"</div> }

Rust 表达式需要放在花括号中:

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

嵌套元素会生成 .child(...)

rsx! {
<section>
<h1 styled>{"Title"}</h1>
<p>{"Body"}</p>
</section>
}

已有 iterable 可以用 spread 子节点:

rsx! {
<div>
{...rows}
</div>
}

使用 {for pattern in iter { ... }} 渲染迭代器:

rsx! {
<ul>
{for (index, item) in items.iter().enumerate() {
<li>{format!("{}: {}", index, item.name)}</li>
}}
</ul>
}

如果循环体里包含有状态元素,需要添加 key={...}id={...}。详见 ID 与 Key

Fragment 可以返回多个根元素:

rsx! {
<>
<div>{"First"}</div>
<div>{"Second"}</div>
</>
}

Fragment 会展开为 vec![...]。如果根元素具体类型不同,通常需要 into_any_element() 或包一层共同父元素。

子节点中可以使用 Rust 表达式:

rsx! {
<div>
{if let Some(user) = &self.user {
rsx! { <span>{user.name.as_str()}</span> }
} else {
rsx! { <span>{"Guest"}</span> }
}}
</div>
}

使用 when 条件转换当前正在构建的元素:

rsx! {
<div
class="px-4 py-2"
when={(active, |el| el.bg(rgb(0x3b82f6)).text_color(rgb(0xffffff)))}
>
{"Status"}
</div>
}

当条件值是 Option 时使用 whenSome

rsx! {
<div whenSome={(custom_width, |el, width| el.w(px(width)))} />
}

使用 whenClass 应用条件静态 class:

rsx! {
<div
class="px-2"
whenClass={(active, "bg-neutral-900 text-white")}
whenClass={(!active, "text-neutral-600")}
/>
}

whenClass 的 class 必须是字符串字面量,并且会拒绝 overflow-scroll 这类有状态 class。涉及 ID 语义时请使用 when

visible={bool} 会映射到 .visible().invisible(),且表达式只求值一次:

rsx! {
<div visible={self.show_sidebar} />
}

grayscale 会映射为 .grayscale(true)

rsx! {
<img src={source} grayscale />
}

styled 会在用户属性前注入标签默认 class:

标签 默认 class
h1 text-3xl font-bold
h2 text-2xl font-bold
h3 text-xl font-bold
h4 text-lg font-bold
h5 text-base font-bold
h6 text-sm font-bold
button, a cursor-pointer
input, textarea px-2 py-1
ul, ol flex flex-col
li flex items-center
p text-base
label text-sm
form flex flex-col gap-4

用户属性会在 styled 之后应用,因此后续值可以覆盖默认值:

rsx! {
<h1 styled class="text-xl">{title.as_str()}</h1>
}