Leptos Counter 示例全解析:用 Rust 与 WASM 构建客户端渲染计数器应用
【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos
本文以 leptos 仓库中的 examples/counter 为例,完整讲解如何在客户端渲染(CSR)模式下,用 Rust 编写组件、使用响应式信号(signal)与事件处理,并通过 Trunk 构建为 WebAssembly 应用运行在浏览器中。读完本文,你将掌握 Leptos 最核心的#[component]组件写法、signal读写模式、view!宏语法、浏览器端挂载方式,以及如何在真实浏览器中对其做 DOM 级自动化测试。
示例概览:一个最简单的 Leptos 应用长什么样
examples/counter是 leptos 仓库中最基础的入门示例,它在纯客户端渲染的 WebAssembly 应用中实现了一个计数器:包含 "Clear"、"−1"、"+1" 三个按钮和一个显示Value: N!的文本节点。整个示例由三部分构成:
| 文件 | 作用 |
|---|---|
| examples/counter/src/lib.rs | 定义可复用的SimpleCounter组件(组件库部分) |
| examples/counter/src/main.rs | 应用入口,初始化日志并把组件挂载到页面 |
| examples/counter/index.html | Trunk 构建的 HTML 模板,声明 wasm 与图标资源 |
| examples/counter/tests/web.rs | 基于wasm-bindgen-test的浏览器内测试 |
| examples/counter/Cargo.toml | 依赖与 release 优化配置 |
从源码结构看,这是 leptos 官方推荐的"库 + 入口"分离结构:把组件放在lib.rs里导出,main.rs只负责挂载,这样组件可以在测试中直接复用(tests/web.rs 中正是通过use counter::*引入组件进行测试的)。
环境准备与快速启动
示例 README 明确给出了最简启动命令:
trunk serve --open该命令会启动 Trunk 开发服务器并自动在浏览器中打开页面。由于这是一个 CSR 应用,trunk负责把 Rust 源码编译成wasm32-unknown-unknown目标并作为前端资源提供。完整的运行前提如下:
- 安装 Rust 工具链并切换到 nightly(示例目录下的 rust-toolchain.toml 已声明
targets = ["wasm32-unknown-unknown"],确保该 target 已通过rustup target add wasm32-unknown-unknown安装); - 安装 Trunk:
cargo install trunk; - 进入示例目录执行
trunk serve --open。
更详细的通用示例运行说明见 examples/README.md。此外每个示例的 Makefile.toml 都extend了 examples/cargo-make/main.toml 等任务文件,因此也可以用cargo-make执行cargo make ci、cargo make start、cargo make stop来搭建、运行和清理示例进程,这是 CI 中使用的可选方式。
index.html:Trunk 如何知道要编译什么
<!doctype html> <html> <head> <link>use leptos::prelude::*; /// A simple counter component. /// /// You can use doc comments like this to document your component. #[component] pub fn SimpleCounter( /// The starting value for the counter initial_value: i32, /// The change that should be applied each time the button is clicked. step: i32, ) -> impl IntoView { let (value, set_value) = signal(initial_value); view! { <div> <button on:click=move |_| set_value.set(0)>"Clear"</button> <button on:click=move |_| *set_value.write() -= step>"-1"</button> <span>"Value: " {value} "!"</span> <button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button> </div> } }要点拆解:
- 组件即函数:
#[component]把普通函数转换为 Leptos 组件,函数的普通参数initial_value: i32、step: i32就是组件的属性(props),在使用处通过<SimpleCounter initial_value=0 step=1/>传入; - 返回
impl IntoView:组件返回值是实现了IntoViewtrait 的类型,view!宏生成的HtmlElement满足这一约束; - doc 注释即组件文档:源码注释说明你可以在组件上用文档注释记录用途,IDE 与文档生成工具都能直接受益;
signal(initial_value):创建一个由i32初始值驱动的响应式信号,返回读写元组(ReadSignal, WriteSignal)。signal是 leptos 响应式系统的核心 API,其实现位于 reactive_graph/src/signal.rs,底层由reactive_graph提供信号读取/写入的同步追踪机制,UI 依赖的信号变化会自动触发相关视图更新。
三种信号写入方式
计数器示例在一个组件里演示了WriteSignal的三种典型用法,这也是实战中最常用的三种写入 API:
| 写法 | 代码 | 行为 |
|---|---|---|
set() | set_value.set(0) | 直接整体覆盖为0,用于 "Clear" 按钮 |
write() | *set_value.write() -= step | 返回&mut i32,原地修改值再写回,用于 "−1" 按钮 |
update() | set_value.update(\|value\| *value += step) | 传入闭包对当前值做任意变换,用于 "+1" 按钮 |
视图模板:view!宏与事件绑定
view!是 leptos 的 JSX 风格模板宏,在src中通过leptos::prelude::*引入。以上组件为例:
view! { <div> <button on:click=move |_| set_value.set(0)>"Clear"</button> <span>"Value: " {value} "!"</span> ... </div> }- 文本节点:
"Clear"这类字符串字面量直接渲染为文本;{value}表示把ReadSignal<i32>作为响应式表达式插入,当信号值变化时该文本节点会自动更新——这就是"响应式"在视图层的体现; - 事件绑定:
on:click=move |_| ...是标准事件绑定语法,on:后跟 DOM 事件名,值为闭包。由于闭包要捕获set_value,用move关键字转移所有权;_是事件参数(这里不需要MouseEvent本身); - 表达式插值:
{value}之外,"Value: "与"!"是静态文本,三者共同组成一个动态文本节点。
入口与挂载:main.rs 的职责
use counter::SimpleCounter; use leptos::prelude::*; pub fn main() { _ = console_log::init_with_level(log::Level::Debug); console_error_panic_hook::set_once(); mount_to_body(|| { view! { <SimpleCounter initial_value=0 step=1/> } }) }入口做了三件事:
- 初始化日志:
console_log::init_with_level(log::Level::Debug)把log输出转发到浏览器 console,便于调试; - 安装 panic 钩子:
console_error_panic_hook::set_once()让 Rust panic 以可读形式显示在 console 中,否则 wasm 中的 panic 信息会很难定位; - 挂载根组件:
mount_to_body(|| view! { <SimpleCounter initial_value=0 step=1/> })把组件渲染进<body>。mount_to_body定义在 leptos/src/mount.rs(pub fn mount_to_body),同文件还提供mount_to(挂载到指定元素并返回UnmountHandle,测试中用来挂到测试容器)与mount_to_renderer等挂载 API。
main.rs里的main()不是fn main() -> Result<(), Box<dyn Error>>这样的服务器入口,而是会被编译为 wasm 并作为模块入口导出的函数,配合wasm-bindgen由 Trunk 处理。
依赖与构建优化:Cargo.toml 解读
[package] name = "counter" version = "0.1.0" edition = "2021" [profile.release] opt-level = 'z' codegen-units = 1 lto = true [dependencies] leptos = { path = "../../leptos", features = ["csr"] } console_log = "1.0" log = "0.4.22" console_error_panic_hook = "0.1.7" gloo-timers = { version = "0.3.0", features = ["futures"] } [dev-dependencies] wasm-bindgen = "0.2.93" wasm-bindgen-test = "0.3.42" web-sys = "0.3.70"leptos = { path = "../../leptos", features = ["csr"] }:以仓库内路径依赖当前主线 leptos,并开启csrfeature——它决定了本应用按纯客户端渲染模式编译,不包含 SSR/hydration 相关代码路径;opt-level = 'z'、codegen-units = 1、lto = true:针对 wasm 体积与性能的发布优化:z以体积优先优化、单 codegen unit 配合全量 LTO 允许跨 crate 内联,显著减小最终 wasm 体积并提升运行性能;gloo-timers:提供set_timeout等定时器能力(带 futures feature),示例依赖中保留它便于演示异步能力;- dev-dependencies:
wasm-bindgen、wasm-bindgen-test、web-sys用于在浏览器里做 DOM 测试。
浏览器内测试:wasm-bindgen-test 实战
tests/web.rs展示了如何在真实浏览器里验证组件行为。测试使用wasm_bindgen_test_configure!(run_in_browser),让测试跑在浏览器环境(而不是 node),并通过wasm-bindgen-test提供的#[wasm_bindgen_test]宏声明异步测试。
clear 测试的核心流程:
- 创建测试容器
<section>并挂到document.body; - 用
mount_to(test_wrapper, || view! { <SimpleCounter initial_value=10 step=1/> })渲染初始值为 10 的计数器,注意这里用的是 mount.rs 中的mount_to,可挂载到任意元素; - 用
query_selector("button")拿到 "Clear" 按钮并click(); - 调用
tick().await让响应式系统完成一轮微任务刷新(注释明确说明:响应式建立在异步系统之上,DOM 不会同步更新,必须让 effect 跑完); - 用
outer_html()/inner_html()与"用signal(0)重新渲染的期望视图"对比,验证点击后值归零。
inc 测试则演示了更朴素的 DOM 操作方式:通过first_child()/next_sibling()遍历按钮与文本节点,连点 "+1" 两次后断言text.text_content() == "Value: 2!",连点 "−1" 四次后断言"Value: -2!",再点 "Clear" 断言"Value: 0!"。其中还展示了一个实用的测试技巧:用signal(n)构造期望文本、再.into_view().build().outer_html()生成对比 HTML——注释指出into_view()在这里只是"使用常规 DOM 渲染器"的便捷写法,视图是惰性的,build()才会真正创建 DOM 元素并返回ElementState。
运行与验证
# 进入示例目录 cd examples/counter # 启动 Trunk 开发服务器并自动打开浏览器 trunk serve --open # 如需运行浏览器内测试(以 wasm-bindgen-test 方式) # 需要先为 wasm32-unknown-unknown 目标安装测试 runner(如 wasm-bindgen-test-runner)打开页面后,你将看到四个元素:"Clear"、"−1"、Value: 0!、"+1"。点击 "+1" 文本实时变为Value: 1!;连续点击 "−1" 变为负数;点击 "Clear" 立即归零。整个过程中没有页面刷新——所有状态变更都由 leptos 响应式信号驱动,最终以最小 DOM 更新的方式同步到视图。
小结
examples/counter虽然只有几十行代码,却完整覆盖了 leptos 开发的核心链路:#[component]组件定义与属性传参、signal响应式状态、view!模板与事件绑定、CSR 模式下的入口挂载,以及基于wasm-bindgen-test的真实浏览器 DOM 测试。以此为起点,你可以沿着仓库中的 examples/README.md 继续探索其他示例(如counter_isomorphic、counter_without_macros、todo_app_sqlite等),把同样的组件与响应式模式扩展到 SSR、路由、服务端函数等更复杂场景。
【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考