smart-house-web: leptos эсперименты

This commit is contained in:

View File

@@ -1,114 +1,89 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use leptos::{ev::MouseEvent, prelude::*}; use leptos::prelude::*;
// This highlights four different ways that child components can communicate // Often, you want to pass some kind of child view to another
// with their parent: // component. There are two basic patterns for doing this:
// 1) <ButtonA/>: passing a WriteSignal as one of the child component props, // - "render props": creating a component prop that takes a function
// for the child component to write into and the parent to read // that creates a view
// 2) <ButtonB/>: passing a closure as one of the child component props, for // - the `children` prop: a special property that contains content
// the child component to call // passed as the children of a component in your view, not as a
// 3) <ButtonC/>: adding an `on:` event listener to a component // property
// 4) <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
#[derive(Copy, Clone)]
struct SmallcapsContext(WriteSignal<bool>);
#[component] #[component]
pub fn App() -> impl IntoView { pub fn App() -> impl IntoView {
// just some signals to toggle four classes on our <p> let (items, set_items) = signal(vec![0, 1, 2]);
let (red, set_red) = signal(false); let render_prop = move || {
let (right, set_right) = signal(false); let len = move || items.read().len();
let (italics, set_italics) = signal(false); view! {
let (smallcaps, set_smallcaps) = signal(false); <p>"Length: " {len}</p>
}
// the newtype pattern isn't *necessary* here but is a good practice };
// it avoids confusion with other possible future `WriteSignal<bool>` contexts
// and makes it easier to refer to it in ButtonD
provide_context(SmallcapsContext(set_smallcaps));
view! { view! {
<main> // This component just displays the two kinds of children,
<p // embedding them in some other markup
// class: attributes take F: Fn() => bool, and these signals all implement Fn() <TakesChildren
class:red=red // for component props, you can shorthand
class:right=right // `render_prop=render_prop` => `render_prop`
class:italics=italics // (this doesn't work for HTML element attributes)
class:smallcaps=smallcaps render_prop
> >
"Lorem ipsum sit dolor amet." // these look just like the children of an HTML element
</p> <p>"Here's a child."</p>
<p>"Here's another child."</p>
// Button A: pass the signal setter </TakesChildren>
<ButtonA setter=set_red/> <hr/>
// This component actually iterates over and wraps the children
// Button B: pass a closure <WrapsChildren>
<ButtonB on_click=move |_| set_right.update(|value| *value = !*value)/> <p>"Here's a child."</p>
<p>"Here's another child."</p>
// Button C: use a regular event listener </WrapsChildren>
// setting an event listener on a component like this applies it
// to each of the top-level elements the component returns
<ButtonC on:click=move |_| set_italics.update(|value| *value = !*value)/>
// Button D gets its setter from context rather than props
<ButtonD/>
</main>
} }
} }
/// Button A receives a signal setter and updates the signal itself /// Displays a `render_prop` and some children within markup.
#[component] #[component]
pub fn ButtonA( pub fn TakesChildren<F, IV>(
/// Signal that will be toggled when the button is clicked. /// Takes a function (type F) that returns anything that can be
setter: WriteSignal<bool>, /// converted into a View (type IV)
) -> impl IntoView { render_prop: F,
/// `children` takes the `Children` type
/// this is an alias for `Box<dyn FnOnce() -> Fragment>`
/// ... aren't you glad we named it `Children` instead?
children: Children,
) -> impl IntoView
where
F: Fn() -> IV,
IV: IntoView,
{
view! { view! {
<button <h1><code>"<TakesChildren/>"</code></h1>
on:click=move |_| setter.update(|value| *value = !*value) <h2>"Render Prop"</h2>
> {render_prop()}
"Toggle Red" <hr/>
</button> <h2>"Children"</h2>
{children()}
} }
} }
/// Button B receives a closure /// Wraps each child in an `<li>` and embeds them in a `<ul>`.
#[component] #[component]
pub fn ButtonB( pub fn WrapsChildren(children: ChildrenFragment) -> impl IntoView {
/// Callback that will be invoked when the button is clicked. // children() returns a `Fragment`, which has a
on_click: impl FnMut(MouseEvent) + 'static, // `nodes` field that contains a Vec<View>
) -> impl IntoView { // this means we can iterate over the children
view! { // to create something new!
<button let children = children()
on:click=on_click .nodes
> .into_iter()
"Toggle Right" .map(|child| view! { <li>{child}</li> })
</button> .collect::<Vec<_>>();
}
}
/// Button C is a dummy: it renders a button but doesn't handle
/// its click. Instead, the parent component adds an event listener.
#[component]
pub fn ButtonC() -> impl IntoView {
view! {
<button>
"Toggle Italics"
</button>
}
}
/// Button D is very similar to Button A, but instead of passing the setter as a prop
/// we get it from the context
#[component]
pub fn ButtonD() -> impl IntoView {
let setter = use_context::<SmallcapsContext>().unwrap().0;
view! { view! {
<button <h1><code>"<WrapsChildren/>"</code></h1>
on:click=move |_| setter.update(|value| *value = !*value) // wrap our wrapped children in a UL
> <ul>{children}</ul>
"Toggle Small Caps"
</button>
} }
} }