基本组件
那个“Hello, world!”是一个非常简单的例子。让我们继续讨论更像普通应用程序的东西。
首先,让我们修改main函数,使其不再渲染整个应用程序,而只是渲染一个 <App/> 组件。组件是大多数 Web 框架中组合和设计的基本单位,Leptos 也不例外。在概念上,它们类似于 HTML 元素:它们表示 DOM 的某个部分,并具有自包含且定义明确的行为。与 HTML 元素不同的是,组件采用 PascalCase 命名方式,因此大多数 Leptos 应用程序通常会从一个 <App/> 组件开始。
use leptos::mount::mount_to_body;
fn main() {
mount_to_body(App);
}
现在让我们来定义 App 组件自身。因为它相对简单,所以我先介绍一下整个过程,然后再逐行讲解。
use leptos::prelude::*;
#[component]
fn App() -> impl IntoView {
let (count, set_count) = signal(0);
view! {
<button
on:click=move |_| set_count.set(3)
>
"Click me: "
{count}
</button>
<p>
"Double count: "
{move || count.get() * 2}
</p>
}
}
导入 Prelude 包
use leptos::prelude::*;
Leptos 提供了一个包含常用特征和函数的 prelude。如果您更喜欢使用单独的导入,请随意使用;编译器将为每个导入提供有用的建议。
组件签名
#[component]
与所有组件定义一样,它以 #[component] 宏开头。#[component] 注释一个函数,以便它可以用作 Leptos 应用程序中的组件。我们将在后面几章中看到此宏的一些其他功能。
fn App() -> impl IntoView
每个组件都是一个具有以下特征的函数
- 它接受零个或多个任意类型的参数。
- 它返回
impl IntoView,这是一个不透明类型,包含您可以从 Leptosview返回的任何内容。
组件函数参数被聚集到一个单独的 props 结构中,该结构由
view宏根据需要构建。
组件主体
组件函数的主体是只运行一次的设置函数,而不是多次重复运行的呈现函数。 您通常会用它来创建几个响应变量,定义响应这些值变化而运行的任何效果,并描述用户界面。
let (count, set_count) = signal(0);
signal
用于创建信号,这是 Leptos 中响应式变化和状态管理的基本单元。
它返回一个 (getter, setter) 元祖。要访问当前值,可以使用 count.get() (或者在 nightly Rust版本中,可以简写为 count())。要设置当前值,则需要调用 set_count.set(...) (或者在 nightly 版本中可以简写为 set_count(...))。
.get()会克隆信号的值,而.set()会覆盖它。在许多情况下,使用.with()或.update()会更加高效。如果您想了解这些方法之间的权衡,可以查阅ReadSignal和WriteSignal的文档。
视图
Leptos 通过 view 宏使用类似 JSX 的格式定义用户界面。
view! {
<button
// define an event listener with on:
on:click=move |_| set_count.set(3)
>
// text nodes are wrapped in quotation marks
"Click me: "
// blocks include Rust code
// in this case, it renders the value of the signal
{count}
</button>
<p>
"Double count: "
{move || count.get() * 2}
</p>
}
这段代码大致上是易于理解的:它看起来像 HTML,其中有一个特殊的 on:click 用于定义一个 click 事件监听器,还有一些看起来像 Rust 字符串的文本节点(text node),以及两个用大括号包裹的值:
第一个 {count} 很容易理解(就是我们信号的值),而另一个则是:
{move || count.get() * 2}
这是什么?
有人开玩笑说,他们在第一次使用 Leptos 开发应用程序时写的闭包比他们人生中写的所有闭包还多。确实如此。
将一个函数传递给视图(view)相当于告诉框架:“嘿,这是一个可能会发生变化的东西。”
当我们点击按钮并调用 set_count 时,count 信号会被更新。而这个 move || count.get() * 2 闭包的值依赖于 count 的值,因此会重新运行。
框架会对与此闭包相关的特定文本节点进行精准更新,而不会影响(touch)应用中的其他部分。正是这种机制实现了对 DOM 的极高效更新。
记住——这一点非常重要——只有信号和函数在视图中被视为响应式值。
这意味着 {count} 和 {count.get()} 在视图中做的事情是非常不同的。
{count} 传递一个信号,告诉框架每当 count 发生变化时更新视图。
{count.get()} 只会访问一次 count 的值,并将一个 i32 值传递到视图中,进行一次性渲染,不会响应更新。
以同样的方式,{move || count.get() * 2} 和 {count.get() * 2} 的行为也不同。
第一个是一个函数,因此它会响应式地渲染。第二个是一个值,所以它只会渲染一次,并且在 count 变化时不会更新。
你可以在下面的 CodeSandbox 中查看到不同之处。
让我们做最后一次修改。set_count.set(3) 对于点击处理来说是一个相当无用的操作。我们将“将这个值设为 3”替换为“将这个值增加 1”:
move |_| {
*set_count.write() += 1;
}
你可以看到,在这里,set_count 只是设置值,而 set_count.write() 给我们一个可变引用,并在原地修改值。无论哪种方式都会触发 UI 中的响应式更新。
在本教程中,我们将使用 CodeSandbox 展示交互式示例。
悬停在任何变量上以显示 Rust-Analyzer 详细信息和文档,了解发生了什么。
随时可以folk这些示例,自己动手玩一下!
Live example
Live example
To show the browser in the sandbox, you may need to click
Add DevTools > Other Previews > 8080.
CodeSandbox 源代码
use leptos::prelude::*;
// The #[component] macro marks a function as a reusable component
// Components are the building blocks of your user interface
// They define a reusable unit of behavior
#[component]
fn App() -> impl IntoView {
// here we create a reactive signal
// and get a (getter, setter) pair
// signals are the basic unit of change in the framework
// we'll talk more about them later
let (count, set_count) = signal(0);
// the `view` macro is how we define the user interface
// it uses an HTML-like format that can accept certain Rust values
view! {
<button
// on:click will run whenever the `click` event fires
// every event handler is defined as `on:{eventname}`
// we're able to move `set_count` into the closure
// because signals are Copy and 'static
on:click=move |_| *set_count.write() += 1
>
// text nodes in RSX should be wrapped in quotes,
// like a normal Rust string
"Click me: "
{count}
</button>
<p>
<strong>"Reactive: "</strong>
// you can insert Rust expressions as values in the DOM
// by wrapping them in curly braces
// if you pass in a function, it will reactively update
{move || count.get()}
</p>
<p>
<strong>"Reactive shorthand: "</strong>
// you can use signals directly in the view, as a shorthand
// for a function that just wraps the getter
{count}
</p>
<p>
<strong>"Not reactive: "</strong>
// NOTE: if you just write {count.get()}, this will *not* be reactive
// it simply gets the value of count once
{count.get()}
</p>
}
}
// This `main` function is the entry point into the app
// It just mounts our component to the <body>
// Because we defined it as `fn App`, we can now use it in a
// template as <App/>
fn main() {
leptos::mount::mount_to_body(App)
}