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

view:动态类、样式和属性

view: Dynamic Classes, Styles and Attributes

到目前为止,我们已经看到了如何使用 view 宏来创建事件监听器,以及如何通过向视图传入函数(例如信号)来创建动态文本。

So far we’ve seen how to use the view macro to create event listeners and to create dynamic text by passing a function (such as a signal) into the view.

但当然,在用户界面中,你可能还想更新其他内容。在本节中,我们将了解如何动态更新类(class)、样式(style)和属性(attribute),并介绍派生信号(derived signal)的概念。

But of course there are other things you might want to update in your user interface. In this section, we’ll look at how to update classes, styles and attributes dynamically, and we’ll introduce the concept of a derived signal.

让我们从一个你应该熟悉的简单组件开始:点击按钮增加计数器。

Let’s start with a simple component that should be familiar: click a button to increment a counter.

#[component]
fn App() -> impl IntoView {
    let (count, set_count) = signal(0);

    view! {
        <button
            on:click=move |_| {
                *set_count.write() += 1;
            }
        >
            "Click me: "
            {count}
        </button>
    }
}

到目前为止,我们在上一章中已经涵盖了所有这些内容。

So far, we’ve covered all of this in the previous chapter.

动态类

Dynamic Classes

现在,假设我想动态更新此元素上的 CSS 类列表。例如,假设我想在计数为奇数时添加 red 类。我可以使用 class: 语法来实现这一点。

Now let’s say I’d like to update the list of CSS classes on this element dynamically. For example, let’s say I want to add the class red when the count is odd. I can do this using the class: syntax.

class:red=move || count.get() % 2 == 1

class: 属性接受:

class: attributes take

  1. 冒号后的类名(red

  2. the class name, following the colon (red)

  3. 一个值,可以是 bool 或返回 bool 的函数

  4. a value, which can be a bool or a function that returns a bool

当值为 true 时,添加该类。当值为 false 时,移除该类。如果值是一个访问信号的函数,则该类将在信号变化时响应式地更新。

When the value is true, the class is added. When the value is false, the class is removed. And if the value is a function that accesses a signal, the class will reactively update when the signal changes.

现在,每当我点击按钮时,随着数字在奇偶之间切换,文本应该在红色和黑色之间切换。

Now every time I click the button, the text should toggle between red and black as the number switches between even and odd.

<button
    on:click=move |_| {
        *set_count.write() += 1;
    }
    // class: 语法会响应式地更新单个类
    // the class: syntax reactively updates a single class
    // 在这里,当 `count` 为奇数时,我们将设置 `red` 类
    // here, we'll set the `red` class when `count` is odd
    class:red=move || count.get() % 2 == 1
>
    "Click me"
</button>

如果你在跟着做,请确保进入 index.html 并添加如下内容:

If you’re following along, make sure you go into your index.html and add something like this:

<style>
  .red {
    color: red;
  }
</style>

某些 CSS 类名无法被 view 宏直接解析,特别是如果它们混合了横杠和数字或其他字符。在这种情况下,你可以使用元组语法:class=("name", value) 仍然可以直接更新单个类。

Some CSS class names can’t be directly parsed by the view macro, especially if they include a mix of dashes and numbers or other characters. In that case, you can use a tuple syntax: class=("name", value) still directly updates a single class.

class=("button-20", move || count.get() % 2 == 1)

元组语法还允许使用数组作为元组的第一个元素,在单个条件下指定多个类。

The tuple syntax also allows specifying multiple classes under a single condition using an array as the first tuple element.

class=(["button-20", "rounded"], move || count.get() % 2 == 1)

动态样式

Dynamic Styles

单个 CSS 属性可以使用类似的 style: 语法直接更新。

Individual CSS properties can be directly updated with a similar style: syntax.

let (count, set_count) = signal(0);

view! {
    <button
        on:click=move |_| {
            *set_count.write() += 10;
        }
        // 设置 `style` 属性
        // set the `style` attribute
        style="position: absolute"
        // 并通过 `style:` 切换单个 CSS 属性
        // and toggle individual CSS properties with `style:`
        style:left=move || format!("{}px", count.get() + 100)
        style:background-color=move || format!("rgb({}, {}, 100)", count.get(), 100)
        style:max-width="400px"
        // 设置一个 CSS 变量供样式表使用
        // Set a CSS variable for stylesheet use
        style=("--columns", move || count.get().to_string())
    >
        "Click to Move"
    </button>
}

动态属性

Dynamic Attributes

这同样适用于普通属性。向属性传递普通字符串或原始值会赋予它一个静态值。向属性传递一个函数(包括信号)会导致它响应式地更新其值。让我们在视图中添加另一个元素:

The same applies to plain attributes. Passing a plain string or primitive value to an attribute gives it a static value. Passing a function (including a signal) to an attribute causes it to update its value reactively. Let’s add another element to our view:

<progress
    max="50"
    // 信号是函数,因此 `value=count` 和 `value=move || count.get()`
    // signals are functions, so `value=count` and `value=move || count.get()`
    // 是可以互换的。
    // are interchangeable.
    value=count
/>

现在,每当我们设置计数时,不仅 <button>class 会切换,<progress> 条的 value 也会增加,这意味着我们的进度条将向前推进。

Now every time we set the count, not only will the class of the <button> be toggled, but the value of the <progress> bar will increase, which means that our progress bar will move forward.

派生信号

Derived Signals

为了好玩,让我们再深入一层。

Let’s go one layer deeper, just for fun.

你已经知道,我们只需将函数传入 view 即可创建响应式界面。这意味着我们可以轻松地更改进度条。例如,假设我们希望它的移动速度快一倍:

You already know that we create reactive interfaces just by passing functions into the view. This means that we can easily change our progress bar. For example, suppose we want it to move twice as fast:

<progress
    max="50"
    value=move || count.get() * 2
/>

但想象一下,如果我们想在不止一个地方重用该计算。你可以使用派生信号(derived signal)来实现:一个访问信号的闭包。

But imagine we want to reuse that calculation in more than one place. You can do this using a derived signal: a closure that accesses a signal.

let double_count = move || count.get() * 2;

/* 插入视图的其余部分 */
/* insert the rest of the view */
<progress
    max="50"
    // 我们在这里使用一次
    // we use it once here
    value=double_count
/>
<p>
    "Double Count: "
    // 在这里再次使用
    // and again here
    {double_count}
</p>

派生信号允许你创建响应式计算值,这些值可以以最小的开销在应用程序的多个位置使用。

Derived signals let you create reactive computed values that can be used in multiple places in your application with minimal overhead.

注意:像这样使用派生信号意味着每当信号改变(当 count() 改变时)以及我们每次访问 double_count 时,计算都会运行一次;换句话说,运行两次。这是一个非常廉价的计算,所以没关系。我们将在后面的章节中讨论 memo(备忘录),它们旨在为昂贵的计算解决这个问题。

Note: Using a derived signal like this means that the calculation runs once per signal change (when count() changes) and once per place we access double_count; in other words, twice. This is a very cheap calculation, so that’s fine. We’ll look at memos in a later chapter, which were designed to solve this problem for expensive calculations.

高级主题:注入原始 HTML

Advanced Topic: Injecting Raw HTML

view 宏提供了一个额外的属性 inner_html,它可以用于直接设置任何元素的 HTML 内容,从而清除你给它的任何其他子元素。请注意,这不会对你提供的 HTML 进行转义。你应该确保它只包含受信任的输入,或者任何 HTML 实体都已转义,以防止跨站脚本(XSS)攻击。

The view macro provides support for an additional attribute, inner_html, which can be used to directly set the HTML contents of any element, wiping out any other children you’ve given it. Note that this does not escape the HTML you provide. You should make sure that it only contains trusted input or that any HTML entities are escaped, to prevent cross-site scripting (XSS) attacks.

let html = "<p>This HTML will be injected.</p>";
view! {
  <div inner_html=html/>
}

点击此处查看完整的 view 宏文档

Click here for the full view macros docs.

在线示例

点击打开 CodeSandbox。 Click to open CodeSandbox.

CodeSandbox Source
use leptos::prelude::*;

#[component]
fn App() -> impl IntoView {
    let (count, set_count) = signal(0);

    // “派生信号”是一个访问其他信号的函数
    // a "derived signal" is a function that accesses other signals
    // 我们可以使用它来创建依赖于一个或多个其他信号值的响应式值
    // we can use this to create reactive values that depend on the
    // values of one or more other signals
    let double_count = move || count.get() * 2;

    view! {
        <button
            on:click=move |_| {
                *set_count.write() += 1;
            }
            // class: 语法响应式地更新单个类
            // the class: syntax reactively updates a single class
            // 在这里,当 `count` 为奇数时,我们将设置 `red` 类
            // here, we'll set the `red` class when `count` is odd
            class:red=move || count.get() % 2 == 1
            class=("button-20", move || count.get() % 2 == 1)
        >
            "Click me"
        </button>
        // 注意:像 <br> 这样的自闭合标签需要显式的 /
        // NOTE: self-closing tags like <br> need an explicit /
        <br/>

        // 我们将在每次 `count` 改变时更新这个进度条
        // We'll update this progress bar every time `count` changes
        <progress
            // 静态属性的工作方式与 HTML 中相同
            // static attributes work as in HTML
            max="50"

            // 向属性传递函数会响应式地设置该属性
            // passing a function to an attribute
            // reactively sets that attribute
            // 信号是函数,因此 `value=count` 和 `value=move || count.get()` 是可以互换的
            // signals are functions, so `value=count` and `value=move || count.get()`
            // are interchangeable.
            value=count
        >
        </progress>
        <br/>

        // 这个进度条将使用 `double_count`
        // This progress bar will use `double_count`
        // 所以它的移动速度应该快一倍!
        // so it should move twice as fast!
        <progress
            max="50"
            // 派生信号是函数,因此它们也可以响应式地更新 DOM
            // derived signals are functions, so they can also
            // reactively update the DOM
            value=double_count
        >
        </progress>
        <p>"Count: " {count}</p>
        <p>"Double Count: " {double_count}</p>
    }
}

fn main() {
    leptos::mount::mount_to_body(App)
}