mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Introduce immutable string, array and map (#2563)
This commit is contained in:
parent
6b89e21034
commit
7ddf26752f
@ -36,6 +36,7 @@ members = [
|
||||
"examples/web_worker_fib",
|
||||
"examples/ssr_router",
|
||||
"examples/suspense",
|
||||
"examples/immutable",
|
||||
|
||||
# Tools
|
||||
"tools/benchmark-struct",
|
||||
|
||||
@ -38,6 +38,7 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo
|
||||
| [function_todomvc](function_todomvc) | F | Implementation of [TodoMVC](http://todomvc.com/) using function components and hooks. |
|
||||
| [futures](futures) | S | Demonstrates how you can use futures and async code with Yew. Features a Markdown renderer. |
|
||||
| [game_of_life](game_of_life) | S | Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) |
|
||||
| [immutable](immutable) | SF | Using immutable types in components. |
|
||||
| [inner_html](inner_html) | S | Embeds an external document as raw HTML by manually managing the element |
|
||||
| [js_callback](js_callback) | F | Interacts with JavaScript code |
|
||||
| [keyed_list](keyed_list) | S | Demonstrates how to use keys to improve the performance of lists |
|
||||
|
||||
12
examples/immutable/Cargo.toml
Normal file
12
examples/immutable/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "immutable"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
implicit-clone = { version = "0.2", features = ["map"] }
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = "0.3"
|
||||
yew = { path = "../../packages/yew", features = ["csr"] }
|
||||
5
examples/immutable/README.md
Normal file
5
examples/immutable/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Immutable Example
|
||||
|
||||
[](https://examples.yew.rs/immutable)
|
||||
|
||||
This is a technical demonstration for how to use immutables types in Yew.
|
||||
13
examples/immutable/index.html
Normal file
13
examples/immutable/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Yew • Immutable</title>
|
||||
<link data-trunk rel="scss" href="index.scss"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
|
||||
</html>
|
||||
11
examples/immutable/index.scss
Normal file
11
examples/immutable/index.scss
Normal file
@ -0,0 +1,11 @@
|
||||
$font-stack: Roboto, sans-serif;
|
||||
$primary-color: #f5f5f5;
|
||||
|
||||
body {
|
||||
font: 100% $font-stack;
|
||||
color: black;
|
||||
background-color: $primary-color;
|
||||
margin: 0 auto;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
}
|
||||
54
examples/immutable/src/array.rs
Normal file
54
examples/immutable/src/array.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use implicit_clone::unsync::*;
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use web_sys::{HtmlInputElement, KeyboardEvent};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct FolksViewProps {
|
||||
folks: IArray<IString>,
|
||||
}
|
||||
|
||||
#[function_component(FolksView)]
|
||||
fn folks_view(props: &FolksViewProps) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<p>{"Hello to:"}</p>
|
||||
<ul>
|
||||
{ for props.folks.iter().map(|s| html!(<li>{s}</li>)) }
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(ArrayExample)]
|
||||
pub fn array_example() -> Html {
|
||||
let folks = use_state(IArray::<IString>::default);
|
||||
let onkeyup = {
|
||||
let folks = folks.clone();
|
||||
Callback::from(move |e: KeyboardEvent| {
|
||||
if e.key() == "Enter" {
|
||||
let event: Event = e.dyn_into().unwrap_throw();
|
||||
let event_target = event.target().unwrap_throw();
|
||||
let target: HtmlInputElement = event_target.dyn_into().unwrap_throw();
|
||||
let name = target.value();
|
||||
target.set_value("");
|
||||
|
||||
folks.set(
|
||||
folks
|
||||
.iter()
|
||||
.chain(std::iter::once(IString::from(name)))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h2>{"Input"}</h2>
|
||||
<input {onkeyup} />
|
||||
<h2>{"Output"}</h2>
|
||||
<FolksView folks={&*folks} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
29
examples/immutable/src/main.rs
Normal file
29
examples/immutable/src/main.rs
Normal file
@ -0,0 +1,29 @@
|
||||
mod array;
|
||||
mod map;
|
||||
mod string;
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
use self::array::*;
|
||||
use self::map::*;
|
||||
use self::string::*;
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
html! {
|
||||
<>
|
||||
<h1>{ "IString Example" }</h1>
|
||||
<StringExample />
|
||||
<hr/>
|
||||
<h1>{ "IArray Example" }</h1>
|
||||
<ArrayExample />
|
||||
<hr/>
|
||||
<h1>{ "IMap Example" }</h1>
|
||||
<MapExample />
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
yew::Renderer::<App>::new().render();
|
||||
}
|
||||
83
examples/immutable/src/map.rs
Normal file
83
examples/immutable/src/map.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use implicit_clone::unsync::*;
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use web_sys::{HtmlInputElement, KeyboardEvent};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct DisplayProps {
|
||||
values: IMap<u32, IString>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Display(props: &DisplayProps) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<p>{"Hello to:"}</p>
|
||||
<ul>
|
||||
{ for props.values.iter().map(|(i, s)| html!(<li>{i}{" => "}{s}</li>)) }
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapExample {
|
||||
values: IMap<u32, IString>,
|
||||
}
|
||||
|
||||
pub enum MapExampleMessage {
|
||||
AddName(String),
|
||||
Noop,
|
||||
}
|
||||
|
||||
impl Component for MapExample {
|
||||
type Message = MapExampleMessage;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
values: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
MapExampleMessage::AddName(name) => {
|
||||
self.values = self
|
||||
.values
|
||||
.iter()
|
||||
.chain(std::iter::once((
|
||||
self.values.len() as u32,
|
||||
IString::from(name),
|
||||
)))
|
||||
.collect();
|
||||
true
|
||||
}
|
||||
MapExampleMessage::Noop => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
let onkeyup = link.callback(|e: KeyboardEvent| {
|
||||
if e.key() == "Enter" {
|
||||
let event: Event = e.dyn_into().unwrap_throw();
|
||||
let event_target = event.target().unwrap_throw();
|
||||
let target: HtmlInputElement = event_target.dyn_into().unwrap_throw();
|
||||
let value = target.value();
|
||||
target.set_value("");
|
||||
MapExampleMessage::AddName(value)
|
||||
} else {
|
||||
MapExampleMessage::Noop
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h2>{"Input"}</h2>
|
||||
<input {onkeyup} />
|
||||
<h2>{"Output"}</h2>
|
||||
<Display values={&self.values} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/immutable/src/string.rs
Normal file
63
examples/immutable/src/string.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use implicit_clone::unsync::*;
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use web_sys::{HtmlInputElement, InputEvent};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct DisplayProps {
|
||||
name: IString,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Display(props: &DisplayProps) -> Html {
|
||||
html! {
|
||||
<p>{"Hello "}{&props.name}{"!"}</p>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringExample {
|
||||
name: IString,
|
||||
}
|
||||
|
||||
pub enum StringExampleMessage {
|
||||
UpdateName(String),
|
||||
}
|
||||
|
||||
impl Component for StringExample {
|
||||
type Message = StringExampleMessage;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
name: "World".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
StringExampleMessage::UpdateName(name) => {
|
||||
self.name = name.into();
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
let oninput = link.callback(|e: InputEvent| {
|
||||
let event: Event = e.dyn_into().unwrap_throw();
|
||||
let event_target = event.target().unwrap_throw();
|
||||
let target: HtmlInputElement = event_target.dyn_into().unwrap_throw();
|
||||
StringExampleMessage::UpdateName(target.value())
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h2>{"Input"}</h2>
|
||||
<input value={&self.name} {oninput} />
|
||||
<h2>{"Output"}</h2>
|
||||
<Display name={&self.name} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,11 +345,11 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
|
||||
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<&'static str as IntoPropValue<AttrValue>>
|
||||
<&'static [(K, V)] as IntoPropValue<implicit_clone::unsync::IMap<K, V>>>
|
||||
<&'static [T] as IntoPropValue<implicit_clone::unsync::IArray<T>>>
|
||||
<&'static str as IntoPropValue<Classes>>
|
||||
<&'static str as IntoPropValue<Option<AttrValue>>>
|
||||
<&'static str as IntoPropValue<Option<String>>>
|
||||
and 27 others
|
||||
and 31 others
|
||||
|
||||
error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfied
|
||||
--> tests/html_macro/component-fail.rs:79:34
|
||||
@ -358,11 +358,11 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
|
||||
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<&'static str as IntoPropValue<AttrValue>>
|
||||
<&'static [(K, V)] as IntoPropValue<implicit_clone::unsync::IMap<K, V>>>
|
||||
<&'static [T] as IntoPropValue<implicit_clone::unsync::IArray<T>>>
|
||||
<&'static str as IntoPropValue<Classes>>
|
||||
<&'static str as IntoPropValue<Option<AttrValue>>>
|
||||
<&'static str as IntoPropValue<Option<String>>>
|
||||
and 27 others
|
||||
and 31 others
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/html_macro/component-fail.rs:80:31
|
||||
|
||||
@ -246,11 +246,11 @@ error[E0308]: mismatched types
|
||||
40 | html! { <option selected=1 /> };
|
||||
| ^ expected `bool`, found integer
|
||||
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:43:26
|
||||
|
|
||||
43 | html! { <input type={()} /> };
|
||||
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
||||
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
||||
|
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -258,11 +258,11 @@ note: required by `into_prop_value`
|
||||
| fn into_prop_value(self) -> T;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:44:27
|
||||
|
|
||||
44 | html! { <input value={()} /> };
|
||||
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
||||
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
||||
|
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -270,11 +270,11 @@ note: required by `into_prop_value`
|
||||
| fn into_prop_value(self) -> T;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `(): IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:45:22
|
||||
|
|
||||
45 | html! { <a href={()} /> };
|
||||
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
||||
| ^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `()`
|
||||
|
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -282,11 +282,11 @@ note: required by `into_prop_value`
|
||||
| fn into_prop_value(self) -> T;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:46:28
|
||||
|
|
||||
46 | html! { <input string={NotToString} /> };
|
||||
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `NotToString`
|
||||
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `NotToString`
|
||||
|
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -294,17 +294,17 @@ note: required by `into_prop_value`
|
||||
| fn into_prop_value(self) -> T;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:47:23
|
||||
|
|
||||
47 | html! { <a media={Some(NotToString)} /> };
|
||||
| ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `Option<NotToString>`
|
||||
| ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<NotToString>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<String>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
|
||||
<Option<String> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
and 4 others
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -312,17 +312,17 @@ note: required by `into_prop_value`
|
||||
| fn into_prop_value(self) -> T;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:48:22
|
||||
|
|
||||
48 | html! { <a href={Some(5)} /> };
|
||||
| ^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `Option<{integer}>`
|
||||
| ^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `Option<{integer}>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<String>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
|
||||
<Option<String> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
and 4 others
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -416,10 +416,10 @@ error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<String>>>
|
||||
<Option<&'static str> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
<Option<F> as IntoPropValue<Option<yew::Callback<I, O>>>>
|
||||
<Option<String> as IntoPropValue<Option<AttrValue>>>
|
||||
<Option<Rc<str>> as IntoPropValue<Option<implicit_clone::unsync::IString>>>
|
||||
and 4 others
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
@ -451,11 +451,11 @@ note: required by a bound in `yew::html::onclick::Wrapper::__macro_new`
|
||||
| |_^ required by this bound in `yew::html::onclick::Wrapper::__macro_new`
|
||||
= note: this error originates in the macro `impl_action` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<AttrValue>>` is not satisfied
|
||||
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<implicit_clone::unsync::IString>>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:60:28
|
||||
|
|
||||
60 | html! { <input string={NotToString} /> };
|
||||
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `NotToString`
|
||||
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<implicit_clone::unsync::IString>>` is not implemented for `NotToString`
|
||||
|
|
||||
note: required by `into_prop_value`
|
||||
--> $WORKSPACE/packages/yew/src/html/conversion.rs
|
||||
|
||||
@ -29,6 +29,7 @@ thiserror = "1.0"
|
||||
|
||||
futures = { version = "0.3", optional = true }
|
||||
html-escape = { version = "0.2.9", optional = true }
|
||||
implicit-clone = { version = "0.2", features = ["map"] }
|
||||
base64ct = { version = "1.5.0", features = ["std"], optional = true }
|
||||
bincode = { version = "1.3.3", optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@ -1,33 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use implicit_clone::unsync::{IArray, IMap};
|
||||
pub use implicit_clone::ImplicitClone;
|
||||
|
||||
use super::super::callback::Callback;
|
||||
use super::{BaseComponent, Children, ChildrenRenderer, Component, NodeRef, Scope};
|
||||
use crate::virtual_dom::{AttrValue, VChild, VNode};
|
||||
|
||||
/// Marker trait for types that the [`html!`](macro@crate::html) macro may clone implicitly.
|
||||
pub trait ImplicitClone: Clone {}
|
||||
|
||||
impl<T: ImplicitClone> ImplicitClone for Option<T> {}
|
||||
impl<T: ?Sized> ImplicitClone for Rc<T> {}
|
||||
|
||||
impl ImplicitClone for NodeRef {}
|
||||
impl<Comp: Component> ImplicitClone for Scope<Comp> {}
|
||||
// TODO there are still a few missing
|
||||
|
||||
macro_rules! impl_implicit_clone {
|
||||
($($ty:ty),+ $(,)?) => {
|
||||
$(impl ImplicitClone for $ty {})*
|
||||
};
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl_implicit_clone!(
|
||||
u8, u16, u32, u64, u128,
|
||||
i8, i16, i32, i64, i128,
|
||||
f32, f64,
|
||||
&'static str,
|
||||
);
|
||||
|
||||
/// A trait similar to `Into<T>` which allows conversion to a value of a `Properties` struct.
|
||||
pub trait IntoPropValue<T> {
|
||||
/// Convert `self` to a value of a `Properties` struct.
|
||||
@ -194,6 +177,34 @@ impl_into_prop!(|value: String| -> AttrValue { AttrValue::Rc(Rc::from(value)) })
|
||||
impl_into_prop!(|value: Rc<str>| -> AttrValue { AttrValue::Rc(value) });
|
||||
impl_into_prop!(|value: VNode| -> Children { Children::new(vec![value]) });
|
||||
|
||||
impl<T: ImplicitClone + 'static> IntoPropValue<IArray<T>> for &'static [T] {
|
||||
fn into_prop_value(self) -> IArray<T> {
|
||||
IArray::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ImplicitClone + 'static> IntoPropValue<IArray<T>> for Vec<T> {
|
||||
fn into_prop_value(self) -> IArray<T> {
|
||||
IArray::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + std::hash::Hash + ImplicitClone + 'static, V: PartialEq + ImplicitClone + 'static>
|
||||
IntoPropValue<IMap<K, V>> for &'static [(K, V)]
|
||||
{
|
||||
fn into_prop_value(self) -> IMap<K, V> {
|
||||
IMap::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + std::hash::Hash + ImplicitClone + 'static, V: PartialEq + ImplicitClone + 'static>
|
||||
IntoPropValue<IMap<K, V>> for indexmap::IndexMap<K, V>
|
||||
{
|
||||
fn into_prop_value(self) -> IMap<K, V> {
|
||||
IMap::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@ -19,13 +19,7 @@ pub mod vtag;
|
||||
#[doc(hidden)]
|
||||
pub mod vtext;
|
||||
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::hint::unreachable_unchecked;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
@ -49,158 +43,7 @@ pub use self::vtag::VTag;
|
||||
pub use self::vtext::VText;
|
||||
|
||||
/// Attribute value
|
||||
#[derive(Debug)]
|
||||
pub enum AttrValue {
|
||||
/// String living for `'static`
|
||||
Static(&'static str),
|
||||
/// Reference counted string
|
||||
Rc(Rc<str>),
|
||||
}
|
||||
|
||||
impl Deref for AttrValue {
|
||||
type Target = str;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
AttrValue::Static(s) => *s,
|
||||
AttrValue::Rc(s) => &*s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AttrValue {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_ref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: &'static str) -> Self {
|
||||
AttrValue::Static(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: String) -> Self {
|
||||
AttrValue::Rc(Rc::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<str>> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: Rc<str>) -> Self {
|
||||
AttrValue::Rc(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: Cow<'static, str>) -> Self {
|
||||
match s {
|
||||
Cow::Borrowed(s) => s.into(),
|
||||
Cow::Owned(s) => s.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AttrValue {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
AttrValue::Static(s) => AttrValue::Static(s),
|
||||
AttrValue::Rc(s) => AttrValue::Rc(Rc::clone(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for AttrValue {
|
||||
fn borrow(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AttrValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AttrValue::Static(s) => write!(f, "{}", s),
|
||||
AttrValue::Rc(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AttrValue {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_ref() == other.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AttrValue {}
|
||||
|
||||
impl AttrValue {
|
||||
/// Consumes the AttrValue and returns the owned String from the AttrValue whenever possible.
|
||||
/// For AttrValue::Rc the <str> is cloned to String in case there are other Rc or Weak pointers
|
||||
/// to the same allocation.
|
||||
pub fn into_string(self) -> String {
|
||||
match self {
|
||||
AttrValue::Static(s) => (*s).to_owned(),
|
||||
AttrValue::Rc(mut rc) => {
|
||||
if let Some(s) = Rc::get_mut(&mut rc) {
|
||||
(*s).to_owned()
|
||||
} else {
|
||||
rc.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_attr_value {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_into_string() {
|
||||
let av = AttrValue::Static("str");
|
||||
assert_eq!(av.into_string(), "str");
|
||||
|
||||
let av = AttrValue::Rc("Rc<str>".into());
|
||||
assert_eq!(av.into_string(), "Rc<str>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_string() {
|
||||
let av = AttrValue::from("str");
|
||||
assert_eq!(av.into_string(), "str");
|
||||
|
||||
let av = AttrValue::from("String".to_string());
|
||||
assert_eq!(av.into_string(), "String");
|
||||
|
||||
let av = AttrValue::from(Cow::from("BorrowedCow"));
|
||||
assert_eq!(av.into_string(), "BorrowedCow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
// construct 3 AttrValue with same embedded value; expectation is that all are equal
|
||||
let a = AttrValue::Static("same");
|
||||
let b = AttrValue::Rc("same".into());
|
||||
|
||||
assert_eq!(a, b);
|
||||
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
pub type AttrValue = implicit_clone::unsync::IString;
|
||||
|
||||
#[cfg(any(feature = "ssr", feature = "hydration"))]
|
||||
mod feat_ssr_hydration {
|
||||
@ -278,7 +121,7 @@ mod feat_ssr_hydration {
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn name(&self) -> super::Cow<'static, str> {
|
||||
pub fn name(&self) -> std::borrow::Cow<'static, str> {
|
||||
match self {
|
||||
#[cfg(debug_assertions)]
|
||||
Self::Component(m) => format!("Component({})", m).into(),
|
||||
|
||||
24
website/docs/advanced-topics/immutable.mdx
Normal file
24
website/docs/advanced-topics/immutable.mdx
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 'Immutable Types'
|
||||
description: 'Immutable data structures for Yew'
|
||||
---
|
||||
|
||||
## What are immutable types?
|
||||
|
||||
These are types that you can instantiate but never mutate the values. In order
|
||||
to update a value, you must instantiate a new value.
|
||||
|
||||
## Why using immutable types?
|
||||
|
||||
Properties, like in React, are propagated from ancestors to
|
||||
children. This means that the properties must live when each component is
|
||||
updated. This is why properties should —ideally— be cheap to clone. In order to
|
||||
achieve this we usually wrap things in `Rc`.
|
||||
|
||||
Immutable types are a great fit for holding property's values because they can
|
||||
be cheaply cloned when passed from component to component.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable)
|
||||
- [Crate `implicit-clone`](https://docs.rs/implicit-clone/)
|
||||
@ -129,6 +129,7 @@ module.exports = {
|
||||
'advanced-topics/optimizations',
|
||||
'advanced-topics/portals',
|
||||
'advanced-topics/server-side-rendering',
|
||||
'advanced-topics/immutable',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user