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

错误处理

Error Handling

在上一章中,我们看到你可以渲染 Option<T>:在 None 的情况下,它什么都不会渲染;而在 Some(T) 的情况下,它会渲染 T(前提是 T 实现了 IntoView)。实际上,你可以对 Result<T, E> 执行非常相似的操作。在 Err(_) 的情况下,它什么都不会渲染。在 Ok(T) 的情况下,它将渲染 T

In the last chapter, we saw that you can render Option<T>: in the None case, it will render nothing, and in the Some(T) case, it will render T (that is, if T implements IntoView). You can actually do something very similar with a Result<T, E>. In the Err(_) case, it will render nothing. In the Ok(T) case, it will render the T.

让我们从一个捕获数字输入的简单组件开始。

Let’s start with a simple component to capture a number input.

#[component]
fn NumericInput() -> impl IntoView {
    let (value, set_value) = signal(Ok(0));

    view! {
        <label>
            "Type an integer (or not!)"
            <input type="number" on:input:target=move |ev| {
              // 当输入发生变化时,尝试从输入中解析数字
              // when input changes, try to parse a number from the input
              set_value.set(ev.target().value().parse::<i32>())
            }/>
            <p>
                "You entered "
                <strong>{value}</strong>
            </p>
        </label>
    }
}

每当你改变输入时,on_input 都会尝试将其值解析为 32 位整数(i32),并将其存储在我们的 value 信号中,该信号是一个 Result<i32, _>。如果你输入数字 42,UI 将显示:

Every time you change the input, on_input will attempt to parse its value into a 32-bit integer (i32), and store it in our value signal, which is a Result<i32, _>. If you type the number 42, the UI will display

You entered 42

但如果你输入字符串 foo,它将显示:

But if you type the string foo, it will display

You entered

这并不理想。虽然它让我们免于使用 .unwrap_or_default() 之类的方法,但如果我们能捕获错误并对其进行处理,那就好得多了。

This is not great. It saves us using .unwrap_or_default() or something, but it would be much nicer if we could catch the error and do something with it.

你可以使用 <ErrorBoundary/> 组件来实现这一点。

You can do that, with the <ErrorBoundary/> component.

注意

人们经常试图指出,<input type="number"> 会阻止你输入像 foo 这样的字符串,或者任何非数字的内容。这在某些浏览器中是真的,但并非所有浏览器都如此!此外,在一个普通的数字输入框中,可以输入很多不是 i32 的内容:浮点数、大于 32 位的数字、字母 e 等等。虽然可以告诉浏览器维持其中一些不变量,但浏览器的行为仍然各不相同:自行解析非常重要!

<ErrorBoundary/>

<ErrorBoundary/>

<ErrorBoundary/> 有点像我们在上一章看到的 <Show/> 组件。如果一切正常——也就是说,如果一切都是 Ok(_)——它会渲染其子组件。但如果在这些子组件中渲染了 Err(_),它将触发 <ErrorBoundary/>fallback(回退界面)。

An <ErrorBoundary/> is a little like the <Show/> component we saw in the last chapter. If everything’s okay—which is to say, if everything is Ok(_)—it renders its children. But if there’s an Err(_) rendered among those children, it will trigger the <ErrorBoundary/>’s fallback.

让我们在这个例子中添加一个 <ErrorBoundary/>

Let’s add an <ErrorBoundary/> to this example.

#[component]
fn NumericInput() -> impl IntoView {
        let (value, set_value) = signal(Ok(0));

    view! {
        <h1>"Error Handling"</h1>
        <label>
            "Type a number (or something that's not a number!)"
            <input type="number" on:input:target=move |ev| {
                // 当输入发生变化时,尝试从输入中解析数字
                // when input changes, try to parse a number from the input
                set_value.set(ev.target().value().parse::<i32>())
            }/>
            // 如果在 <ErrorBoundary/> 内部渲染了 `Err(_)`,
            // 则会显示 fallback。否则,将显示 <ErrorBoundary/> 的子组件。
            // If an `Err(_) had been rendered inside the <ErrorBoundary/>,
            // the fallback will be displayed. Otherwise, the children of the
            // <ErrorBoundary/> will be displayed.
            <ErrorBoundary
                // fallback 接收一个包含当前错误的信号
                // the fallback receives a signal containing current errors
                fallback=|errors| view! {
                    <div class="error">
                        <p>"Not a number! Errors: "</p>
                        // 如果愿意,我们可以将错误列表渲染为字符串
                        // we can render a list of errors
                        // as strings, if we'd like
                        <ul>
                            {move || errors.get()
                                .into_iter()
                                .map(|(_, e)| view! { <li>{e.to_string()}</li>})
                                .collect::<Vec<_>>()
                            }
                        </ul>
                    </div>
                }
            >
                <p>
                    "You entered "
                    // 因为 `value` 是 `Result<i32, _>`,
                    // 如果它是 `Ok`,它将渲染 `i32`;
                    // 如果它是 `Err`,它将不渲染任何内容并触发错误边界。
                    // 这是一个信号,所以当 `value` 改变时,它会动态更新。
                    // because `value` is `Result<i32, _>`,
                    // it will render the `i32` if it is `Ok`,
                    // and render nothing and trigger the error boundary
                    // if it is `Err`. It's a signal, so this will dynamically
                    // update when `value` changes
                    <strong>{value}</strong>
                </p>
            </ErrorBoundary>
        </label>
    }
}

现在,如果你输入 42valueOk(42),你将看到:

Now, if you type 42, value is Ok(42) and you’ll see

You entered 42

如果你输入 foo,值将是 Err(_),并且会渲染 fallback。我们选择了将错误列表渲染为 String,所以你会看到类似下面的内容:

If you type foo, value is Err(_) and the fallback will render. We’ve chosen to render the list of errors as a String, so you’ll see something like

Not a number! Errors:
- cannot parse integer from empty string

如果你修复了错误,错误信息将消失,你包裹在 <ErrorBoundary/> 中的内容将再次出现。

If you fix the error, the error message will disappear and the content you’re wrapping in an <ErrorBoundary/> will appear again.

在线示例

点击打开 CodeSandbox。

CodeSandbox 源代码
use leptos::prelude::*;

#[component]
fn App() -> impl IntoView {
    let (value, set_value) = signal(Ok(0));

    view! {
        <h1>"Error Handling"</h1>
        <label>
            "Type a number (or something that's not a number!)"
            <input type="number" on:input:target=move |ev| {
                // 当输入发生变化时,尝试从输入中解析数字
                // when input changes, try to parse a number from the input
                set_value.set(ev.target().value().parse::<i32>())
            }/>
            // 如果在 <ErrorBoundary/> 内部渲染了 `Err(_)`,
            // 则会显示 fallback。否则,将显示 <ErrorBoundary/> 的子组件。
            // If an `Err(_) had been rendered inside the <ErrorBoundary/>,
            // the fallback will be displayed. Otherwise, the children of the
            // <ErrorBoundary/> will be displayed.
            <ErrorBoundary
                // fallback 接收一个包含当前错误的信号
                // the fallback receives a signal containing current errors
                fallback=|errors| view! {
                    <div class="error">
                        <p>"Not a number! Errors: "</p>
                        // 如果愿意,我们可以将错误列表渲染为字符串
                        // we can render a list of errors
                        // as strings, if we'd like
                        <ul>
                            {move || errors.get()
                                .into_iter()
                                .map(|(_, e)| view! { <li>{e.to_string()}</li>})
                                .collect::<Vec<_>>()
                            }
                        </ul>
                    </div>
                }
            >
                <p>
                    "You entered "
                    // 因为 `value` 是 `Result<i32, _>`,
                    // 如果它是 `Ok`,它将渲染 `i32`;
                    // 如果它是 `Err`,它将不渲染任何内容并触发错误边界。
                    // 这是一个信号,所以当 `value` 改变时,它会动态更新。
                    // because `value` is `Result<i32, _>`,
                    // it will render the `i32` if it is `Ok`,
                    // and render nothing and trigger the error boundary
                    // if it is `Err`. It's a signal, so this will dynamically
                    // update when `value` changes
                    <strong>{value}</strong>
                </p>
            </ErrorBoundary>
        </label>
    }
}

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