Обработка ошибок
В предыдущей главе, мы рассмотрели, что можно рендерить Option<T>
:
в случае None
, ничего не будет выведено, а в случае Some(T)
, будет выведен T
(если T
реализует IntoView
). С Result<T, E>
можно обойтись весьма схожим образом.
В случае Err(_)
, ничего не будет выведено. В случае Ok(T)
будет выведен T
.
Давайте начнем с простого компонента, осуществляющего захват числового поля ввода.
#[component]
fn NumericInput() -> impl IntoView {
let (value, set_value) = create_signal(Ok(0));
// when input changes, try to parse a number from the input
let on_input = move |ev| set_value(event_target_value(&ev).parse::<i32>());
view! {
<label>
"Type an integer (or not!)"
<input type="number" on:input=on_input/>
<p>
"You entered "
<strong>{value}</strong>
</p>
</label>
}
}
Каждый раз когда значение поля ввода меняется, on_input
попытается превратить это значение в 32-битное число (i32
)
и сохранить его в наш сигнал value
с типом Result<i32, _>
. Если ввести число 42
, UI отобразит
You entered 42
Но если введете строку foo
, он отобразит
You entered
Выглядит так себе. Это экономит нам вызов .unwrap_or_default()
или чего-то подобного, но было бы намного лучше, если
мы могли бы поймать эту ошибку и что-нибудь с ней сделать.
Это можно сделать, используя компонент <ErrorBoundary/>
Люди часто пытаются указать на то, что `<input type="number>` не даст вам написать строку как `foo` или что-либо ещё,
что не является числом. Это справедливо в каких-то браузерах, но не во всех! Более того, есть множество вещей, которые
можно напечатать в обычный числовое поле ввода и которые не являются `i32`: число с плавающей точкой, число больше
чем позволяют 32 бита, буква `e` и так далее. Браузеру можно сказать чтоб он поддерживал некоторые из этих инвариантов,
однако поведение браузера всё же вариативно: Важно парсить самостоятельно!
<ErrorBoundary/>
<ErrorBoundary/>
немного сродни компоненту <Show/>
, рассмотренному нами в предыдущей главе.
Если всё окей —точнее сказать, если всё Ok(_)
— он выводит дочерние элементы.
Но если среди потомков будет выведен Err(_)
, это вызовет отображение fallback
в <ErrorBoundary/>
.
Давайте добавим <ErrorBoundary/>
в наш пример.
#[component]
fn NumericInput() -> impl IntoView {
let (value, set_value) = create_signal(Ok(0));
let on_input = move |ev| set_value(event_target_value(&ev).parse::<i32>());
view! {
<h1>"Error Handling"</h1>
<label>
"Type a number (or something that's not a number!)"
<input type="number" on:input=on_input/>
<ErrorBoundary
// 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_view()
}
</ul>
</div>
}
>
<p>"You entered " <strong>{value}</strong></p>
</ErrorBoundary>
</label>
}
}
Теперь если ввести 42
, value
примет значение Ok(42)
и вы увидите
You entered 42
Если ввести foo
, value
будет Err(_)
и отобразится fallback
.
Мы вывели список ошибок в виде String
, так что вы увидите что-то вроде
Not a number! Errors:
- cannot parse integer from empty string
Если исправить эту ошибку, сообщение об ошибке исчезнет и контент обёрнутый в <ErrorBoundary/>
появится снова.
[Нажмите, чтобы открыть CodeSandbox.](https://codesandbox.io/p/sandbox/7-errors-0-5-5mptv9?file=%2Fsrc%2Fmain.rs%3A1%2C1)
<noscript>
Пожалуйста, включите Javascript для просмотра примеров.
</noscript>
<template>
<iframe src="https://codesandbox.io/p/sandbox/7-errors-0-5-5mptv9?file=%2Fsrc%2Fmain.rs%3A1%2C1" width="100%" height="1000px" style="max-height: 100vh"></iframe>
</template>
Код примера CodeSandbox
use leptos::*;
#[component]
fn App() -> impl IntoView {
let (value, set_value) = create_signal(Ok(0));
// when input changes, try to parse a number from the input
let on_input = move |ev| set_value(event_target_value(&ev).parse::<i32>());
view! {
<h1>"Error Handling"</h1>
<label>
"Type a number (or something that's not a number!)"
<input type="number" on:input=on_input/>
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
// the fallback will be displayed. Otherwise, the children of the
// <ErrorBoundary/> will be displayed.
<ErrorBoundary
// 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 "
// 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_to_body(App)
}