基于 Leptos 信号系统与浏览器事件构建多计数器响应式应用:counters 示例深度解读
2026/9/13 5:45:49 网站建设 项目流程

基于 Leptos 信号系统与浏览器事件构建多计数器响应式应用:counters 示例深度解读

【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos

counters 是 Leptos 仓库中的一个经典 CSR(客户端渲染)示例,用约 100 行 Rust 代码演示了响应式应用的核心骨架:信号(Signal)、派生计算、浏览器事件绑定、动态列表渲染与组件上下文通信。本文将结合该示例的源码、单元测试与端到端测试,完整讲解其实现原理,并给出可直接复现的运行与测试步骤。

示例概览:一个带增删改的多计数器应用

counters 示例 实现了一个基础但完整的响应式应用:页面上有三个操作按钮(Add Counter、Add 1000 Counters、Clear Counters),可以动态新增、批量新增或清空计数器;每个计数器由-1+1两个按钮和一个可编辑输入框构成,支持单独增减或直接输入数值,并能通过x按钮从列表中移除;页面顶部实时汇总「总计数」与「计数器数量」。

它的教学价值在于集中展示了 Leptos 中几乎最常用的几类 API:

  • signal!宏创建响应式信号,ReadSignal/WriteSignal读写分离;
  • ArcRwSignal在动态列表中传递共享可变状态;
  • provide_context/use_context实现组件间通信;
  • For组件高效渲染/复用列表项;
  • on:clickon:input:target等浏览器事件绑定;
  • prop:value属性绑定与RwSignal的双向绑定。

对应源码位于 examples/counters/src/lib.rs,入口挂载逻辑在 examples/counters/src/main.rs。

数据模型:以ArcRwSignal承载动态列表状态

const MANY_COUNTERS: usize = 1000; type CounterHolder = Vec<(usize, ArcRwSignal<i32>)>;

CounterHolder是计数器的底层存储:一个Vec,每个元素是「自增 id + 计数器信号值」的元组。选择ArcRwSignal<i32>而非普通信号,原因在于Vec元素需要被For组件和子组件Counter同时持有——ArcRwSignal提供了可克隆、可共享的引用计数所有权语义,克隆一份引用不会复制内部状态,却能让多个组件读写同一个信号源。其完整实现见 reactive_graph/src/signal/arc_rw.rs(pub struct ArcRwSignal<T>)。

应用级的状态由两个信号管理:

let (next_counter_id, set_next_counter_id) = signal(0); let (counters, set_counters) = signal::<CounterHolder>(vec![]);
  • next_counter_id负责为计数器分配自增 id(作为For的 key 与删除依据);
  • counters持有整个列表,配合set_counters.update(...)原地修改。

在子组件Counter内部,通过RwSignal::from(value)把传入的ArcRwSignal包装成读写一体的RwSignal,从而在视图里直接使用{value}渲染、用value.update(...)修改。

组件上下文:provide_context打通父子通信

由于每个Counter都需要「删除自己」的能力,而删除操作作用于父级持有的counters列表,示例采用了 Leptos 的上下文机制:

#[derive(Copy, Clone)] struct CounterUpdater { set_counters: WriteSignal<CounterHolder>, } // 在 Counters 根组件中 provide_context(CounterUpdater { set_counters }); // 在 Counter 子组件中 let CounterUpdater { set_counters } = use_context().unwrap();

provide_contextWriteSignal注入组件树,子组件通过use_context::<CounterUpdater>()取出同一份句柄,从而在点击x按钮时直接从父级列表中移除自己:

set_counters.update(move |counters| counters.retain(|(counter_id, _)| counter_id != &id))

这比逐层传递回调(callback)更简洁,也避免了组件数量增多时的 prop-drilling 问题。上下文类型的查找在 Leptos 中按组件树向上进行,Copy特性保证了传递的只是廉价句柄。

增删改三个核心操作的事件处理

示例用闭包直接绑定浏览器事件,每个闭包内部通过set_counters.update(...)原地修改信号,从而触发依赖它的视图自动更新:

let add_counter = move |_| { let id = next_counter_id.get(); let sig = ArcRwSignal::new(0); set_counters.update(move |counters| counters.push((id, sig))); set_next_counter_id.update(|id| *id += 1); };

add_many_counters一次性追加 1000 个计数器,clear_counters直接清空列表:

let add_many_counters = move |_| { let next_id = next_counter_id.get(); let new_counters = (next_id..next_id + MANY_COUNTERS).map(|id| { let signal = ArcRwSignal::new(0); (id, signal) }); set_counters.update(move |counters| counters.extend(new_counters)); set_next_counter_id.update(|id| *id += MANY_COUNTERS); }; let clear_counters = move |_| { set_counters.update(|counters| counters.clear()); };

事件绑定语法为on:click=add_counteron:input:target=...。其中on:input:target是 Leptos 提供的「事件目标强类型」写法,让闭包参数ev直接携带target().value()方法,无需手动类型转换:

on:input:target=move |ev| { value.set(ev.target().value().parse::<i32>().unwrap_or_default()) }

输入框使用prop:value=value把信号值作为 DOM 属性value绑定,因此既能在输入时写回信号,又能让+1/-1的修改实时反映到输入框与旁边的<span>{value}</span>

响应式汇总与For动态列表

页面顶部的汇总文本是「派生计算」的典型写法——直接读取counters信号并返回字符串,闭包会被框架追踪依赖,任何计数变化都会触发重算与局部更新:

<span><For each=move || counters.get() key=|counter| counter.0 children=move |(id, value)| { view! { <Counter id value/> } } />

key使用自增 id 而非列表下标,保证了「先删除后新增」等场景下条目的稳定复用;children接收(id, value)并转交给Counter组件渲染。

完整运行指南:从 Trunk 快速启动到 Cargo Make

快速启动

counters 是纯客户端渲染示例,最简启动命令只需 Trunk:

trunk serve --open

执行后 Trunk 会编译 wasm 并在浏览器打开应用。其入口 HTML 为 examples/counters/index.html,通过data-trunk rel="rust"指令让 Trunk 完成 Rust 到 wasm 的构建与注入。

Cargo Make 方式

也可用cargo-make走与 CI 一致的流程。前置要求:

  • 安装 Nightly Rust:rustup toolchain install nightly
  • 添加 wasm 目标:rustup target add wasm32-unknown-unknown(examples/counters/rust-toolchain.toml 已声明targets = ["wasm32-unknown-unknown"]
  • 安装 Trunk:cargo install trunk
  • 安装 Cargo Make:cargo install --force cargo-make

然后进入示例目录执行:

cargo make ci # 初始化并测试示例 cargo make start # 启动开发服务器(默认 http://127.0.0.1:8080) cargo make stop # 停止由 start 启动的进程

examples/counters/Makefile.toml 通过extend引入了 examples/cargo-make/main.toml、wasm-test.tomltrunk_server.tomlplaywright-trunk-test.toml等公共任务定义,因此cistartstop等任务开箱即用。更完整的示例运行说明见 examples/README.md。

依赖与工具链说明

examples/counters/Cargo.toml 中,示例通过路径依赖指向仓库内的 Leptos 主 crate,并启用csr特性:

leptos = { path = "../../leptos", features = ["csr"] } console_error_panic_hook = "0.1.7" [dev-dependencies] wasm-bindgen-test = "0.3.42" wasm-bindgen = "0.2.93" web-sys = "0.3.70"

其中console_error_panic_hook让 wasm 中的 panic 信息输出到浏览器控制台(在 examples/counters/src/main.rs 中调用console_error_panic_hook::set_once()启用),调试阶段非常有用;wasm-bindgen-testweb-sys用于浏览器内的单元测试。

测试矩阵:wasm 单元测试与 Playwright E2E

该示例同时提供两层测试,覆盖了从信号逻辑到真实浏览器交互的完整链路。

浏览器内单元测试(wasm-bindgen-test)

examples/counters/tests/web.rs 通过wasm_bindgen_test_configure!(run_in_browser)在真实浏览器环境中运行测试。核心用例inc完整模拟了用户操作并逐段断言 DOM:

  1. 挂载Counters组件,断言初始 HTML 为「3 个按钮 + Total: 0 from 0 counters」;
  2. 连续点击 3 次 Add Counter,断言出现 3 个<li>计数器;
  3. 分别点击第 1、2、3 个计数器的+1按钮各 1、2、3 次,断言Total: 6且三个计数依次为 1、2、3;
  4. 点击第一个计数器的x按钮,断言列表剩 2 个且总数变为 5。

测试通过leptos::task::tick().await等待一次微任务刷新,再读取inner_html()比对,验证了「信号更新 → DOM 自动同步」的响应式闭环。

端到端测试(Playwright)

e2e 目录下按功能拆分了多个 spec 文件:view_countersadd_counteradd_1k_countersincrement_countdecrement_countenter_countclear_countersremove_counter。以 add_counter.spec.ts 为例,它通过共享的CountersPage封装(fixtures/counters_page.ts)调用addCounter()三次,然后断言计数器数量文本为"3"

这些测试配合cargo makeplaywright-trunk-test任务运行,验证了按钮点击、输入框输入、批量新增与清空等完整用户路径在真实浏览器中的行为。

小结

counters 示例是一份「小而全」的 Leptos 入门范本:从signal!ArcRwSignalprovide_context/use_contextForon:click/on:input:target/prop:value,几乎覆盖了编写响应式客户端应用的全部核心 API;同时其双层测试结构(wasm 单元测试 + Playwright E2E)也为后续项目如何验证响应式逻辑提供了可复制的模式。想进一步研究底层信号实现,可继续阅读 reactive_graph/src/signal/arc_rw.rs;想对比同主题的完整交互,可参考仓库内其他示例(如 counter、counter_isomorphic、todomvc)。

【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询