2025年7月

1)设置console_error_panic_hook

默认情况下,在浏览器中运行 WASM 代码时发生的panic只会在浏览器中抛出一个错误,并显示一条无用的消息Unreachable executed,和 指向 WASM 二进制文件的堆栈跟踪。

通过console_error_panic_hook,您可以得到 精确到 Rust 源代码中的某一行的Rust 堆栈跟踪。

设置起来非常简单:

(1)在您的项目的根目录下运行 cargo add console_error_panic_hook
(2)在您的main function中,添加console_error_panic_hook::set_once();

_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();

2) Editor Autocompletion inside #[component] and #[server]

由于宏的性质,rust-analyzer 很难做到正确的自动完成和其他支持。

如果您在编辑器中使用这些宏时遇到问题,可以明确指示 rust-analyzer 忽略某些 proc 宏。

尤其对于 #[server]这种 只 注释 (annotates)函数体但实际上并不转换函数体内部任何内容的宏来说,这非常有帮助。

从 Leptos 0.5.3 版本开始,rust-analyzer 已添加对#[component]宏的支持。但如果您遇到问题,可以将 #[component] 添加到宏忽略列表中。请注意,这意味着 rust-analyzer 无法识别您的组件 props,而props可能会在 IDE 中生成其自身的错误或警告。

VSCode的settings.json

"rust-analyzer.procMacro.ignored": {
    "leptos_macro": [
        // optional:
        // "component",
        "server"
    ],
}

带 cargo-leptos 的 VSCode的settings.json

"rust-analyzer.procMacro.ignored": {
    "leptos_macro": [
        // optional:
        // "component",
        "server"
    ],
},
// if code that is cfg-gated for the `ssr` feature is shown as inactive,
// you may want to tell rust-analyzer to enable the `ssr` feature by default
//
// you can also use `rust-analyzer.cargo.allFeatures` to enable all features
"rust-analyzer.cargo.features": ["ssr"]


3) 与 Rust Analyzer 一起使用leptosfmt (可选设置)

leptosfmt是 Leptos 的 view!宏 的格式化程序(您通常会在宏里面编写 UI 代码)。由于该view!宏支持使用“RSX”(类似 JSX)风格编写 UI,因此 cargo-fmt 很难自动格式化宏内的代码view!。

leptosfmt是一个可以解决您的格式化问题并让您的 RSX 风格 UI 代码保持美观整洁的 crate!

leptosfmt可以通过命令行或代码编辑器安装和使用:

首先,安装该工具 cargo install leptosfmt

如果您只想使用命令行中的默认选项,只需从项目根目录运行

leptosfmt ./*/.rs

即可使用leptosfmt格式化所有 rust 文件。

与 Rust Analyzer 一起使用
(1)在项目的根目录下放置 rustfmt.toml , 内容为

edition = "2021"

(2) 配置 Rust Analyzer

方法一:在项目的根目录下防止 rust-analyzer.toml, 内容为

[rustfmt]
overrideCommand = ["leptosfmt", "--stdin", "--rustfmt"]
# (optional) other config...

这种方法与你使用 什么编辑器无关,所以更值得推荐。
唯一需要注意的是, rust-analyzer的版本最好是 2024-06-10 之后

方法二:

对于 VSCode 用户,我建议使用工作区设置(CMD + shift + p -> 打开工作区设置),这样您只能leptosfmt为使用 leptos 的工作区进行配置。

打开您的工作区设置并添加以下配置:

{
  "rust-analyzer.rustfmt.overrideCommand": ["leptosfmt", "--stdin", "--rustfmt"]
}

在 leptosfmt.toml 可以设置其他选项

max_width = 100 # Maximum width of each line
tab_spaces = 4 # Number of spaces per tab
indentation_style = "Auto" # "Tabs", "Spaces" or "Auto"
newline_style = "Auto" # "Unix", "Windows" or "Auto"
attr_value_brace_style = "WhenRequired" # "Always", "AlwaysUnlessLit", "WhenRequired" or "Preserve"
macro_names = [ "leptos::view", "view" ] # Macro names which will be formatted
closing_tag_style = "Preserve" # "Preserve", "SelfClosing" or "NonSelfClosing"

# Attribute values can be formatted by custom formatters
# Every attribute name may only select one formatter (this might change later on)
[attr_values]
class = "Tailwind" # "Tailwind" is the only attribute value formatter available for now


4) 在开发过程中使用 --cfg=erase_components

Leptos 0.7 对渲染器进行了一些更改,使其更加依赖类型系统。对于较大的项目,这可能会导致编译时间变慢。
--cfg=erase_components 在开发过程中使用自定义配置标志可以缓解大部分编译时间的拖慢。
(这会删除一些类型信息,以减少编译器的工作量和调试信息,但会增加二进制文件大小和运行时开销,因此最好不要在release模式下使用。)

在命令行启用,就是运行trunk是带环境变量

RUSTFLAGS="--cfg erase_components" trunk serve

或者

RUSTFLAGS="--cfg erase_components" cargo leptos watch

又或者,直接在 .cargo/config.toml 进行配置

[target.wasm32-unknown-unknown]
rustflags = [
   "--cfg",
   "erase_components",
]

从上面这些来看, Leptos比sycamore更完善一些

  1. 安装rust

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

  2. 添加wasm编译器

    rustup target add wasm32-unknown-unknown

  3. 安装trunk

    cargo install trunk

  4. 新建项目

    cargo init leptos-tutorial
    cd leptos-tutorial
    cargo add leptos --features=csr

在项目的根目录创建 index.html

<!DOCTYPE html>
<html>
  <head></head>
  <body></body>
</html>

用下面的内容替换src/main.rs

use leptos::prelude::*;

fn main() {
    leptos::mount::mount_to_body(|| view! { <p>"Hello, world!"</p> })
}

在项目的根目录运行

trunk serve --open

Trunk 会自动编译你的应用并在默认浏览器中打开它。

响应式状态(Reactive state)由响应式节点(reactive nodes)构建而成。响应式节点最简单的例子就是信号。它可以被认为是一个类型的简单包装器,用于跟踪何时被访问以及何时被更新。要创建新的信号,请使用create_signal函数。

let signal = create_signal(123);

信号内部的值可以读取或写入。默认情况下,该Signal::get方法会复制信号内部的值,因此包装类型必须实现Copy。如果您有不可复制的类型,则可以改用Signal::get_clone。

let signal = create_signal(123);
// Should print `123`.
console_log!("{}", signal.get());

// Update the signal with a new value.
signal.set(456);

// Should print `456`.
console_log!("{}", signal.get());
// `Signal<T>` also implements `Display` so this is the same as the above.
console_log!("{signal}");



我们在这里使用console_log!而不是println!

因为在wasm32-unknown-unknown target上,Rust 标准库无法访问 Web API,因此println!不会执行任何操作。console_log!宏会调用javascript的console.log将值打印到 JavaScript 控制台的函数。

Signal::set 只接受普通(不可变)引用,而不是可变引用。这也是 Rust中的cell 类型的一个示例,提供了内部可变性。

effect 是每次其依赖项之一更新时都会被调用的函数。每当在函数内部使用依赖项时,依赖项都会被自动跟踪。

let signal = create_signal(123);
create_effect(move || {
    // Using `.get(...)` automatically tracks this signal as a dependency.
    let value = signal.get();
    console_log!("{value}");

    // Or we can use the shorter: `console_log!("{signal}");`
});
signal.set(456);
signal.set(789);

这将在终端上打印以下内容:

123
456
789

Memos 和派生状态
memos 存储派生信号的值,并且仅在其依赖项发生变化时更新该值。

let signal = create_signal(1);
let derived = create_memo(move || expensive_computation(signal.get()));

// The memo will only call the closure once so long as `signal` hasn't changed.
let foo = derived.get();
let bar = derived.get();

// This also triggers `derived` to recompute its value.
signal.set(2);

create_memo钩子返回一个ReadSignal,它是一个只能被读取而不能写入的信号。该Signal类型会自动解引用为ReadSignal,因此你可以在任何需要 ReadSignal的地方 使用 Signal 。

effects实际上是通过memos实现的。从语义上讲,这并不完全正确,因为我们使用效果来创建 side-effects,而使用memos来执行纯计算。然而,从实现的角度来看,effect仅仅是一个不返回值的memo。

响应式视图

现在我们已经准备好了所有这些响应式机制,让我们看看如何使用它来管理应用的状态。我们想要做的是将状态存储在响应式节点中,然后让 UI 自动与状态同步。

Sycamore 让这一切变得极其简单。只需在view!宏中插入一个片段,访问响应式状态,UI 就会自动更新以适应任何状态变化。

let counter = create_signal(1);

view! {
    p { "Count: " (counter) }
}

其本质上是这样的:

let counter = create_signal(1);

let text1 = document().create_text_node("Count: ");
let text2 = document().create_text_node(counter.get().to_string());

create_effect(move || {
    // This effect is automatically run whenever `counter` is changed.
    text2.set_text_content(counter.get().to_string());
});

我们当然也可以在视图中使用派生状态:

let counter = create_signal(1);
let doubled = create_memo(move || counter.get() * 2);

view! {
    p { "Count: " (counter) }
    p { "Doubled: " (doubled) }
}

事件处理程序