错误处理
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>
}
}
现在,如果你输入 42,value 为 Ok(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 源代码
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)
}