一个基础组件
那个“Hello, world!”是一个非常简单的例子。 让我们继续学习更像普通应用程序的内容。
首先,让我们编辑 main
函数,使其不再渲染整个应用程序,而只是渲染一个 <App/>
组件。 组件是大多数 Web 框架中组合和设计的基本单元,Leptos 也不例外。
从概念上讲,它们类似于 HTML 元素:它们代表 DOM 的一部分,具有独立的、定义的行为。 与 HTML 元素不同,它们采用 PascalCase
形式,因此大多数 Leptos 应用程序都将以 <App/>
组件之类的内容开头。
fn main() {
leptos::mount_to_body(|| view! { <App/> })
}
现在让我们定义 <App/>
组件本身。 因为它相对简单,
我将先完整地展示它,然后逐行解释。
#[component]
fn App() -> impl IntoView {
let (count, set_count) = create_signal(0);
view! {
<button
on:click=move |_| {
// 在稳定版本中,这是 set_count.set(3);
set_count(3);
}
>
"Click me: "
// 在稳定版本中,这是 move || count.get();
{move || count()}
</button>
}
}
组件签名
#[component]
与所有组件定义一样,这以 #[component]
宏开头。 #[component]
注释一个函数,以便它可以
在你的 Leptos 应用程序中用作组件。 我们将在接下来的几章中看到此宏的其他一些功能。
fn App() -> impl IntoView
每个组件都是具有以下特征的函数
- 它接受零个或多个任何类型的参数。
- 它返回
impl IntoView
,这是一个不透明类型,包括 你可以从 Leptosview
中返回的任何内容。
组件函数参数被收集到一个由
view
宏根据需要构建的 props 结构体中。
组件主体
组件函数的主体是一个只运行一次的设置函数,而不是一个多次重新运行的渲染函数。 你通常会使用它来创建一些响应式变量,定义任何响应这些值变化而运行的副作用,以及描述用户界面。
let (count, set_count) = create_signal(0);
create_signal
创建一个信号,它是 Leptos 中响应式变化和状态管理的基本单元。
这将返回一个 (getter, setter)
元组。 要访问当前值,你将使用 count.get()
(或者,在 nightly
Rust 上,使用简写 count()
)。 要设置当前值,你将调用 set_count.set(...)
(或者 set_count(...)
)。
.get()
克隆值,.set()
覆盖它。 在许多情况下,使用.with()
或.update()
更有效率;如果你现在想了解更多关于这些权衡的信息,请查看ReadSignal
和WriteSignal
的文档。
视图
Leptos 使用类似 JSX 的格式通过 view
宏定义用户界面。
view! {
<button
// 使用 on: 定义事件监听器
on:click=move |_| {
set_count(3);
}
>
// 文本节点用引号括起来
"Click me: "
// 代码块可以包含 Rust 代码
{move || count()}
</button>
}
这应该很容易理解:它看起来像 HTML,带有一个特殊的 on:click
来定义 click
事件监听器,一个格式化像 Rust 字符串的文本节点,然后是...
{move || count()}
无论那是什么。
人们有时会开玩笑说,他们在他们的第一个 Leptos 应用程序中使用的闭包比他们一生中使用的任何时候都多。 这很公平。 基本上,将一个函数传递给视图会告诉框架:“嘿,这是一个可能会改变的东西。”
当我们点击按钮并调用 set_count
时,count
信号会被更新。 这个
move || count()
闭包,它的值依赖于 count
的值,会重新运行,
框架会对那个特定的文本节点进行有针对性的更新,而不会触及应用程序中的任何其他内容。 这就是允许对 DOM 进行极其高效的更新的原因。
现在,如果你打开了 Clippy——或者你有一双特别敏锐的眼睛——你可能会注意到
这个闭包是多余的,至少在 nightly
Rust 中是这样。 如果你在 nightly
Rust 中使用
Leptos,信号已经是函数了,所以闭包是不必要的。
因此,你可以编写一个更简单的视图:
view! {
<button /* ... */>
"Click me: "
// 与 {move || count()} 相同
{count}
</button>
}
记住——这非常重要——只有函数是响应式的。 这意味着
{count}
和 {count()}
在你的视图中做的事情非常不同。 {count}
传递
一个函数,告诉框架每次 count
改变时都要更新视图。
{count()}
只访问一次 count
的值,并将一个 i32
传递给视图,
渲染一次,非响应式。 你可以在下面的 CodeSandbox 中看到区别!
让我们做最后一个改变。 set_count(3)
对于点击处理程序来说是一个非常无用的操作。 让我们将“将此值设置为 3”替换为“将此值递增 1”:
move |_| {
set_count.update(|n| *n += 1);
}
你可以在这里看到,虽然 set_count
只是设置值,但 set_count.update()
为我们提供了一个可变引用并在原地修改值。 两者都会触发我们 UI 中的响应式更新。
在整个教程中,我们将使用 CodeSandbox 来展示交互式示例。 将鼠标悬停在任何变量上以显示 Rust-Analyzer 详细信息 以及正在发生的事情的文档。 随意 fork 示例自己尝试!
实时示例
要在沙盒中显示浏览器,你可能需要点击
添加开发者工具 > 其他预览 > 8080。
CodeSandbox 源代码
use leptos::*;
// #[component] 宏将函数标记为可重用组件
// 组件是用户界面的构建块
// 它们定义了一个可重用的行为单元
#[component]
fn App() -> impl IntoView {
// 在这里我们创建一个响应式信号
// 并获取一个 (getter, setter) 对
// 信号是框架中变化的基本单元
// 我们稍后会详细讨论它们
let (count, set_count) = create_signal(0);
// `view` 宏是我们定义用户界面的方式
// 它使用类似 HTML 的格式,可以接受某些 Rust 值
view! {
<button
// on:click 将在每次 `click` 事件触发时运行
// 每个事件处理程序都定义为 `on:{eventname}`
// 我们能够将 `set_count` 移入闭包中
// 因为信号是 Copy 和 'static
on:click=move |_| {
set_count.update(|n| *n += 1);
}
>
// RSX 中的文本节点应该用引号括起来,
// 就像普通的 Rust 字符串一样
"Click me"
</button>
<p>
<strong>"响应式: "</strong>
// 你可以通过将 Rust 表达式括在大括号中来将它们作为值插入 DOM 中
// 如果你传入一个函数,它将进行响应式更新
{move || count()}
</p>
<p>
<strong>"响应式简写: "</strong>
// 信号是函数,所以我们可以移除包装闭包
{count}
</p>
<p>
<strong>"非响应式: "</strong>
// 注意:如果你写 {count()},这将*不会*是响应式的
// 它只是获取 count 的值一次
{count()}
</p>
}
}
// 这个 `main` 函数是应用程序的入口点
// 它只是将我们的组件挂载到 <body> 上
// 因为我们将其定义为 `fn App`,所以我们现在可以在
// 模板中将其用作 <App/>
fn main() {
leptos::mount_to_body(|| view! { <App/> })
}