响应式状态(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) }
}

事件处理程序

0.安装rust

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

在中国大陆用替代方法

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust.sh
sed -i 's|static.rust-lang.org|mirrors.ustc.edu.cn/rust-static|g' rust.sh
export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup
chmod a+x rust.sh
./rust.sh

安装完成后

echo "RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup"  >> ~/.cargo/env
  1. 添加wasm编译器
rustup target add wasm32-unknown-unknown

  1. 安装Trunk

Trunk是Sycamore的构建工具,类似于Javascript前端开发中的webpack

cargo install --locked trunk

Sycamore开发团队也在尝试开发专用的构建工具,但目前还是推荐使用 Trunk

  1. 建立一个新的sycamore项目
cargo new hello-sycamore
cd hello-sycamore
cargo add [email protected]

  1. 编辑项目中的 src/main.rs, 用下面的内容完全替代它
use sycamore::prelude::*;

fn main() {
    sycamore::render(|| "Hello, world!".into());
}
  1. 在项目的根目录下创建 index.html, 内容为
<!DOCTYPE html>
<html>
    <head></head>
    <body></body>
</html>
  1. 运行
trunk serve

将会构建你的应用,并在监听在 本机的8080端口,你用浏览器打开 http://localhost:8080 即可看到

这个域名注册于2006年7月19日,当时我在一家芯片公司上班。

最早是用的wordpress,放在Dreamhost上

后来搬迁在namechep的VPS上

2019年迁移到github

2021年用另外一个博客程序,写了两篇文章放在racknerd

2025年用typecho重建,放在cloudflare