Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

无宏模式:视图构建器语法

No Macros: The View Builder Syntax

如果你对到目前为止所描述的 view! 宏语法非常满意,欢迎跳过本章。本节中描述的构建器语法始终可用,但并非必须。

If you’re perfectly happy with the view! macro syntax described so far, you’re welcome to skip this chapter. The builder syntax described in this section is always available, but never required.

由于种种原因,许多开发人员更愿意避免使用宏。也许你不喜欢受限的 rustfmt 支持。(尽管你应该去看看 leptosfmt,这是一个非常出色的工具!)也许你担心宏对编译时间的影响。也许你更喜欢纯 Rust 语法的审美,或者你在类 HTML 语法和 Rust 代码之间进行上下文切换时感到困难。或者,你可能希望在创建和操作 HTML 元素时拥有比 view 宏提供的更多灵活性。

For one reason or another, many developers would prefer to avoid macros. Perhaps you don’t like the limited rustfmt support. (Although, you should check out leptosfmt, which is an excellent tool!) Perhaps you worry about the effect of macros on compile time. Perhaps you prefer the aesthetics of pure Rust syntax, or you have trouble context-switching between an HTML-like syntax and your Rust code. Or perhaps you want more flexibility in how you create and manipulate HTML elements than the view macro provides.

如果你属于以上任何一种情况,构建器语法可能适合你。

If you fall into any of those camps, the builder syntax may be for you.

view 宏将类 HTML 语法扩展为一系列 Rust 函数和方法调用。如果你不想使用 view 宏,只需亲自使用这些扩展语法即可。而且实际上它感觉还挺不错的!

The view macro expands an HTML-like syntax to a series of Rust functions and method calls. If you’d rather not use the view macro, you can simply use that expanded syntax yourself. And it’s actually pretty nice!

首先,如果你愿意,你甚至可以放弃使用 #[component] 宏:组件只是一个用于创建视图的设置函数,因此你可以将组件定义为一个简单的函数调用:

First off, if you want you can even drop the #[component] macro: a component is just a setup function that creates your view, so you can define a component as a simple function call:

pub fn counter(initial_value: i32, step: u32) -> impl IntoView { }

通过调用与 HTML 元素同名的函数来创建元素:

Elements are created by calling a function with the same name as the HTML element:

p()

自定义元素/Web 组件可以通过使用带有名称的 custom() 函数来创建:

Custom elements/web components can be created by using the custom() function with their name:

custom("my-custom-element")

你可以使用 .child() 向元素添加子节点,该方法接收单个子节点,或者实现了 IntoView 的类型的元组或数组。

You can add children to the element with .child(), which takes a single child or a tuple or array of types that implement IntoView.

p().child((em().child("Big, "), strong().child("bold "), "text"))

使用 .attr() 添加特性 (Attribute)。这可以接收任何你可以作为特性传递给视图宏的相同类型(即实现了 Attribute 的类型)。

Attributes are added with .attr(). This can take any of the same types that you could pass as an attribute into the view macro (types that implement Attribute).

p().attr("id", "foo")
    .attr("data-count", move || count.get().to_string())

也可以使用特性方法添加它们,这些方法适用于任何内置的 HTML 特性名称:

They can also be added with attribute methods, which are available for any built-in HTML attribute name:

p().id("foo")
    .attr("data-count", move || count.get().to_string())

类似地,class:prop:style: 语法直接映射到 .class().prop().style() 方法。

Similarly, the class:, prop:, and style: syntaxes map directly onto .class(), .prop(), and .style() methods.

使用 .on() 添加事件监听器。在 leptos::ev 中找到的类型化事件可以防止事件名称中的拼写错误,并允许在回调函数中进行正确的类型推导。

Event listeners can be added with .on(). Typed events found in leptos::ev prevent typos in event names and allow for correct type inference in the callback function.

button()
    .on(ev::click, move |_| set_count.set(0))
    .child("Clear")

如果你更喜欢这种风格,所有这些加起来就成了一种非常“Rust 化”的语法,用于构建功能齐全的视图。

All of this adds up to a very Rusty syntax to build full-featured views, if you prefer this style.

/// 一个简单的计数器视图。
/// A simple counter view.
// 组件实际上只是一个函数调用:它运行一次以创建 DOM 和响应式系统
// A component is really just a function call: it runs once to create the DOM and reactive system
pub fn counter(initial_value: i32, step: i32) -> impl IntoView {
    let (count, set_count) = signal(initial_value);
    div().child((
        button()
            // 在 leptos::ev 中找到的类型化事件
            // 1) 防止事件名称拼写错误
            // 2) 允许回调函数中的正确类型推导
            // typed events found in leptos::ev
            // 1) prevent typos in event names
            // 2) allow for correct type inference in callbacks
            .on(ev::click, move |_| set_count.set(0))
            .child("Clear"),
        button()
            .on(ev::click, move |_| *set_count.write() -= step)
            .child("-1"),
        span().child(("Value: ", move || count.get(), "!")),
        button()
            .on(ev::click, move |_| *set_count.write() += step)
            .child("+1"),
    ))
}

在构建器语法中使用组件

Using Components with the Builder Syntax

要使用构建器语法创建你自己的组件,你可以简单地使用普通函数(见上文)。要使用其他组件(例如内置的 ForShow 控制流组件),你可以利用这样一个事实:每个组件都是一个具有单个组件属性参数的函数,而组件属性有其自己的构建器。

To create your own components with the builder syntax, you can simply use plain functions (see above). To use other components (for example, the built-in For or Show control-flow components), you can take advantage of the fact that each component is a function of one component props argument, and component props have their own builder.

你可以使用组件属性构建器:

You can either use the component props builder:

use leptos::html::p;

let (value, set_value) = signal(0);

Show(
    ShowProps::builder()
        .when(move || value.get() > 5)
        .fallback(|| p().child("I will appear if `value` is 5 or lower"))
        .children(ToChildren::to_children(|| {
            p().child("I will appear if `value` is above 5")
        }))
        .build(),
)

或者你可以直接构建属性结构体:

or you can directly build the props struct:

use leptos::html::p;

let (value, set_value) = signal(0);

Show(ShowProps {
    when: move || value.get() > 5,
    fallback: (|| p().child("I will appear if `value` is 5 or lower")).into(),
    children: ToChildren::to_children(|| p().child("I will appear if `value` is above 5")),
})

使用组件构建器会正确应用各种修饰符,如 #[prop(into)];使用结构体语法,我们需要通过自己调用 .into() 来手动应用这一点。

Using the component builder correctly applies the various modifiers like #[prop(into)]; using the struct syntax, we’ve applied this manually by calling .into() ourselves.

扩展宏

Expanding Macros

这里没有详细描述 view 宏或 component 宏语法的每一项特性。然而,Rust 为你提供了理解宏底层运作所需的工具。具体来说,rust-analyzer 的“递归扩展宏”功能允许你扩展任何宏以显示它生成的代码,而 cargo-expand 则将项目中的所有宏扩展为常规 Rust 代码。本书的其余部分将继续使用 view 宏语法,但如果你不确定如何将其转换为构建器语法,可以使用这些工具来探索生成的代码。

Not every feature of the view macro or component macro syntax has been described here in detail. However, Rust provides you the tools you need to understand what is going on with any macro. Specifically, rust-analyzer’s "expand macro recursively" feature allows you to expand any macro to show the code it generates, and cargo-expand expands all the macros in a project into regular Rust code. The rest of this book will continue using the view macro syntax, but if you’re ever unsure how to translate this into the builder syntax, you can use these tools to explore the code that is generated.