2026/6/19 20:14:03

Dioxus 的 `rsx!` 语法:如果你会 React,上手确实特别快

Dioxus 的 `rsx!` 语法:如果你会 React,上手确实特别快 前言上一篇我们把第一个 Dioxus 项目跑起来了。这一篇先不讲 CLI也不碰状态管理只聊一件事rsx!到底该怎么理解。我一开始对它的判断也很简单这不就是 Rust 版 JSX 吗。但真写了输入框、按钮、列表、条件分支之后我发现这个理解只能对一半。它确实像 JSX但更准确一点说它是一套长得像 HTML 的 Rust UI 语法。这个认知早点拧过来后面学组件、路由、Signals 都会轻松不少。要是没拧过来你就会一直下意识去找三元表达式、event.target.value越写越别扭。1.rsx!到底是什么先说结论rsx!不是模板引擎它就是一个 Rust 宏。官方文档在 Dioxus 0.7 里说得很直接RSX 是用来构建 Dioxus UI 的语法底层会被过程宏展开成 Rust 代码不是单独的模板文件。所以你看到的rsx!{h1{Welcome to Dioxus!}p{Hello, {name}}}它不是“在 Rust 里塞了一段 HTML”更像是“用更短的方式描述一棵 UI 树”。这也是它和传统模板系统差别最大的地方模板系统通常有自己的一套语法规则rsx!直接活在 Rust 语法环境里花括号里的内容不是特殊模板语法而是普通 Rust 表达式举个例子usedioxus::prelude::*;fnmain(){dioxus::launch(App);}#[component]fnApp()-Element{letnameDioxus;rsx!{div{h1{你好{name}}p{这段 UI 不是模板文件而是 Rust 代码的一部分。}}}}这段代码真正重要的不是“标签看起来像 HTML”而是你已经在一个 Rust 函数里把 UI 写完了。2. 为什么 React 开发者会觉得它很眼熟如果你写过 React第一眼看到rsx!大概率会觉得挺顺。因为它和 JSX 有几个很像的地方。2.1 都是声明式 UI你不需要一行行去创建节点、设置文本、挂事件。你只描述“页面应该长什么样”状态变化后框架帮你更新。2.2 都是标签结构 属性 插值举个例子下面这段 Dioxus 代码基本不用翻译rsx!{section{class:hero,h1{欢迎回来{user_name}}p{今天继续改你的 Dioxus 页面。}}}你能一眼看出来section、h1、p是元素class是属性{user_name}是动态插值2.3 事件也是贴在元素上button{onclick:move|_|println!(clicked),点我}这和 React 的心智很接近事件写在元素旁边逻辑也跟着组件走。所以如果你是从 React 过来的rsx!最舒服的地方不是它和 JSX 一模一样而是你不用重新适应一套很陌生的 UI DSL。3. 它和 JSX 最关键的差异花括号里是 Rust不是 JavaScript这句话我建议直接记住rsx!像 JSX但花括号里跑的是 Rust 表达式。这会直接影响你写条件、写列表、写事件、写字符串拼接的方式。3.1 字符串插值走 Rust 的格式化规则letworldearth;rsx!{h1{Hello {world}!}}这个感觉更接近 Rust 的format!不是 JavaScript 模板字符串那套。3.2 复杂表达式可以直接塞进去举个例子如果你想把字符串大写后再渲染rsx!{span{{format!(当前用户: {},current_user_name()).to_uppercase()}}}只要这个表达式最后能变成 Dioxus 能渲染的东西就能塞进来。3.3match、if/else都是正经 Rust 写法这个地方往往是 React 开发者第一次明显觉得“哦这里已经不是 JSX 了”。在 React 里大家太习惯三元表达式了const screen authenticated ? Dashboard / : Login /;到了 Dioxus这里就老老实实按 Rust 来写letscreenifauthenticated(){rsx!{Dashboard{}}}else{rsx!{Login{}}};rsx!{main{{screen}}}如果分支再多一点match往往比 JSX 还顺手letbadgematchstatus.as_str(){successrsx!{span{class:ok,已完成}},pendingrsx!{span{class:pending,进行中}},_rsx!{span{class:draft,草稿}},};rsx!{div{{badge}}}说白了Dioxus 没有打算把 Rust 伪装成 JavaScript。它只是给 Rust UI 写法套了一层更像前端的外观。4. 条件渲染怎么写别找三元也别硬套这一段是rsx!里最值得单独适应的地方。官方文档给了两种思路。4.1 先算出一个Element再插进去letpaneliflogged_in(){rsx!{UserPanel{}}}else{rsx!{GuestPanel{}}};rsx!{div{{panel}}}这个写法很稳逻辑稍微复杂一点时尤其好用。4.2 直接在rsx!里写内联ifDioxus 也支持直接在rsx!里写内联ifrsx!{div{iflogged_in(){你已经登录了}else{你还没有登录}}}再举个更接近日常页面的例子rsx!{section{h2{发布设置}ifis_saving(){p{class:tips,正在保存...}}ifsave_error().is_some(){p{class:error,保存失败请稍后重试}}}}这里有两个细节可以顺手记一下if的分支体是 RSX不是普通 Rust 语句块即使没有elseDioxus 也能正常渲染缺省分支会变成一个占位节点所以别再下意识往 React 那套上靠{loading Spinner /}在 Dioxus 里直接写if loading() { Spinner {} }通常更干脆。5. 列表渲染怎么写map能用内联for也能用如果说条件渲染最容易让 React 用户卡一下那列表渲染反而是最容易上手的部分。因为 Dioxus 两种都支持。5.1 直接用迭代器lettodosvec![读文档,改按钮,接路由];rsx!{ul{{todos.iter().map(|todo|rsx!{li{{todo}}})}}}这很像 JSX 里的array.map(...)。5.2 用 Dioxus 提供的内联for官方文档还给了一个更顺手的写法lettodosvec![读文档,改按钮,接路由];rsx!{ul{fortodointodos.iter(){li{{todo}}}}}这段我个人挺喜欢。原因很简单它比{items.iter().map(...)}更像在读结构不像在读一串链式调用。5.3 循环里要临时算东西怎么办也可以直接包一层表达式rsx!{ul{foruserinusers.iter(){{letlabelformat!({} ({}),user.name,user.role);rsx!{li{{label}}}}}}}这一点也很能体现rsx!的脾气你不是在写模板循环你是在 RSX 里继续写 Rust。6. 属性绑定怎么写语义像 HTML写法像 Rust属性这一块Dioxus 的规则其实很统一属性名后面跟冒号值写 Rust 表达式。最基础的是这种rsx!{input{class:search-input,id:keyword,placeholder:搜索文章}}6.1 动态值直接写表达式#[component]fnSearchBox()-Element{letmutkeyworduse_signal(String::new);rsx!{input{value:{keyword},placeholder:输入关键字,oninput:move|evt|keyword.set(evt.value())}}}这段代码里最容易让人停一下的通常是这一句oninput:move|evt|keyword.set(evt.value())因为很多人脑子里会先冒出event.target.value。但别忘了你现在已经不在 JS 世界里了。事件参数是 Dioxus 自己的 Rust 类型取值方式自然也和浏览器原生事件对象不一样。6.2 布尔属性和条件属性也可以直接算button{disabled:is_saving(),onclick:move|_|save(),保存}只要最终能算出属性需要的值就可以直接写进去。6.3class可以写多次条件拼样式会更自然这一点挺实用。button{class:btn,class:ifis_active(){btn-active},class:ifis_large(){btn-large},切换状态}比起手动拼一长串 class 字符串这种写法干净很多尤其是状态一多的时候。7. 常见 HTML 元素和属性在 Dioxus 里怎么落地如果你现在脑子里想的是“那我以前那段 HTML 到底该怎么抄过来”可以先看这张最常用的对照表。场景HTML / JSX 习惯Dioxus 写法classclasscard/classNamecardclass: cardididheroid: hero文本插值{name}你好{name}或{name}事件onClick{...}onclick: move输入框取值event.target.valueevt.value()条件渲染cond ? A : Bif cond { rsx!{ A {} } } else { rsx!{ B {} } }列表渲染items.map(...){items.iter().map(...)}或for item in items.iter()内联样式stylecolor:redstyle: color: red;这张表当然覆盖不了全部但把大部分静态 HTML 和基础交互页面迁过来已经够用了。8. 样式怎么写内联、CSS 文件、Tailwind 都能接这一块是 Dioxus 很讨喜的地方。它没有自己再发明一套样式系统而是老老实实站在 HTML CSS 这边。8.1 最直接的是内联stylersx!{div{style:background-color: #1d4ed8; color: white; padding: 16px; border-radius: 12px;,这是一个带内联样式的卡片}}8.2 也可以直接写单个 CSS 属性这点很多人第一次看到时会有点惊喜rsx!{div{background_color:#1d4ed8,color:white,padding:16px,border_radius:12px,这也是合法写法}}也就是说CSS 属性名可以改成snake_case直接写进 RSX。8.3 真正做项目还是建议样式表分出去官方文档在 0.7 里推荐用asset!()和document::Stylesheet引入样式文件usedioxus::prelude::*;staticMAIN_CSS:Assetasset!(/assets/main.css);#[component]fnApp()-Element{rsx!{document::Stylesheet{href:MAIN_CSS}div{class:page,h1{Hello Dioxus}}}}对应的assets/main.css就是你熟悉的 CSS.page{width:min(720px,calc(100vw - 32px));margin:48px auto;padding:32px;border-radius:20px;background:white;}8.4 Tailwind 也能接而且官方就是这么支持的如果你已经是 Tailwind 用户Dioxus 这边基本没有额外心智成本。类名照写rsx!{div{class:flex flex-col gap-4 rounded-2xl bg-white p-6 shadow-lg,h1{class:text-2xl font-bold,Rust Dioxus}p{class:text-slate-600,这一段就是标准的 Tailwind class。}}}按 Dioxus 0.7 官方文档的做法你在项目根目录放一个tailwind.cssimporttailwindcss;source./src/**/*.{rs,html,css};然后在应用里引入生成后的样式rsx!{document::Stylesheet{href:asset!(/assets/tailwind.css)}}这条路为什么舒服因为你没有被迫去学什么“Rust 专属样式系统”。你原来会的 CSS、Tailwind、选择器、布局思路大部分都还能接着用。9. 从 HTML / React 迁过来时最容易卡住的 4 个点9.1 不要把rsx!当成模板语言它长得像模板但你应该把它看成“Rust 里的声明式 UI 宏”。一旦这么看很多写法就顺了。比如循环不是“模板循环标签”而是迭代器或者for条件也不是什么“模板指令”就是if和match。9.2 少找event.target.value输入事件最容易暴露思维惯性。看到下面这句别慌oninput:move|evt|name.set(evt.value())它不是奇怪就是更 Rust 一点。9.3 复杂逻辑先在外面算再往rsx!里塞很多人 JSX 写久了习惯把一大坨条件和拼接都塞进标记里。到 Dioxus 这边我反而建议你更 Rust 一点letheaderifis_editing(){编辑文章}else{发布文章};letsubmit_textifis_saving(){保存中...}else{保存};然后再渲染rsx!{h1{{header}}button{{submit_text}}}这样读起来通常会更轻松。9.4 有现成 HTML 时可以用dx translate官方文档还提到一个挺实用的工具dx translate。如果你手里已经有一段 HTML可以先自动翻成 RSX再手动整理。迁移现有页面或者先拿设计稿生成的 HTML 搭个架子这个命令都能省你不少时间。总结rsx!最容易让人误解的地方是它看起来像“Rust 版 JSX”但真正决定手感的其实是后半句它本质上还是 Rust。如果把这篇压成几句人话大概就是rsx!不是模板它是 Rust 宏。标签、属性、事件这些地方很像 JSX所以 React 用户上手会快。条件、循环、表达式这些地方遵循 Rust 思维而不是 JavaScript 思维。样式层面直接站在 HTML CSS Tailwind 这边不需要重新学一套新规则。下一篇我会接着写 Dioxus 的响应式状态管理use_signal、use_memo、use_effect到底怎么配合为什么它和 React 的useState不是一回事。如果你已经在写 Dioxus最开始让你别扭的是条件渲染、事件绑定还是列表写法评论区聊聊。