smart-house-web: leptos эсперименты
This commit is contained in:
@@ -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>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user