diff --git a/Cargo.lock b/Cargo.lock index ca18847cf55..822accc5b3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2200,6 +2200,7 @@ dependencies = [ name = "nested_list" version = "0.1.0" dependencies = [ + "implicit-clone", "log", "wasm-logger", "yew", @@ -4350,6 +4351,7 @@ dependencies = [ name = "yew-macro" version = "0.21.0" dependencies = [ + "implicit-clone", "once_cell", "prettyplease", "proc-macro-error", diff --git a/Cargo.toml b/Cargo.toml index 7254f0ac2bb..28d75d73b23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,4 @@ unexpected_cfgs = { level = "warn", check-cfg = [ ]} [workspace.dependencies] tokio = { version = "1.47.1" } +implicit-clone = { version = "0.5.1" } diff --git a/examples/immutable/Cargo.toml b/examples/immutable/Cargo.toml index d8902c3652e..46d131db951 100644 --- a/examples/immutable/Cargo.toml +++ b/examples/immutable/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -implicit-clone = { version = "0.5", features = ["map"] } +implicit-clone = { workspace = true, features = ["map"] } wasm-bindgen = "0.2" web-sys = "0.3" yew = { path = "../../packages/yew", features = ["csr"] } diff --git a/examples/nested_list/Cargo.toml b/examples/nested_list/Cargo.toml index 04e6bcf6913..2a44093f095 100644 --- a/examples/nested_list/Cargo.toml +++ b/examples/nested_list/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] +implicit-clone = { workspace = true } log = "0.4" wasm-logger = "0.2" yew = { path = "../../packages/yew", features = ["csr"] } diff --git a/examples/nested_list/src/list.rs b/examples/nested_list/src/list.rs index 042227750df..c73c071d25e 100644 --- a/examples/nested_list/src/list.rs +++ b/examples/nested_list/src/list.rs @@ -1,5 +1,4 @@ -use std::rc::Rc; - +use implicit_clone::unsync::IArray; use yew::prelude::*; use yew::virtual_dom::VChild; @@ -14,9 +13,9 @@ pub enum Msg { #[derive(Clone, PartialEq, Properties)] pub struct Props { #[prop_or_default] - pub header: Vec>, + pub header: IArray>, #[prop_or_default] - pub children: Vec>, + pub children: IArray>, pub on_hover: Callback, pub weak_link: WeakComponentLink, @@ -56,9 +55,9 @@ impl Component for List { html! {
- { ctx.props().header.clone() } + { &ctx.props().header }
- { Self::view_items(ctx.props().children.clone()) } + { Self::view_items(&ctx.props().children) }
@@ -67,13 +66,13 @@ impl Component for List { } impl List { - fn view_items(children: Vec>) -> Html { + fn view_items(children: &IArray>) -> Html { children - .into_iter() + .iter() .filter(|c| !c.props.hide) .enumerate() .map(|(i, mut c)| { - let props = Rc::make_mut(&mut c.props); + let props = c.get_mut(); props.name = format!("#{} - {}", i + 1, props.name).into(); c }) diff --git a/examples/nested_list/src/main.rs b/examples/nested_list/src/main.rs index 480b680a3b7..510b941941c 100644 --- a/examples/nested_list/src/main.rs +++ b/examples/nested_list/src/main.rs @@ -40,7 +40,7 @@ impl PartialEq for WeakComponentLink { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, ImplicitClone, PartialEq, Eq, Hash)] pub enum Hovered { Header, Item(AttrValue), @@ -48,8 +48,6 @@ pub enum Hovered { None, } -impl ImplicitClone for Hovered {} - impl fmt::Display for Hovered { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/examples/timer/src/main.rs b/examples/timer/src/main.rs index 241f86a9805..742b1200c3f 100644 --- a/examples/timer/src/main.rs +++ b/examples/timer/src/main.rs @@ -1,6 +1,6 @@ use gloo::console::{self, Timer}; use gloo::timers::callback::{Interval, Timeout}; -use yew::{html, Component, Context, Html}; +use yew::prelude::*; pub enum Msg { StartTimeout, @@ -13,7 +13,7 @@ pub enum Msg { pub struct App { time: String, - messages: Vec<&'static str>, + messages: Vec, _standalone: (Interval, Interval), interval: Option, timeout: Option, @@ -68,7 +68,7 @@ impl Component for App { self.messages.clear(); console::clear!(); - self.messages.push("Timer started!"); + self.log("Timer started!"); self.console_timer = Some(Timer::new("Timer")); true } @@ -82,18 +82,18 @@ impl Component for App { self.messages.clear(); console::clear!(); - self.messages.push("Interval started!"); + self.log("Interval started!"); true } Msg::Cancel => { self.cancel(); - self.messages.push("Canceled!"); + self.log("Canceled!"); console::warn!("Canceled!"); true } Msg::Done => { self.cancel(); - self.messages.push("Done!"); + self.log("Done!"); // todo weblog // ConsoleService::group(); @@ -107,7 +107,7 @@ impl Component for App { true } Msg::Tick => { - self.messages.push("Tick..."); + self.log("Tick..."); // todo weblog // ConsoleService::count_named("Tick"); true @@ -139,7 +139,7 @@ impl Component for App { { &self.time }
- { for self.messages.iter().map(|message| html! {

{ *message }

}) } + { for self.messages.iter().map(|message| html! {

{ message }

}) }
@@ -147,6 +147,12 @@ impl Component for App { } } +impl App { + fn log(&mut self, message: impl Into) { + self.messages.push(message.into()); + } +} + fn main() { yew::Renderer::::new().render(); } diff --git a/examples/timer_functional/src/main.rs b/examples/timer_functional/src/main.rs index 5a717cb5476..4f2ffc067ae 100644 --- a/examples/timer_functional/src/main.rs +++ b/examples/timer_functional/src/main.rs @@ -18,11 +18,34 @@ enum TimerAction { #[derive(Clone, Debug)] struct TimerState { - messages: Vec<&'static str>, + messages: Messages, interval_handle: Option>, timeout_handle: Option>, } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct Messages(Vec); + +impl Messages { + fn log(&mut self, message: impl Into) { + self.0.push(message.into()); + } +} + +impl std::ops::Deref for Messages { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromIterator<&'static str> for Messages { + fn from_iter>(it: T) -> Self { + Messages(it.into_iter().map(Into::into).collect()) + } +} + impl PartialEq for TimerState { fn eq(&self, other: &Self) -> bool { self.messages == other.messages @@ -37,7 +60,7 @@ impl Reducible for TimerState { match action { TimerAction::Add(message) => { let mut messages = self.messages.clone(); - messages.push(message); + messages.log(message); Rc::new(TimerState { messages, interval_handle: self.interval_handle.clone(), @@ -45,18 +68,18 @@ impl Reducible for TimerState { }) } TimerAction::SetInterval(t) => Rc::new(TimerState { - messages: vec!["Interval started!"], + messages: ["Interval started!"].into_iter().collect(), interval_handle: Some(Rc::from(t)), timeout_handle: self.timeout_handle.clone(), }), TimerAction::SetTimeout(t) => Rc::new(TimerState { - messages: vec!["Timer started!!"], + messages: ["Timer started!!"].into_iter().collect(), interval_handle: self.interval_handle.clone(), timeout_handle: Some(Rc::from(t)), }), TimerAction::TimeoutDone => { let mut messages = self.messages.clone(); - messages.push("Done!"); + messages.log("Done!"); Rc::new(TimerState { messages, interval_handle: self.interval_handle.clone(), @@ -65,7 +88,7 @@ impl Reducible for TimerState { } TimerAction::Cancel => { let mut messages = self.messages.clone(); - messages.push("Canceled!"); + messages.log("Canceled!"); Rc::new(TimerState { messages, interval_handle: None, @@ -94,7 +117,7 @@ fn clock() -> Html { #[function_component] fn App() -> Html { let state = use_reducer(|| TimerState { - messages: Vec::new(), + messages: Default::default(), interval_handle: None, timeout_handle: None, }); @@ -105,7 +128,7 @@ fn App() -> Html { .iter() .map(|message| { key += 1; - html! {

{ *message }

} + html! {

{ message }

} }) .collect(); diff --git a/packages/yew-macro/Cargo.toml b/packages/yew-macro/Cargo.toml index 248c1057b00..94deaa88496 100644 --- a/packages/yew-macro/Cargo.toml +++ b/packages/yew-macro/Cargo.toml @@ -28,6 +28,7 @@ rustversion = "1" [dev-dependencies] trybuild = "1" yew = { path = "../yew" } +implicit-clone = { workspace = true } [lints] workspace = true diff --git a/packages/yew-macro/tests/html_macro/component-pass.rs b/packages/yew-macro/tests/html_macro/component-pass.rs index 5b6ec617cb5..62c2b90fc92 100644 --- a/packages/yew-macro/tests/html_macro/component-pass.rs +++ b/packages/yew-macro/tests/html_macro/component-pass.rs @@ -59,7 +59,7 @@ impl ::yew::Component for Container { } } -#[derive(::std::clone::Clone, ::std::cmp::PartialEq)] +#[derive(::std::clone::Clone, ::implicit_clone::ImplicitClone, ::std::cmp::PartialEq)] pub enum ChildrenVariants { Child(::yew::virtual_dom::VChild), AltChild(::yew::virtual_dom::VChild), diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index ac614369d82..8488c5ceaa4 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -472,7 +472,7 @@ error[E0277]: the trait bound `NotToString: IntoPropValue>` `&'static str` implements `IntoPropValue` `&'static str` implements `IntoPropValue` - `&String` implements `IntoPropValue` + `&ChildrenRenderer` implements `IntoPropValue` and $N others error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied @@ -672,7 +672,7 @@ error[E0277]: the trait bound `NotToString: IntoPropValue>` `&'static str` implements `IntoPropValue` `&'static str` implements `IntoPropValue` - `&String` implements `IntoPropValue` + `&ChildrenRenderer` implements `IntoPropValue` and $N others error[E0277]: the trait bound `(): IntoPropValue` is not satisfied diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index cc72f5372a5..0044dc73896 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -27,7 +27,7 @@ yew-macro = { version = "^0.21.0", path = "../yew-macro" } thiserror = "2.0" futures = { version = "0.3", default-features = false, features = ["std"] } html-escape = { version = "0.2.13", optional = true } -implicit-clone = { version = "0.5", features = ["map"] } +implicit-clone = { workspace = true, features = ["map"] } base64ct = { version = "1.6.0", features = ["std"], optional = true } bincode = { version = "2.0.0-rc.3", optional = true, features = ["serde"] } serde = { version = "1", features = ["derive"] } diff --git a/packages/yew/src/html/classes.rs b/packages/yew/src/html/classes.rs index d2305a129e4..78ca7d83e7f 100644 --- a/packages/yew/src/html/classes.rs +++ b/packages/yew/src/html/classes.rs @@ -2,23 +2,21 @@ use std::borrow::Cow; use std::iter::FromIterator; use std::rc::Rc; -use implicit_clone::ImplicitClone; use indexmap::IndexSet; use super::IntoPropValue; +use crate::html::ImplicitClone; use crate::utils::RcExt; use crate::virtual_dom::AttrValue; /// A set of classes, cheap to clone. /// /// The preferred way of creating this is using the [`classes!`][yew::classes!] macro. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, ImplicitClone, Default)] pub struct Classes { set: Rc>, } -impl ImplicitClone for Classes {} - /// helper method to efficiently turn a set of classes into a space-separated /// string. Abstracts differences between ToString and IntoPropValue. The /// `rest` iterator is cloned to pre-compute the length of the String; it diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index 701f00fd9df..94205309bb5 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -3,7 +3,8 @@ use std::fmt; use std::rc::Rc; -use crate::html::Html; +use crate::html::{Html, ImplicitClone}; +use crate::utils::RcExt; use crate::virtual_dom::{VChild, VComp, VList, VNode}; use crate::{BaseComponent, Properties}; @@ -151,14 +152,28 @@ pub type Children = ChildrenRenderer; pub type ChildrenWithProps = ChildrenRenderer>; /// A type used for rendering children html. -#[derive(Clone)] pub struct ChildrenRenderer { - pub(crate) children: Vec, + pub(crate) children: Option>>, } +impl Clone for ChildrenRenderer { + fn clone(&self) -> Self { + Self { + children: self.children.clone(), + } + } +} + +impl ImplicitClone for ChildrenRenderer {} + impl PartialEq for ChildrenRenderer { fn eq(&self, other: &Self) -> bool { - self.children == other.children + match (self.children.as_ref(), other.children.as_ref()) { + (Some(a), Some(b)) => a == b, + (Some(a), None) => a.is_empty(), + (None, Some(b)) => b.is_empty(), + (None, None) => true, + } } } @@ -168,24 +183,30 @@ where { /// Create children pub fn new(children: Vec) -> Self { - Self { children } + if children.is_empty() { + Self { children: None } + } else { + Self { + children: Some(Rc::new(children)), + } + } } /// Children list is empty pub fn is_empty(&self) -> bool { - self.children.is_empty() + self.children.as_ref().map(|x| x.is_empty()).unwrap_or(true) } /// Number of children elements pub fn len(&self) -> usize { - self.children.len() + self.children.as_ref().map(|x| x.len()).unwrap_or(0) } /// Render children components and return `Iterator` pub fn iter(&self) -> impl Iterator + '_ { // clone each child lazily. // This way `self.iter().next()` only has to clone a single node. - self.children.iter().cloned() + self.children.iter().flat_map(|x| x.iter()).cloned() } /// Convert the children elements to another object (if there are any). @@ -197,7 +218,7 @@ where /// children.map(|children| { /// html! { ///
- /// {children.clone()} + /// {children} ///
/// } /// }) @@ -215,7 +236,7 @@ where impl Default for ChildrenRenderer { fn default() -> Self { Self { - children: Vec::new(), + children: Default::default(), } } } @@ -226,20 +247,28 @@ impl fmt::Debug for ChildrenRenderer { } } -impl IntoIterator for ChildrenRenderer { +impl IntoIterator for ChildrenRenderer { type IntoIter = std::vec::IntoIter; type Item = T; fn into_iter(self) -> Self::IntoIter { - self.children.into_iter() + if let Some(children) = self.children { + let children = RcExt::unwrap_or_clone(children); + children.into_iter() + } else { + Vec::new().into_iter() + } } } impl From> for Html { fn from(mut val: ChildrenRenderer) -> Self { - if val.children.len() == 1 { - if let Some(m) = val.children.pop() { - return m; + if let Some(children) = val.children.as_mut() { + if children.len() == 1 { + let children = Rc::make_mut(children); + if let Some(m) = children.pop() { + return m; + } } } @@ -249,10 +278,7 @@ impl From> for Html { impl From> for VList { fn from(val: ChildrenRenderer) -> Self { - if val.is_empty() { - return VList::new(); - } - VList::with_children(val.children, None) + VList::from(val.children) } } @@ -266,7 +292,7 @@ where .into_iter() .map(VComp::from) .map(VNode::from) - .collect(), + .collect::>(), ) } } diff --git a/packages/yew/src/html/conversion/into_prop_value.rs b/packages/yew/src/html/conversion/into_prop_value.rs index cc91bfd4842..579660920dd 100644 --- a/packages/yew/src/html/conversion/into_prop_value.rs +++ b/packages/yew/src/html/conversion/into_prop_value.rs @@ -6,10 +6,9 @@ use implicit_clone::unsync::{IArray, IMap}; pub use implicit_clone::ImplicitClone; use crate::callback::Callback; -use crate::html::{BaseComponent, ChildrenRenderer, Component, NodeRef, Scope}; +use crate::html::{BaseComponent, ChildrenRenderer, Component, Scope}; use crate::virtual_dom::{AttrValue, VChild, VList, VNode, VText}; -impl ImplicitClone for NodeRef {} impl ImplicitClone for Scope {} // TODO there are still a few missing @@ -126,7 +125,7 @@ where { #[inline] fn into_prop_value(self) -> ChildrenRenderer { - ChildrenRenderer::new(self.into_iter().map(|m| m.into()).collect()) + ChildrenRenderer::new(self.into_iter().map(|m| m.into()).collect::>()) } } @@ -167,6 +166,13 @@ impl IntoPropValue for ChildrenRenderer { } } +impl IntoPropValue for &ChildrenRenderer { + #[inline] + fn into_prop_value(self) -> VNode { + VNode::VList(Rc::new(VList::from(self.children.clone()))) + } +} + impl IntoPropValue> for VNode { #[inline] fn into_prop_value(self) -> ChildrenRenderer { @@ -184,14 +190,14 @@ impl IntoPropValue> for VText { impl IntoPropValue for ChildrenRenderer { #[inline] fn into_prop_value(self) -> VList { - VList::with_children(self.children, None) + VList::from(self.children) } } impl IntoPropValue for VChild { #[inline] fn into_prop_value(self) -> VList { - VList::with_children(vec![self.into()], None) + VList::from(VNode::from(self)) } } @@ -204,7 +210,7 @@ impl IntoPropValue> for AttrValue { impl IntoPropValue for Vec { #[inline] fn into_prop_value(self) -> VNode { - VNode::VList(Rc::new(VList::with_children(self, None))) + VNode::VList(Rc::new(VList::from(self))) } } @@ -326,6 +332,7 @@ impl_into_prop_value_via_display!(f64); impl_into_prop_value_via_attr_value!(String); impl_into_prop_value_via_attr_value!(AttrValue); +impl_into_prop_value_via_attr_value!(&AttrValue); impl_into_prop_value_via_attr_value!(Rc); impl_into_prop_value_via_attr_value!(Cow<'static, str>); diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 1a746259225..622fed48c87 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -87,7 +87,7 @@ impl IntoHtmlResult for Html { /// ``` /// ## Relevant examples /// - [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) -#[derive(Default, Clone)] +#[derive(Default, Clone, ImplicitClone)] pub struct NodeRef(Rc>); impl PartialEq for NodeRef { diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index 4f33ea672b2..743d266e986 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -2,6 +2,8 @@ use std::marker::PhantomData; +use implicit_clone::unsync::IArray; +use implicit_clone::ImplicitClone; use yew::html::ChildrenRenderer; /// Map `IntoIterator>` to `Iterator` @@ -15,38 +17,64 @@ where /// A special type necessary for flattening components returned from nested html macros. #[derive(Debug)] -pub struct NodeSeq(Vec, PhantomData); +pub struct NodeSeq(IArray, PhantomData); -impl, OUT> From for NodeSeq { +impl, OUT: ImplicitClone + 'static> From for NodeSeq { fn from(val: IN) -> Self { - Self(vec![val.into()], PhantomData) + Self(IArray::Single([val.into()]), PhantomData) } } -impl, OUT> From> for NodeSeq { +impl, OUT: ImplicitClone + 'static> From> for NodeSeq { fn from(val: Option) -> Self { - Self(val.map(|s| vec![s.into()]).unwrap_or_default(), PhantomData) + Self( + val.map(|s| IArray::Single([s.into()])).unwrap_or_default(), + PhantomData, + ) } } -impl, OUT> From> for NodeSeq { - fn from(val: Vec) -> Self { - Self(val.into_iter().map(|x| x.into()).collect(), PhantomData) +impl, OUT: ImplicitClone + 'static> From> for NodeSeq { + fn from(mut val: Vec) -> Self { + if val.len() == 1 { + let item = val.pop().unwrap(); + Self(IArray::Single([item.into()]), PhantomData) + } else { + Self(val.into_iter().map(|x| x.into()).collect(), PhantomData) + } } } -impl + Clone, OUT> From<&ChildrenRenderer> for NodeSeq { +impl + ImplicitClone, OUT: ImplicitClone + 'static> From> + for NodeSeq +{ + fn from(val: IArray) -> Self { + Self(val.iter().map(|x| x.into()).collect(), PhantomData) + } +} + +impl + ImplicitClone, OUT: ImplicitClone + 'static> From<&IArray> + for NodeSeq +{ + fn from(val: &IArray) -> Self { + Self(val.iter().map(|x| x.into()).collect(), PhantomData) + } +} + +impl + Clone, OUT: ImplicitClone + 'static> From<&ChildrenRenderer> + for NodeSeq +{ fn from(val: &ChildrenRenderer) -> Self { Self(val.iter().map(|x| x.into()).collect(), PhantomData) } } -impl IntoIterator for NodeSeq { - type IntoIter = std::vec::IntoIter; +impl IntoIterator for NodeSeq { + type IntoIter = implicit_clone::unsync::Iter; type Item = OUT; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + self.0.iter() } } diff --git a/packages/yew/src/virtual_dom/key.rs b/packages/yew/src/virtual_dom/key.rs index da68583d5f2..11bda7ea4bc 100644 --- a/packages/yew/src/virtual_dom/key.rs +++ b/packages/yew/src/virtual_dom/key.rs @@ -9,7 +9,7 @@ use crate::html::ImplicitClone; /// Represents the (optional) key of Yew's virtual nodes. /// /// Keys are cheap to clone. -#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +#[derive(Clone, ImplicitClone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Key { key: Rc, } @@ -47,8 +47,6 @@ impl From for Key { } } -impl ImplicitClone for Key {} - macro_rules! key_impl_from_to_string { ($type:ty) => { impl From<$type> for Key { diff --git a/packages/yew/src/virtual_dom/listeners.rs b/packages/yew/src/virtual_dom/listeners.rs index 9bd8359f5e0..57f94fa15c1 100644 --- a/packages/yew/src/virtual_dom/listeners.rs +++ b/packages/yew/src/virtual_dom/listeners.rs @@ -160,18 +160,17 @@ gen_listener_kinds! { } /// A list of event listeners -#[derive(Debug)] +#[derive(Debug, Clone, ImplicitClone, Default)] pub enum Listeners { /// No listeners registered or pending. /// Distinct from `Pending` with an empty slice to avoid an allocation. + #[default] None, /// Not yet added to the element or registry Pending(Box<[Option>]>), } -impl ImplicitClone for Listeners {} - impl PartialEq for Listeners { fn eq(&self, rhs: &Self) -> bool { use Listeners::*; @@ -203,18 +202,3 @@ impl PartialEq for Listeners { } } } - -impl Clone for Listeners { - fn clone(&self) -> Self { - match self { - Self::None => Self::None, - Self::Pending(v) => Self::Pending(v.clone()), - } - } -} - -impl Default for Listeners { - fn default() -> Self { - Self::None - } -} diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 15daaafba20..b3146fe6a0f 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -216,6 +216,17 @@ where } } +impl VChild +where + COMP: BaseComponent, + COMP::Properties: Clone, +{ + /// Get a mutable reference to the underlying properties. + pub fn get_mut(&mut self) -> &mut COMP::Properties { + Rc::make_mut(&mut self.props) + } +} + impl From> for VComp where COMP: BaseComponent, diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index c315b05f8ec..574eca3e5d9 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -3,7 +3,6 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use super::{Key, VNode}; -use crate::html::ImplicitClone; #[doc(hidden)] #[derive(Clone, Copy, Debug, PartialEq)] @@ -25,11 +24,18 @@ pub struct VList { pub key: Option, } -impl ImplicitClone for VList {} - impl PartialEq for VList { fn eq(&self, other: &Self) -> bool { - self.key == other.key && self.children == other.children + if self.key != other.key { + return false; + } + + match (self.children.as_ref(), other.children.as_ref()) { + (Some(a), Some(b)) => a == b, + (Some(a), None) => a.is_empty(), + (None, Some(b)) => b.is_empty(), + (None, None) => true, + } } } @@ -61,6 +67,65 @@ impl DerefMut for VList { } } +impl> FromIterator for VList { + fn from_iter>(iter: T) -> Self { + let children = iter.into_iter().map(|n| n.into()).collect::>(); + if children.is_empty() { + VList::new() + } else { + VList { + children: Some(Rc::new(children)), + fully_keyed: FullyKeyedState::Unknown, + key: None, + } + } + } +} + +impl From>>> for VList { + fn from(children: Option>>) -> Self { + if children.as_ref().map(|x| x.is_empty()).unwrap_or(true) { + VList::new() + } else { + let mut vlist = VList { + children, + fully_keyed: FullyKeyedState::Unknown, + key: None, + }; + vlist.recheck_fully_keyed(); + vlist + } + } +} + +impl From> for VList { + fn from(children: Vec) -> Self { + if children.is_empty() { + VList::new() + } else { + let mut vlist = VList { + children: Some(Rc::new(children)), + fully_keyed: FullyKeyedState::Unknown, + key: None, + }; + vlist.recheck_fully_keyed(); + vlist + } + } +} + +impl From for VList { + fn from(child: VNode) -> Self { + let mut vlist = VList { + children: Some(Rc::new(vec![child])), + fully_keyed: FullyKeyedState::Unknown, + key: None, + }; + vlist.recheck_fully_keyed(); + vlist + } +} + impl VList { /// Creates a new empty [VList] instance. pub const fn new() -> Self { @@ -73,12 +138,8 @@ impl VList { /// Creates a new [VList] instance with children. pub fn with_children(children: Vec, key: Option) -> Self { - let mut vlist = VList { - fully_keyed: FullyKeyedState::Unknown, - children: Some(Rc::new(children)), - key, - }; - vlist.recheck_fully_keyed(); + let mut vlist = VList::from(children); + vlist.key = key; vlist } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 7a4f35b4c6b..baf12f857a3 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -13,7 +13,7 @@ use crate::virtual_dom::VRaw; use crate::AttrValue; /// Bind virtual element to a DOM reference. -#[derive(Clone, PartialEq)] +#[derive(Clone, ImplicitClone, PartialEq)] #[must_use = "html does not do anything unless returned to Yew for rendering."] pub enum VNode { /// A bind between `VTag` and `Element`. @@ -36,8 +36,6 @@ pub enum VNode { VRaw(VRaw), } -impl ImplicitClone for VNode {} - impl VNode { pub fn key(&self) -> Option<&Key> { match self { @@ -65,8 +63,7 @@ impl VNode { match *self { Self::VList(ref mut m) => return Rc::make_mut(m), _ => { - *self = - VNode::VList(Rc::new(VList::with_children(vec![mem::take(self)], None))); + *self = VNode::VList(Rc::new(VList::from(mem::take(self)))); } } } @@ -172,9 +169,8 @@ impl From for VNode { impl> FromIterator for VNode { fn from_iter>(iter: T) -> Self { - VNode::VList(Rc::new(VList::with_children( - iter.into_iter().map(|n| n.into()).collect(), - None, + VNode::VList(Rc::new(VList::from_iter( + iter.into_iter().map(|n| n.into()), ))) } } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index 9c8e7a3c46a..82bcc110063 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -2,13 +2,11 @@ use crate::html::ImplicitClone; use crate::AttrValue; /// A raw HTML string to be used in VDOM. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, ImplicitClone, Debug, PartialEq, Eq)] pub struct VRaw { pub html: AttrValue, } -impl ImplicitClone for VRaw {} - impl From for VRaw { fn from(html: AttrValue) -> Self { Self { html } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index d509eb5d9cb..c129a5f40be 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -2,7 +2,7 @@ use super::{Key, VNode}; use crate::html::ImplicitClone; /// This struct represents a suspendable DOM fragment. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, ImplicitClone, Debug, PartialEq)] pub struct VSuspense { /// Child nodes. pub(crate) children: VNode, @@ -14,8 +14,6 @@ pub struct VSuspense { pub(crate) key: Option, } -impl ImplicitClone for VSuspense {} - impl VSuspense { pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option) -> Self { Self { diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 96875e0ae38..7cf94df952b 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -63,7 +63,7 @@ impl Deref for Value { /// Fields specific to /// [InputElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) [VTag](crate::virtual_dom::VTag)s -#[derive(Debug, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Clone, ImplicitClone, Default, Eq, PartialEq)] pub(crate) struct InputFields { /// Contains a value of an /// [InputElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). @@ -76,8 +76,6 @@ pub(crate) struct InputFields { pub(crate) checked: Option, } -impl ImplicitClone for InputFields {} - impl Deref for InputFields { type Target = Value; @@ -115,7 +113,7 @@ pub(crate) struct TextareaFields { /// [VTag] fields that are specific to different [VTag] kinds. /// Decreases the memory footprint of [VTag] by avoiding impossible field and value combinations. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ImplicitClone)] pub(crate) enum VTagInner { /// Fields specific to /// [InputElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) @@ -134,12 +132,10 @@ pub(crate) enum VTagInner { }, } -impl ImplicitClone for VTagInner {} - /// A type for a virtual /// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) /// representation. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ImplicitClone)] pub struct VTag { /// [VTag] fields that are specific to different [VTag] kinds. pub(crate) inner: VTagInner, @@ -152,8 +148,6 @@ pub struct VTag { pub key: Option, } -impl ImplicitClone for VTag {} - impl VTag { /// Creates a new [VTag] instance with `tag` name (cannot be changed later in DOM). pub fn new(tag: impl Into) -> Self { diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index bbd778b4648..e7f83e5403a 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -8,14 +8,12 @@ use crate::html::ImplicitClone; /// A type for a virtual /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) /// representation. -#[derive(Clone)] +#[derive(Clone, ImplicitClone)] pub struct VText { /// Contains a text of the node. pub text: AttrValue, } -impl ImplicitClone for VText {} - impl VText { /// Creates new virtual text node with a content. pub fn new(text: impl Into) -> Self { diff --git a/website/docs/advanced-topics/immutable.mdx b/website/docs/advanced-topics/immutable.mdx index d2cb0d97122..4fcb17e2c4b 100644 --- a/website/docs/advanced-topics/immutable.mdx +++ b/website/docs/advanced-topics/immutable.mdx @@ -18,6 +18,16 @@ 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. +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + ## Further reading - [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/docs/concepts/contexts.mdx b/website/docs/concepts/contexts.mdx index 72745408ad1..282bc519bb3 100644 --- a/website/docs/concepts/contexts.mdx +++ b/website/docs/concepts/contexts.mdx @@ -162,7 +162,7 @@ See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use We have 2 options to consume contexts in struct components: -- [Higher Order Components](../advanced-topics/struct-components/hoc.mdx): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. - Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## Use cases diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index bb4c010601c..625bbf7c440 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -341,9 +341,9 @@ These include, but are not limited to: **Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should _generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed) so you may have to manually force a render. Like all things, it has its place. Use it with caution. -3. Using `Vec` type instead of `IArray`.
- **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either - a reference-counted slice (`Rc`) or a `&'static [T]`, thus very cheap to clone.
+3. Using `Vec` type instead of `IArray`.
+ **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either + a reference-counted slice (`Rc<[T]>`) or a `&'static [T]`, thus very cheap to clone.
**Note**: `IArray` can be imported from [implicit-clone](https://crates.io/crates/implicit-clone) See that crate to learn more. 4. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue diff --git a/website/docs/concepts/html/events.mdx b/website/docs/concepts/html/events.mdx index 882ec9aec9c..9c31cdfd476 100644 --- a/website/docs/concepts/html/events.mdx +++ b/website/docs/concepts/html/events.mdx @@ -40,7 +40,7 @@ listens for `click` events. See the end of this page for a [full list of availab Events dispatched by Yew follow the virtual DOM hierarchy when bubbling up to listeners. Currently, only the bubbling phase is supported for listeners. Note that the virtual DOM hierarchy is most often, but not always, identical to the actual -DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals.mdx) and other +DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals) and other more advanced techniques. The intuition for well-implemented components should be that events bubble from children to parents. In this way the hierarchy in your coded `html!` is the one observed by event handlers. diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx index 091f848446e..5ea0688dae2 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ React と同様に、プロパティは祖先から子孫に伝播されます イミュータブルタイプは、コンポーネント間でプロパティの値を低コストでクローンできるため、プロパティの値を保持するのに最適です。 +## 一般的なイミュータブルタイプ + +Yew は `implicit-clone` クレートから以下のイミュータブルタイプの使用を推奨しています: + +- `IString`(Yew では `AttrValue` としてエイリアス化)- `String` の代わりに文字列用 +- `IArray` - `Vec` の代わりに配列・ベクター用 +- `IMap` - `HashMap` の代わりにマップ用 + +これらのタイプは参照カウント(`Rc`)または静的参照のいずれかであり、非常に安価にクローンできます。 + ## さらに読む - [イミュータブルの例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/contexts.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/contexts.mdx index f57bdb00dd6..503df32c2ea 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/contexts.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 構造体コンポーネント内でコンテキストを使用するには、2つの方法があります: -- [高階コンポーネント](../advanced-topics/struct-components/hoc.mdx):高階関数コンポーネントがコンテキストを使用し、必要なデータを構造体コンポーネントに渡します。 +- [高階コンポーネント](../advanced-topics/struct-components/hoc):高階関数コンポーネントがコンテキストを使用し、必要なデータを構造体コンポーネントに渡します。 - 構造体コンポーネント内で直接コンテキストを使用します。詳細については、[構造体コンポーネントのコンシューマーとしての例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) を参照してください。 ## 使用シナリオ diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx index 9c5dbe8254f..c1c600ee455 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` は内部的には [implicit-clone](https://crates.io/crates/implicit-clone) からの `IString` です。詳細はそのパッケージを参照してください。 2. 内部可変性を使用する。
**なぜ悪いのか?** 内部可変性(例えば `RefCell`、`Mutex` など)は _通常_ 避けるべきです。これにより再レンダリングの問題が発生する可能性があり(Yewは状態が変更されたことを認識しません)、手動で再レンダリングを強制する必要があるかもしれません。すべてのものと同様に、適切な使用場所があります。慎重に使用してください。 -3. `Vec` 型を `IArray` の代わりに使用する。
- **なぜ悪いのか?** `Vec` も `String` と同様にクローンのコストが高いです。`IArray` は参照カウントされたスライス (`Rc`) または `&'static [T]` であり、非常に安価にクローンできます。
+3. `Vec` 型を `IArray` の代わりに使用する。
+ **なぜ悪いのか?** `Vec` も `String` と同様にクローンのコストが高いです。`IArray` は参照カウントされたスライス (`Rc<[T]>`) または `&'static [T]` であり、非常に安価にクローンできます。
**注意**:`IArray` は [implicit-clone](https://crates.io/crates/implicit-clone) からインポートできます。詳細はそのパッケージを参照してください。 4. 新しい発見があるかもしれません。早く知っておきたかったエッジケースに遭遇しましたか?問題を作成するか、このドキュメントに修正のPRを提供してください。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/events.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/events.mdx index 139649bcbc0..2e85be40f9b 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/events.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## イベントキャプチャ {#event-bubbling} -Yew がディスパッチするイベントは仮想 DOM 階層に従い、リスナーに向かってバブルアップします。現在、リスナーのバブルフェーズのみがサポートされています。仮想 DOM 階層は通常(ただし常にではありません)実際の DOM 階層と同じです。[ポータル](../../advanced-topics/portals.mdx)やその他の高度な技術を扱う際には、この違いが重要です。よく設計されたコンポーネントでは、直感的にイベントは子コンポーネントから親コンポーネントにバブルアップするはずです。これにより、`html!` で記述した階層がイベントハンドラによって観察される階層となります。 +Yew がディスパッチするイベントは仮想 DOM 階層に従い、リスナーに向かってバブルアップします。現在、リスナーのバブルフェーズのみがサポートされています。仮想 DOM 階層は通常(ただし常にではありません)実際の DOM 階層と同じです。[ポータル](../../advanced-topics/portals)やその他の高度な技術を扱う際には、この違いが重要です。よく設計されたコンポーネントでは、直感的にイベントは子コンポーネントから親コンポーネントにバブルアップするはずです。これにより、`html!` で記述した階層がイベントハンドラによって観察される階層となります。 イベントのバブルアップを避けたい場合は、アプリケーションを起動する前に以下のコードを呼び出すことができます diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx new file mode 100644 index 00000000000..f54f9c02eb4 --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx @@ -0,0 +1,318 @@ +--- +title: 'Children' +--- + +:::caution + +Inspecting and manipulating `Children` can often result in surprising and hard-to-explain behaviours in your application. +This can lead to edge cases and often does not yield expected result. +You should consider other approaches if you are trying to manipulate `Children`. + +Yew supports using `Html` as the type of the children prop. +You should use `Html` as children if you do not need `Children` or `ChildrenRenderer`. +It doesn't have the drawbacks of `Children` and has a lower performance overhead. + +::: + +## General usage + +_Most of the time,_ when allowing a component to have children, you don't care +what type of children the component has. In such cases, the below example will +suffice. + +```rust +use yew::{html, Component, Context, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: Html, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ {ctx.props().children.clone()} +
+ } + } +} +``` + +## Advanced usage + +### Typed children + +In cases where you want one type of component to be passed as children to your component, +you can use `yew::html::ChildrenWithProps`. + +```rust +use yew::{html, ChildrenWithProps, Component, Context, Html, Properties}; + +pub struct Item; + +impl Component for Item { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "item" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenWithProps, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +## Nested Children with Props + +Nested component properties can be accessed and mutated if the containing component types its children. + +```rust +use std::rc::Rc; +use yew::prelude::*; + +#[derive(Clone, PartialEq, Properties)] +pub struct ListItemProps { + value: String, +} + +#[function_component] +fn ListItem(props: &ListItemProps) -> Html { + let ListItemProps { value } = props.clone(); + html! { + + {value} + + } +} + +#[derive(PartialEq, Properties)] +pub struct Props { + pub children: ChildrenWithProps, +} + +#[function_component] +fn List(props: &Props) -> Html { + let modified_children = props.children.iter().map(|mut item| { + let mut props = Rc::make_mut(&mut item.props); + props.value = format!("item-{}", props.value); + item + }); + html! { for modified_children } +} + +html! { + + + + + +}; +``` + +### Enum typed children + +Of course, sometimes you might need to restrict the children to a few different +components. In these cases, you have to get a little more hands-on with Yew. + +The [`derive_more`](https://github.com/JelteF/derive_more) crate is used here +for better ergonomics. If you don't want to use it, you can manually implement +`From` for each variant. + +```rust +use yew::{ + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, +}; + +pub struct Primary; + +impl Component for Primary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Primary" } + } + } +} + +pub struct Secondary; + +impl Component for Secondary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Secondary" } + } + } +} + +#[derive(Clone, derive_more::From, PartialEq)] +pub enum Item { + Primary(VChild), + Secondary(VChild), +} + +// Now, we implement `Into` so that yew knows how to render `Item`. +#[allow(clippy::from_over_into)] +impl Into for Item { + fn into(self) -> Html { + match self { + Self::Primary(child) => child.into(), + Self::Secondary(child) => child.into(), + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenRenderer, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +### Optional typed child + +You can also have a single optional child component of a specific type too: + +```rust +use yew::{ + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties +}; + +pub struct PageSideBar; + +impl Component for PageSideBar { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "sidebar" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct PageProps { + #[prop_or_default] + pub sidebar: Option>, +} + +struct Page; + +impl Component for Page { + type Message = (); + type Properties = PageProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } + // ... page content +
+ } + } +} + +// The page component can be called either with the sidebar or without: + +pub fn render_page(with_sidebar: bool) -> Html { + if with_sidebar { + // Page with sidebar + html! { + + }} /> + } + } else { + // Page without sidebar + html! { + + } + } +} +``` + +## Further Reading + +- For a real-world example of this pattern, check out the yew-router source code. For a more advanced example, check out the [nested-list example](https://github.com/yewstack/yew/tree/master/examples/nested_list) in the main yew repository. diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx index c0a15e94326..cd0e62ed003 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx @@ -1,8 +1,74 @@ --- -title: How it works -description: Low level details about the framework +title: 'How it works' +description: 'Low level details about the framework' --- -# 低レベルなライブラリの中身 +# Low-level library internals -コンポーネントのライフサイクルの状態機械、VDOM の異なるアルゴリズム +## Under the hood of the `html!` macro + +The `html!` macro turns code written in a custom HTML-like syntax into valid Rust code. Using this +macro is not necessary for developing Yew applications, but it is recommended. The code generated +by this macro makes use of the public Yew library API which can be used directly if you wish. Note +that some methods used are undocumented intentionally to avoid accidental misuse. With each +update of `yew-macro`, the generated code will be more efficient and handle any breaking changes +without many (if any) modifications to the `html!` syntax. + +Because the `html!` macro allows you to write code in a declarative style, your UI layout code will +closely match the HTML that is generated for the page. This becomes increasingly useful as your +application gets more interactive and your codebase gets larger. Rather than manually writing +all of the code to manipulate the DOM yourself, the macro will handle it for you. + +Using the `html!` macro can feel pretty magical, but it has nothing to hide. If you are curious about +how it works, try expanding the `html!` macro calls in your program. There is a useful command called +`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` does not ship with +`cargo` by default so you will need to install it with `cargo install cargo-expand` if you have not +already. [Rust-Analyzer](https://rust-analyzer.github.io/) also provides a mechanism for +[obtaining macro output from within an IDE](https://rust-analyzer.github.io/manual.html#expand-macro-recursively). + +Output from the `html!` macro is often pretty terse! This is a feature: machine-generated code can +sometimes clash with other code in an application. To prevent issues, `proc_macro` +"hygiene" is adhered to. Some examples include: + +1. Instead of using `yew::` the macro generates `::yew::` to make sure that the + Yew package is referenced correctly. This is also why `::alloc::vec::Vec::new()` is called instead + of just `Vec::new()`. +2. Due to potential trait method name collisions, `` is used to make sure that we are + using members from the correct trait. + +## What is a virtual DOM? + +The DOM ("document object model") is a representation of the HTML content that is managed by the browser +for your web page. A "virtual" DOM is simply a copy of the DOM that is held in application memory. Managing +a virtual DOM results in a higher memory overhead, but allows for batching and faster reads by avoiding +or delaying the use of browser APIs. + +Having a copy of the DOM in memory can be helpful for libraries that promote the use of +declarative UIs. Rather than needing specific code for describing how the DOM should be modified +in response to a user event, the library can use a generalized approach with DOM "diffing". When a Yew +component is updated and wants to change how it is rendered, the Yew library will build a second copy +of the virtual DOM and directly compare it to a virtual DOM which mirrors what is currently on screen. +The "diff" (or difference) between the two can be broken down into incremental updates and applied in +a batch with browser APIs. Once the updates are applied, the old virtual DOM copy is discarded and the +new copy is saved for future diff checks. + +This "diff" algorithm can be optimized over time to improve the performance of complex applications. +Since Yew applications are run with WebAssembly, we believe that Yew has a competitive edge to adopt +more sophisticated algorithms in the future. + +The Yew virtual DOM is not exactly one-to-one with the browser DOM. It also includes "lists" and +"components" for organizing DOM elements. A list can simply be an ordered list of elements but can +also be much more powerful. By annotating each list element with a "key", application developers +can help Yew make additional optimizations to ensure that when a list changes, the least amount +of work is done to calculate the diff update. Similarly, components provide custom logic to +indicate whether a re-render is required to help with performance. + +## Yew scheduler and component-scoped event loop + +_Contribute to the docs – explain how `yew::scheduler` and `yew::html::scope` work in depth_ + +## Further reading + +- [More information about macros from the Rust Book](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) +- [More information about `cargo-expand`](https://github.com/dtolnay/cargo-expand) +- [The API documentation for `yew::virtual_dom`](https://docs.rs/yew/*/yew/virtual_dom/index.html) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx new file mode 100644 index 00000000000..4fcb17e2c4b --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx @@ -0,0 +1,34 @@ +--- +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. 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. + +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + +## Further reading + +- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) +- [Crate `implicit-clone`](https://docs.rs/implicit-clone/) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx index 193de37e85e..b567556d400 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx @@ -1,117 +1,84 @@ --- -title: Optimizations -description: Make your app faster +title: '最適化とベストプラクティス' +sidebar_label: Optimizations +description: 'Make your app faster' --- -# 最適化とベストプラクティス - -## neq_assign - -親コンポーネントから props を受け取った際、`change`メソッドが呼ばれます。 -これはコンポーネントの状態を更新することができるのに加え、コンポーネントが props が変わった際に再レンダリングするかどうかを決める -`ShouldRender`という真偽値を返すことができます。 - -再レンダリングはコストがかかるもので、もし避けられるのであれば避けるべきです。 -一般的なルールとして props が実際に変化した際にのみ再レンダリングすれば良いでしょう。 -以下のコードブロックはこのルールを表しており、props が前と変わったときに`true`を返します。 - -```rust -use yew::ShouldRender; - -#[derive(PartialEq)] -struct ExampleProps; - -struct Example { - props: ExampleProps, -}; - -impl Example { - fn change(&mut self, props: ExampleProps) -> ShouldRender { - if self.props != props { - self.props = props; - true - } else { - false - } - } -} -``` - -しかし我々は先に進んでいけます! -この 6 行のボイラープレードは`PartialEq`を実装したものにトレイトとブランケットを用いることで 1 行のコードへと落とし込むことができます。 -[こちら](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html)にて`yewtil`クレートの`NewAssign`トレイトを見てみてください。 - ## 効果的にスマートポインタを使う **注意: このセクションで使われている用語がわからなければ Rust book は [スマートポインタについての章](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) があり、非常に有用です。** -再レンダリングの際に props を作るデータを大量にコピーしないために、スマートポインタを用いてデータ自体ではなくデータへの参照だけを -コピーできます。 -props や子コンポーネントで関連するデータに実データではなく参照を渡すと、子コンポーネントでデータを変更する必要がなければ -データのコピーを避けることができます。 -その際、`Rc::make_mut`によって変更したいデータの変更可能な参照を得ることができます。 - -これにより、props が変更されたときにコンポーネントが再レンダリングされるかどうかを決めるかで`Component::change`に更なる恩恵があります。 -なぜなら、データの値を比較する代わりに元々のポインタのアドレス (つまりデータが保管されている機械のメモリの場所) を比較できるためです。 -2 つのポインターが同じデータを指す場合、それらのデータの値は同じでなければならないのです。 -ただし、その逆は必ずしも成り立たないことに注意してください! -もし 2 つのポインタが異なるのであれば、そのデータは同じである可能性があります。 -この場合はデータを比較するべきでしょう。 - -この比較においては`PartialEq`ではなく`Rc::ptr_eq`を使う必要があります。 -`PartialEq`は等価演算子`==`を使う際に自動的に使われます。 -Rust のドキュメントには[`Rc::ptr_eq`についてより詳しく書いてあります](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq)。 - -この最適化は`Copy`を実装していないデータの型に対して極めて有効なものです。 -もしデータを簡単に変更できるのであれば、スマートポインタに取り換える必要はありません。 -しかし`Vec`や`HashMap`、`String`などのような重たいデータの構造体に対してはスマートポインタを使うことで -パフォーマンスを改善することができるでしょう。 - -この最適化は値がまだ一度も子によって更新されていない場合に極めて有効で、親からほとんど更新されない場合においてもかなり有効です。 -これにより、`Rc<_>s`が純粋なコンポーネントに対してプロパティの値をラップする良い一手となります。 - -## View 関数 - -コードの可読性の理由から`html!`の部分を関数へと移植するのは意味があります。 -これは、インデントを減らすのでコードを読みやすくするだけでなく、良いデザインパターンを産むことにも繋がるのです。 -これらの関数は複数箇所で呼ばれて書くべきコード量を減らせるため、分解可能なアプリケーションを作ることができるのです。 - -## 純粋なコンポーネント - -純粋なコンポーネントは状態を変化せず、ただ中身を表示してメッセージを普通の変更可能なコンポーネントへ渡すコンポーネントのことです。 -View 関数との違いとして、純粋なコンポーネントは式の構文\(`{some_view_function()}`\)ではなく -コンポーネントの構文\(``\)を使うことで`html!`マクロの中で呼ばれる点、 -そして実装次第で記憶され (つまり、一度関数が呼ばれれば値は"保存"され、 -同じ引数でもう一度呼ばれても値を再計算する必要がなく最初に関数が呼ばれたときの保存された値を返すことができる)、 -先述の`neq_assign`ロジックを使う別々の props で再レンダリングを避けられる点があります。 - -Yew は純粋な関数やコンポーネントをサポートしていませんが、外部のクレートを用いることで実現できます。 - -## 関数型コンポーネント (a.k.a フック) - -関数型コンポーネントはまだ開発中です! -開発状況については[プロジェクトボード](https://github.com/yewstack/yew/projects/3)に詳しく書いてあります。 - -## キー付き DOM ノード - -## ワークスペースでコンパイル時間を減らす - -間違いなく Yew を使う上での最大の欠点はコンパイルに時間がかかる点です。 -プロジェクトのコンパイルにかかる時間は`html!`マクロに渡されるコードの量に関係しています。 -これは小さなプロジェクトにはそこまで問題ないようですが、大きなアプリではコードを複数クレートに分割することでアプリに変更が加られた際に -コンパイラの作業量を減らすのが有効です。 - -一つ可能なやり方として、ルーティングとページ洗濯を担当するメインのクレートを作り、それぞれのページに対して別のクレートを作ることです。 -そうして各ページは異なるコンポーネントか、`Html`を生成する大きな関数となります。 -アプリの異なる部分を含むクレート同士で共有されるコードはプロジェクト全体で依存する分離したクレートに保存されます。 -理想的には 1 回のコンパイルでコード全てを再ビルドせずメインのクレートかどれかのページのクレートを再ビルドするだけにすることです。 -最悪なのは、"共通"のクレートを編集して、はじめに戻ってくることです: -共有のクレートに依存している全てのコード、恐らく全てのコードをコンパイルすることです。 - -もしメインのクレートが重たすぎる、もしくは深くネストしたページ (例えば別ページのトップでレンダリングされるページ) -で速く繰り返したい場合、クレートの例を用いてメインページの実装をシンプルにしたりトップで動かしているコンポーネントをレンダリングできます。 +再レンダリング時にpropsを作成するために大量のデータをクローンすることを避けるために、 +データそのものではなく、データへの参照のみをクローンするスマートポインタを使用できます。 +実際のデータではなく、propsと子コンポーネントで関連するデータへの参照を渡すことで、 +子コンポーネントでデータを変更する必要があるまでデータのクローンを避けることができます。 +その場合、`Rc::make_mut`を使用してクローンし、変更したいデータへの可変参照を取得できます。 + +これにより、propの変更がコンポーネントの再レンダリングを必要とするかどうかを判断する際に、 +`Component::changed`でさらなる利点がもたらされます。これは、データの値を比較する代わりに、 +基礎となるポインタアドレス(つまり、データが格納されているマシンのメモリ上の位置)を +比較できるためです。2つのポインタが同じデータを指している場合、それらが指すデータの値は +同じでなければなりません。ただし、逆は必ずしも真ではないことに注意してください! +2つのポインタアドレスが異なっていても、基礎となるデータは同じかもしれません。 +この場合、基礎となるデータを比較する必要があります。 + +この比較を行うには、`PartialEq`(等値演算子`==`を使用してデータを比較する際に自動的に使用される)を +使用するのではなく、`Rc::ptr_eq`を使用する必要があります。Rustのドキュメントには +[`Rc::ptr_eq`についての詳細](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq)があります。 + +この最適化は、`Copy`を実装していないデータ型に最も有用です。データを安価にコピーできる場合、 +スマートポインタの背後に置く価値はありません。`Vec`、`HashMap`、`String`のようにデータが重くなる可能性のある +構造体の場合、スマートポインタを使用するとパフォーマンスの改善が見込まれます。 + +この最適化は、値が子によって決して更新されない場合に最も効果的であり、 +親によってもめったに更新されない場合はさらに効果的です。これにより、`Rc<_>`は +ピュアコンポーネントのプロパティ値をラップするのに適した選択となります。 + +ただし、子コンポーネントでデータを自分でクローンする必要がない限り、 +この最適化は無用であるだけでなく、参照カウントの不必要なコストも追加することに注意する必要があります。 +Yewのpropsはすでに参照カウントされており、内部的にデータのクローンは発生しません。 + +## ビュー関数 + +コードの可読性のために、`html!`のセクションを独自の関数に移行することはしばしば意味があります。 +これにより、インデントの量が減るためコードがより読みやすくなるだけでなく、 +良い設計パターンも促進されます。特に、これらの関数は複数の場所から呼び出すことができるため、 +記述する必要のあるコードの量が減り、構成可能なアプリケーションの構築に役立ちます。 + +## ピュアコンポーネント + +ピュアコンポーネントは、状態を変更せず、コンテンツを表示し、 +通常の可変コンポーネントにメッセージを伝播するだけのコンポーネントです。 +ビュー関数とは異なり、式構文(\(`{some_view_function()}`\))ではなく +コンポーネント構文(\(``\))を使用して`html!`マクロ内から使用でき、 +実装によってはメモ化できます(これは、関数が一度呼び出されるとその値が「保存」され、 +同じ引数で複数回呼び出された場合、値を再計算する必要がなく、 +最初の関数呼び出しから保存された値を返すことができることを意味します)。 +これにより、同一のpropsに対する再レンダリングを防ぎます。 +Yewは内部的にpropsを比較し、propsが変更された場合のみUIが再レンダリングされます。 + +## ワークスペースを使用してコンパイル時間を短縮する + +間違いなく、Yewを使用する最大の欠点は、Yewアプリのコンパイルに長い時間がかかることです。 +プロジェクトのコンパイルにかかる時間は、`html!`マクロに渡されるコードの量に関連しているようです。 +これは小規模なプロジェクトではそれほど問題にならない傾向がありますが、大規模なアプリケーションでは、 +アプリケーションに加えられた各変更に対してコンパイラが行う必要がある作業量を最小限に抑えるために、 +コードを複数のクレートに分割することが理にかなっています。 + +可能なアプローチの1つは、メインクレートにルーティング/ページ選択を処理させ、 +各ページに異なるクレートを作成することです。各ページは異なるコンポーネントか、 +`Html`を生成する大きな関数になる可能性があります。アプリケーションの異なる部分を含む +クレート間で共有されるコードは、プロジェクトが依存する別のクレートに格納できます。 +最良のケースでは、各コンパイルですべてのコードを再ビルドすることから、 +メインクレートと1つのページクレートのみを再ビルドすることになります。 +最悪の場合、「共通」クレートで何かを編集すると、その共通に共有されるクレートに依存する +すべてのコード(おそらく他のすべて)をコンパイルする元の状態に戻ってしまいます。 + +メインクレートが重すぎる場合、または深くネストされたページ(\(例:別のページの上にレンダリングされるページ\))で +迅速に反復したい場合は、サンプルクレートを使用してメインページの簡略化された実装を作成し、 +作業中のコンポーネントを追加でレンダリングできます。 ## バイナリサイズを小さくする @@ -125,7 +92,7 @@ Yew は純粋な関数やコンポーネントをサポートしていません `Cargo.toml`で`[profile.release]`のセクションに設定を書き込むことでリリースビルドを小さくすることが可能です。 -```text +```toml, title=Cargo.toml [profile.release] # バイナリに含むコードを少なくする panic = 'abort' @@ -139,6 +106,30 @@ opt-level = 'z' lto = true ``` +### Nightly Cargo設定 + +rustとcargoの実験的なナイトリー機能から追加の利点を得ることもできます。 +`trunk`でナイトリーツールチェーンを使用するには、`RUSTUP_TOOLCHAIN="nightly"`環境変数を設定します。 +その後、`.cargo/config.toml`で不安定なrustc機能を設定できます。 +設定を理解するには、[unstable features]のドキュメント、特に[`build-std`]と +[`build-std-features`]に関するセクションを参照してください。 + +```toml, title=".cargo/config.toml" +[unstable] +# rust-srcコンポーネントが必要です。`rustup +nightly component add rust-src` +build-std = ["std", "panic_abort"] +build-std-features = ["panic_immediate_abort"] +``` + +[unstable features]: https://doc.rust-lang.org/cargo/reference/unstable.html +[`build-std`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std +[`build-std-features`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std-features + +:::caution +ナイトリーrustコンパイラには、[このような](https://github.com/yewstack/yew/issues/2696)バグが含まれている可能性があり、 +時折注意と調整が必要です。これらの実験的オプションは慎重に使用してください。 +::: + ### wasm-opt 更に`wasm`のコードのサイズを最適化することができます。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx new file mode 100644 index 00000000000..c3b734dbb8b --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx @@ -0,0 +1,64 @@ +--- +title: 'Portals' +description: 'Rendering into out-of-tree DOM nodes' +--- + +## What is a portal? + +Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. +`yew::create_portal(child, host)` returns an `Html` value that renders `child` not hierarchically under its parent component, +but as a child of the `host` element. + +## Usage + +Typical uses of portals can include modal dialogs and hovercards, as well as more technical applications +such as controlling the contents of an element's +[`shadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot), appending +stylesheets to the surrounding document's `` and collecting referenced elements inside a +central `` element of an ``. + +Note that `yew::create_portal` is a low-level building block. Libraries should use it to implement +higher-level APIs which can then be consumed by applications. For example, here is a +simple modal dialogue that renders its `children` into an element outside `yew`'s control, +identified by the `id="modal_host"`. + +```rust +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct ModalProps { + #[prop_or_default] + pub children: Html, +} + +#[function_component] +fn Modal(props: &ModalProps) -> Html { + let modal_host = gloo::utils::document() + .get_element_by_id("modal_host") + .expect("Expected to find a #modal_host element"); + + create_portal( + props.children.clone(), + modal_host.into(), + ) +} +``` + +## Event handling + +Events emitted on elements inside portals follow the virtual DOM when bubbling up. That is, +if a portal is rendered as the child of an element, then an event listener on that element +will catch events dispatched from inside the portal, even if the portal renders its contents +in an unrelated location in the actual DOM. + +This allows developers to be oblivious of whether a component they consume, is implemented with +or without portals. Events fired on its children will bubble up regardless. + +A known issue is that events from portals into **closed** shadow roots will be dispatched twice, +once targeting the element inside the shadow root and once targeting the host element itself. Keep +in mind that **open** shadow roots work fine. If this impacts you, feel free to open a bug report +about it. + +## Further reading + +- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md new file mode 100644 index 00000000000..6d3789ff33c --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md @@ -0,0 +1,203 @@ +--- +title: 'Server-side Rendering' +description: 'Render Yew on the server-side.' +--- + +# Server-side Rendering + +By default, Yew components render on the client side. When a viewer +visits a website, the server sends a skeleton HTML file without any actual +content and a WebAssembly bundle to the browser. +Everything is rendered on the client side by the WebAssembly +bundle. This is known as client-side rendering. + +This approach works fine for most websites, with some caveats: + +1. Users will not be able to see anything until the entire WebAssembly + bundle is downloaded and the initial render has been completed. + This can result in a poor experience for users on a slow network. +2. Some search engines do not support dynamically rendered web content and + those who do usually rank dynamic websites lower in the search results. + +To solve these problems, we can render our website on the server side. + +## How it Works + +Yew provides a `ServerRenderer` to render pages on the +server side. + +To render Yew components on the server side, you can create a renderer +with `ServerRenderer::::new()` and call `renderer.render().await` +to render `` into a `String`. + +```rust +use yew::prelude::*; +use yew::ServerRenderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +// we use `flavor = "current_thread"` so this snippet can be tested in CI, +// where tests are run in a WASM environment. You likely want to use +// the (default) `multi_thread` favor as: +// #[tokio::main] +#[tokio::main(flavor = "current_thread")] +async fn no_main() { + let renderer = ServerRenderer::::new(); + + let rendered = renderer.render().await; + + // Prints:
Hello, World!
+ println!("{}", rendered); +} +``` + +## Component Lifecycle + +The recommended way of working with server-side rendering is +function components. + +All hooks other than `use_effect` (and `use_effect_with`) +will function normally until a component successfully renders into `Html` +for the first time. + +:::caution Web APIs are not available! + +Web APIs such as `web_sys` are not available when your component is +rendering on the server side. +Your application will panic if you try to use them. +You should isolate logics that need Web APIs in `use_effect` or +`use_effect_with` as effects are not executed during server-side rendering. + +::: + +:::danger Struct Components + +While it is possible to use Struct Components with server-side rendering, +there are no clear boundaries between client-side safe logic like the +`use_effect` hook for function components and lifecycle events are invoked +in a different order than the client side. + +In addition, Struct Components will continue to accept messages until all of its +children are rendered and `destroy` method is called. Developers need to +make sure no messages possibly passed to components would link to logic +that makes use of Web APIs. + +When designing an application with server-side rendering support, +prefer function components unless you have a good reason not to. + +::: + +## Data Fetching during Server-side Rendering + +Data fetching is one of the difficult points with server-side rendering and hydration. + +Traditionally, when a component renders, it is instantly available +(outputs a virtual DOM to be rendered). This works fine when the +component does not want to fetch any data. But what happens if the component +wants to fetch some data during rendering? + +In the past, there was no mechanism for Yew to detect whether a component is still +fetching data. The data-fetching client is responsible to implement +a solution to detect what is being requested during the initial render and triggers +a second render after requests are fulfilled. The server repeats this process until +no more pending requests are added during a render before returning a response. + +This not only wastes CPU resources by repeatedly rendering components, +but the data client also needs to provide a way to make the data fetched on the +server side available during the hydration process to make sure that the +virtual DOM returned by the initial render is consistent with the +server-side rendered DOM tree which can be hard to implement. + +Yew takes a different approach by trying to solve this issue with ``. + +Suspense is a special component that when used on the client side, provides a +way to show a fallback UI while the component is fetching +data (suspended) and resumes to normal UI when the data fetching completes. + +When the application is rendered on the server side, Yew waits until a +component is no longer suspended before serializing it into the string +buffer. + +During the hydration process, elements within a `` component +remains dehydrated until all of its child components are no longer +suspended. + +With this approach, developers can build a client-agnostic, SSR-ready +application with data fetching with very little effort. + +## SSR Hydration + +Hydration is the process that connects a Yew application to the +server-side generated HTML file. By default, `ServerRender` prints +hydratable HTML string which includes additional information to facilitate hydration. +When the `Renderer::hydrate` method is called, instead of starting rendering from +scratch, Yew will reconcile the Virtual DOM generated by the application +with the HTML string generated by the server renderer. + +:::caution + +To successfully hydrate an HTML representation created by the +`ServerRenderer`, the client must produce a Virtual DOM layout that +exactly matches the one used for SSR including components that do not +contain any elements. If you have any component that is only useful in +one implementation, you may want to use a `PhantomComponent` to fill the +position of the extra component. +::: + +:::warning + +The hydration can only succeed if the real DOM matches the expected DOM +after initial render of the SSR output (static HTML) by browser. If your HTML is +not spec-compliant, the hydration _may_ fail. Browsers may change the DOM structure +of the incorrect HTML, causing the actual DOM to be different from the expected DOM. +For example, [if you have a `` without a ``, the browser may add a `` to the DOM](https://github.com/yewstack/yew/issues/2684) +::: + +## Component Lifecycle during hydration + +During Hydration, components schedule 2 consecutive renders after it is +created. Any effects are called after the second render completes. +It is important to make sure that the render function of your +component is free of side effects. It should not mutate any states or trigger +additional renders. If your component currently mutates states or triggers +additional renders, move them into a `use_effect` hook. + +It is possible to use Struct Components with server-side rendering in +hydration, the view function will be called +multiple times before the rendered function will be called. +The DOM is considered as not connected until the rendered function is called, +you should prevent any access to rendered nodes +until `rendered()` method is called. + +## Example + +```rust ,ignore +use yew::prelude::*; +use yew::Renderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +fn main() { + let renderer = Renderer::::new(); + + // hydrates everything under body element, removes trailing + // elements (if any). + renderer.hydrate(); +} +``` + +Example: [simple_ssr](https://github.com/yewstack/yew/tree/master/examples/simple_ssr) +Example: [ssr_router](https://github.com/yewstack/yew/tree/master/examples/ssr_router) + +:::caution + +Server-side rendering is currently experimental. If you find a bug, please file +an issue on [GitHub](https://github.com/yewstack/yew/issues/new?assignees=&labels=bug&template=bug_report.md&title=). + +::: diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx index 7799426a3c6..7d2a70fc03d 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx @@ -1,35 +1,86 @@ --- -title: Callbacks -description: ComponentLink and Callbacks +title: 'Callbacks' --- -”リンク”コンポーネントはコンポーネントがコールバックを登録できて自身を更新することができるメカニズムです。 +## Callbacks -## ComponentLink API +Callbacks are used to communicate with services, agents, and parent components within Yew. +Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned. -### callback +They have an `emit` function that takes their `` type as an argument and converts that to a message expected by its destination. If a callback from a parent is provided in props to a child component, the child can call `emit` on the callback in its `update` lifecycle hook to send a message back to its parent. Closures or Functions provided as props inside the `html!` macro are automatically converted to Callbacks. -実行時にコンポーネントの更新メカニズムにメッセージを送信するコールバックを登録します。 -これは、渡されたクロージャから返されるメッセージで `send_self` を呼び出します。 -`Fn(IN) -> Vec`が渡され、`Callback`が返されます。 +A simple use of a callback might look something like this: -### send_message +```rust +use yew::{html, Component, Context, Html}; -現在のループが終了した直後にコンポーネントにメッセージを送信し、別の更新ループを開始します。 +enum Msg { + Clicked, +} -### send_message_batch +struct Comp; -実行時に一度に多数のメッセージを一括して送信するコールバックを登録します。 -メッセージによってコンポーネントが再レンダリングされる場合、バッチ内のすべてのメッセージが処理された後、コンポーネントは再レンダリングされます。 -`Fn(IN) -> COMP::Message`が渡され、`Callback`が返されます。 +impl Component for Comp { -## コールバック + type Message = Msg; + type Properties = (); -_\(This might need its own short page.\)_ + fn create(_ctx: &Context) -> Self { + Self + } -コールバックは、Yew 内のサービス、エージェント、親コンポーネントとの通信に使われます。 -これらは単に `Fn` を `Rc` でラップしただけであり、クローンを作成できるようにするためのものです。 + fn view(&self, ctx: &Context) -> Html { + // highlight-next-line + let onclick = ctx.link().callback(|_| Msg::Clicked); + html! { + // highlight-next-line + + } + } +} +``` -これらの関数には `emit` 関数があり、`` 型を引数に取り、それをアドレスが欲しいメッセージに変換します。 -親からのコールバックが子コンポーネントに props で提供されている場合、子は `update` ライフサイクルフックで `emit` をコールバックに呼び出して親にメッセージを返すことができます。 -マクロ内で props として提供されたクロージャや関数は自動的にコールバックに変換されます。 +The function passed to `callback` must always take a parameter. For example, the `onclick` handler requires a function that takes a parameter of type `MouseEvent`. The handler can then decide what kind of message should be sent to the component. This message is scheduled for the next update loop unconditionally. + +If you need a callback that might not need to cause an update, use `batch_callback`. + +```rust +use yew::{events::KeyboardEvent, html, Component, Context, Html}; + +enum Msg { + Submit, +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // highlight-start + let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| { + if event.key() == "Enter" { + Some(Msg::Submit) + } else { + None + } + }); + + html! { + + } + // highlight-end + } +} +``` + +## Relevant examples + +- [Counter](https://github.com/yewstack/yew/tree/master/examples/counter) +- [Timer](https://github.com/yewstack/yew/tree/master/examples/timer) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx new file mode 100644 index 00000000000..eea2038c116 --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx @@ -0,0 +1,82 @@ +--- +title: 'Higher Order Components' +--- + +There are several cases where Struct components do not directly support a feature (ex. Suspense) or require a lot of boilerplate code to use the features (ex. Context). + +In those cases, it is recommended to create function components that are higher-order components. + +## Higher Order Components Definition + +Higher Order Components are components that do not add any new HTML and only wrap some other components to provide extra functionality. + +### Example + +Hook into Context and pass it down to a struct component + +```rust +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + context={(*ctx).clone()}> + + > + } +} + +// highlight-start +#[function_component] +pub fn ThemedButtonHOC() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! {} +} +// highlight-end + +#[derive(Properties, PartialEq)] +pub struct Props { + pub theme: Theme, +} + +struct ThemedButtonStructComponent; + +impl Component for ThemedButtonStructComponent { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let theme = &ctx.props().theme; + html! { + + } + } +} + + + + +``` diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx new file mode 100644 index 00000000000..311fe6ae5a1 --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: 'Introduction' +description: 'Components in Yew' +--- + +## What are Components? + +Components are the building blocks of Yew. They manage an internal state and can render elements to the DOM. +Components are created by implementing the `Component` trait for a type. + +## Writing Component's markup + +Yew uses Virtual DOM to render elements to the DOM. The Virtual DOM tree can be constructed by using the +`html!` macro. `html!` uses a syntax which is similar to HTML but is not the same. The rules are also +much stricter. It also provides superpowers like conditional rendering and rendering of lists using iterators. + +:::info +[Learn more about the `html!` macro, how it is used and its syntax](concepts/html/introduction.mdx) +::: + +## Passing data to a component + +Yew components use _props_ to communicate between parents and children. A parent component may pass any data as props to +its children. Props are similar to HTML attributes but any Rust type can be passed as props. + +:::info +[Learn more about the props](advanced-topics/struct-components/properties.mdx) +::: + +:::info +For other than parent/child communication, use [contexts](../../concepts/contexts.mdx) +::: diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx index 274c3d815d9..0f3221d9908 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx @@ -1,45 +1,46 @@ --- -title: Introduction -description: Components and their lifecycle hooks +title: 'ライフサイクル' +description: 'コンポーネントとそのライフサイクルフック' --- -## コンポーネントとは? - -コンポーネントは Yew を構成するブロックです。 -コンポーネントは状態を管理し、自身を DOM へレンダリングすることができます。 -コンポーネントはライフサイクルの機能がある`Component`トレイトを実装することによって作られます。 +`Component` トレイトには実装が必要なメソッドがいくつかあります。Yew はコンポーネントのライフサイクルの異なる段階でこれらを呼び出します。 ## ライフサイクル :::important contribute -`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/docs/issues/22) +`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/yew/issues/1915) ::: ## ライフサイクルのメソッド ### Create -コンポーネントが作られると、`ComponentLink`と同様に親コンポーネントからプロパティを受け取ります。 +コンポーネントが作られると、親コンポーネントからプロパティを受け取り、`create` メソッドに渡される `Context` 内に格納されます。 プロパティはコンポーネントの状態を初期化するのに使われ、"link"はコールバックを登録したりコンポーネントにメッセージを送るのに使われます。 -props と link をコンポーネント構造体に格納するのが一般的です。 -例えば: - ```rust -pub struct MyComponent { - props: Props, - link: ComponentLink, -} +use yew::{Component, Context, html, Html, Properties}; + +#[derive(PartialEq, Properties)] +pub struct Props; + +pub struct MyComponent; impl Component for MyComponent { + type Message = (); type Properties = Props; - // ... - fn create(props: Self::Properties, link: ComponentLink) -> Self { - MyComponent { props, link } + // highlight-start + fn create(ctx: &Context) -> Self { + MyComponent } + // highlight-end - // ... + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } } ``` @@ -48,17 +49,38 @@ impl Component for MyComponent { コンポーネントは`view()`メソッドによってレイアウトを宣言します。 Yew は`html!`マクロによって HTML と SVG ノード、リスナー、子コンポーネントを宣言できます。 マクロは React の JSX のような動きをしますが、JavaScript の代わりに Rust の式を用います。 +Yew は Svelte のように、`onclick={onclick}` の代わりに `{onclick}` と書くことができる省略構文を提供しています。 ```rust +use yew::{Component, Context, html, Html, Properties}; + +enum Msg { + Click, +} + +#[derive(PartialEq, Properties)] +struct Props { + button_text: String, +} + +struct MyComponent; + impl Component for MyComponent { - // ... + type Message = Msg; + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } - fn view(&self) -> Html { - let onclick = self.link.callback(|_| Msg::Click); + // highlight-start + fn view(&self, ctx: &Context) -> Html { + let onclick = ctx.link().callback(|_| Msg::Click); html! { - + } } + // highlight-end } ``` @@ -66,36 +88,46 @@ impl Component for MyComponent { ### Rendered -`rendered()`コンポーネントのライフサイクルのメソッドは`view()`が処理されたて Yew がコンポーネントをレンダリングした後、 +`rendered()`コンポーネントのライフサイクルのメソッドは`view()`が処理されて Yew がコンポーネントをレンダリングした後、 ブラウザがページを更新する前に呼ばれます。 コンポーネントは、コンポーネントが要素をレンダリングした後にのみ実行できるアクションを実行するため、このメソッドを実装したい場合があります。 コンポーネントが初めてレンダリングされたかどうかは `first_render` パラメータで確認できます。 ```rust -use stdweb::web::html_element::InputElement; -use stdweb::web::IHtmlElement; -use yew::prelude::*; +use web_sys::HtmlInputElement; +use yew::{ + Component, Context, html, Html, NodeRef, +}; pub struct MyComponent { node_ref: NodeRef, } impl Component for MyComponent { - // ... + type Message = (); + type Properties = (); - fn view(&self) -> Html { + fn create(_ctx: &Context) -> Self { + Self { + node_ref: NodeRef::default(), + } + } + + fn view(&self, ctx: &Context) -> Html { html! { } } - fn rendered(&mut self, first_render: bool) { + // highlight-start + fn rendered(&mut self, _ctx: &Context, first_render: bool) { if first_render { - if let Some(input) = self.node_ref.try_into::() { + if let Some(input) = self.node_ref.cast::() { input.focus(); } } } + // highlight-end } ``` @@ -113,63 +145,123 @@ impl Component for MyComponent { `update()`がどのようなのかについての例は以下の通りです: ```rust +use yew::{Component, Context, html, Html}; + +// highlight-start pub enum Msg { SetInputEnabled(bool) } +// highlight-end + +struct MyComponent { + input_enabled: bool, +} impl Component for MyComponent { + // highlight-next-line type Message = Msg; + type Properties = (); - // ... + fn create(_ctx: &Context) -> Self { + Self { + input_enabled: false, + } + } + + // highlight-start + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SetInputEnabled(enabled) => { + if self.input_enabled != enabled { + self.input_enabled = enabled; + true // Re-render + } else { + false + } + } + } + } + // highlight-end - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::SetInputEnabled(enabled) => { - if self.input_enabled != enabled { - self.input_enabled = enabled; - true // Re-render - } else { - false - } - } - } + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } } + } ``` -### Change +### Changed コンポーネントは親によって再レンダリングされることがあります。 このような場合、新しいプロパティを受け取り、再レンダリングを選択する可能性があります。 この設計では、プロパティを変更することで、親から子へのコンポーネントの通信が容易になります。 +props が変更されたときにコンポーネントを再レンダリングするデフォルト実装があります。 -典型的な実装例は以下の通りです: +### Destroy + +After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is +necessary if you need to undertake operations to clean up after earlier actions of a component +before it is destroyed. This method is optional and does nothing by default. + +### Infinite loops + +Infinite loops are possible with Yew's lifecycle methods but are only caused when trying to update +the same component after every render, when that update also requests the component to be rendered. + +A simple example can be seen below: ```rust -impl Component for MyComponent { - // ... +use yew::{Context, Component, Html}; - fn change(&mut self, props: Self::Properties) -> ShouldRender { - if self.props != props { - self.props = props; - true - } else { - false - } +struct Comp; + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + // We are going to always request to re-render on any msg + true + } + + fn view(&self, _ctx: &Context) -> Html { + // For this example it doesn't matter what is rendered + Html::default() + } + + fn rendered(&mut self, ctx: &Context, _first_render: bool) { + // Request that the component is updated with this new msg + ctx.link().send_message(()); } } ``` -### Destroy +Let's run through what happens here: + +1. Component is created using the `create` function. +2. The `view` method is called so Yew knows what to render to the browser DOM. +3. The `rendered` method is called, which schedules an update message using the `Context` link. +4. Yew finishes the post-render phase. +5. Yew checks for scheduled events and sees the update message queue is not empty so works through + the messages. +6. The `update` method is called which returns `true` to indicate something has changed and the + component needs to re-render. +7. Jump back to 2. -コンポーネントが DOM からアンマウントされた後、Yew は `destroy()` ライフサイクルメソッドを呼び出し、必要なクリーンアップ操作をサポートします。 -このメソッドはオプションで、デフォルトでは何もしません。 +You can still schedule updates in the `rendered` method and it is often useful to do so, but +consider how your component will terminate this loop when you do. ## Associated Types -`Component`トレイトは 2 つの関連型があります: `Message`と`Properties`です。 +The `Component` trait has two associated types: `Message` and `Properties`. -```rust +```rust ,ignore impl Component for MyComponent { type Message = Msg; type Properties = Props; @@ -178,21 +270,31 @@ impl Component for MyComponent { } ``` -`Message`はコンポーネントによって処理され、何らかの副作用を引き起こすことができるさまざまなメッセージを表します。 -例えば、API リクエストをトリガーしたり、UI コンポーネントの外観を切り替えたりする `Click` メッセージがあります。 -コンポーネントのモジュールで `Msg` という名前の列挙型を作成し、それをコンポーネントのメッセージ型として使用するのが一般的です。 -"message"を"msg"と省略するのも一般的です。 +The `Message` type is used to send messages to a component after an event has taken place; for +example, you might want to undertake some action when a user clicks a button or scrolls down the +page. Because components tend to have to respond to more than one event, the `Message` type will +normally be an enum, where each variant is an event to be handled. + +When organizing your codebase, it is sensible to include the definition of the `Message` type in the +same module in which your component is defined. You may find it helpful to adopt a consistent naming +convention for message types. One option (though not the only one) is to name the types +`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type +`HomepageMsg`. ```rust enum Msg { Click, + FormInput(String) } ``` -`Properties`は、親からコンポーネントに渡される情報を表します。 -この型は Properties trait を実装していなければならず\(通常はこれを派生させることで\)、特定のプロパティが必須かオプションかを指定することができます。 -この型は、コンポーネントの作成・更新時に使用されます。 -コンポーネントのモジュール内に `Props` という構造体を作成し、それをコンポーネントの `Properties` 型として使用するのが一般的です。 -”Properties”を"props"に短縮するのが一般的です。 -Props は親コンポーネントから継承されるので、アプリケーションのルートコンポーネントは通常`()`型の`Properties`を持ちます。 -ルートコンポーネントのプロパティを指定したい場合は、`App::mount_with_props`メソッドを利用します。 +`Properties` represents the information passed to a component from its parent. This type must implement the `Properties` trait \(usually by deriving it\) and can specify whether certain properties are required or optional. This type is used when creating and updating a component. It is common practice to create a struct called `Props` in your component's module and use that as the component's `Properties` type. It is common to shorten "properties" to "props". Since props are handed down from parent components, the root component of your application typically has a `Properties` type of `()`. If you wish to specify properties for your root component, use the `App::mount_with_props` method. + +:::info +[Learn more about properties](./properties) +::: + +## Lifecycle Context + +All component lifecycle methods take a context object. This object provides a reference to the component's scope, which +allows sending messages to a component and the props passed to the component. diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx index 560f9c1e6e7..d2cc2128561 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx @@ -1,29 +1,42 @@ --- -title: Properties -description: Parent to child communication +title: 'Properties' +description: 'Parent to child communication' --- プロパティは、子コンポーネントと親コンポーネントが互いに通信できるようにします。 +各コンポーネントには、親から渡されるものを記述する関連付けられたプロパティタイプがあります。 +理論的には、これは `Properties` trait を実装する任意のタイプにすることができますが、実際には +各フィールドがプロパティを表す構造体以外にする理由はありません。 ## マクロの継承 `Properties`を自分で実装しようとせず、代わりに`#[derive(Properties)]`を使ってください。 +`Properties`を継承した型は`PartialEq`も実装していなければいけません。 -:::note -`Properties`を継承した型は`Clone`を実装していなければいけません。 -これは`#[derive(Properties, Clone)`か`Clone`を手で実装することで可能です。 +### フィールド属性 + +デフォルトでは、`Properties` を導出する構造体内のフィールドは必須です。 +以下の属性を使うと、props に初期値を与えることができ、他の値に設定されない限りこの値が使用されます。 + +:::tip +Attributes aren't visible in Rustdoc generated documentation. +The doc strings of your properties should mention whether a prop is optional and if it has a special default value. ::: -### 必要な属性 +#### `#[prop_or_default]` -デフォルトでは、`Properties` を導出する構造体内のフィールドは必須です。 -フィールドが欠落していて `html!` マクロでコンポーネントが作成された場合、コンパイラエラーが返されます。 -オプションのプロパティを持つフィールドについては、`#[prop_or_default]` 属性を使用して、prop が指定されていない場合はその型のデフォルト値を使用します。 -値を指定するには `#[prop_or(value)]` 属性を用います。 -ここで value はプロパティのデフォルト値、あるいは代わりに `#[prop_or_else(function)]` を使用して、`function` はデフォルト値を返します。 -例えば、ブール値のデフォルトを `true` とするには、属性 `#[prop_or(true)]` を使用します。オプションのプロパティでは、デフォルト値 `None` を持つ `Option` 列挙型を使うのが一般的です。 +Initialize the prop value with the default value of the field's type using the `Default` trait. + +#### `#[prop_or(value)]` + +Use `value` to initialize the prop value. `value` can be any expression that returns the field's type. +For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`. -### PartialEq +#### `#[prop_or_else(function)]` + +Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type. + +## `PartialEq` もし可能なら props で `PartialEq` を継承するのが良いかもしれません。 `PartialEq`を使うことで、不必要な再レンダリングを避けることができます @@ -31,22 +44,21 @@ description: Parent to child communication ## プロパティを使用する際のメモリと速度のオーバーヘッド -`Compoenent::view`ではコンポーネントの状態への参照を取り、それを使って `Html` を作成します。 +`Component::view`ではコンポーネントの状態への参照を取り、それを使って `Html` を作成します。 しかし、プロパティは自身の値です。 つまり、それらを作成して子コンポーネントに渡すためには、`view` 関数で提供される参照を所有する必要があるのです。 これは所有する値を取得するためにコンポーネントに渡される参照を暗黙のうちにクローンすることで行われます。 -これは、各コンポーネントが親から受け継いだ状態の独自のコピーを持っていることを意味し、コンポーネントを再レンダリングするときはいつでも、再レンダリングしたコンポーネントのすべての子コンポーネントの props がクローンされなければならないことを意味します。 - -このことの意味するところは、もしそうでなければ*大量の*データ \(10KB もあるような文字列\) を props として渡してしまうのであれば、子コンポーネントを親が呼び出す `Html` を返す関数にすることを考えた方がいいかもしれないということです。 - -props を介して渡されたデータを変更する必要がない場合は、実際のデータそのものではなく、データへの参照カウントされたポインタのみが複製されるように `Rc` でラップすることができます。 +:::tip +Make use of `AttrValue` which is our custom type for attribute values instead of defining them as String or another similar type. +::: -## 例 +## Example ```rust -use std::rc::Rc; use yew::Properties; +/// Importing the AttrValue from virtual_dom +use yew::virtual_dom::AttrValue; #[derive(Clone, PartialEq)] pub enum LinkColor { @@ -57,22 +69,59 @@ pub enum LinkColor { Purple, } -impl Default for LinkColor { - fn default() -> Self { - // The link color will be blue unless otherwise specified. - LinkColor::Blue - } +fn create_default_link_color() -> LinkColor { + LinkColor::Blue } -#[derive(Properties, Clone, PartialEq)] +#[derive(Properties, PartialEq)] pub struct LinkProps { /// The link must have a target. - href: String, - /// If the link text is huge, this will make copying the string much cheaper. - /// This isn't usually recommended unless performance is known to be a problem. - text: Rc, - /// Color of the link. + href: AttrValue, + /// Also notice that we are using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] + color: LinkColor, + /// The view function will not specify a size if this is None. #[prop_or_default] + size: Option, + /// When the view function does not specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} +``` + +## Props macro + +The `yew::props!` macro allows you to build properties the same way the `html!` macro does it. + +The macro uses the same syntax as a struct expression except that you cannot use attributes or a base expression (`Foo { ..base }`). +The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). + +```rust +use yew::{props, Properties, virtual_dom::AttrValue}; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +fn create_default_link_color() -> LinkColor { + LinkColor::Blue +} + +#[derive(Properties, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: AttrValue, + /// Also notice that we're using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] color: LinkColor, /// The view function will not specify a size if this is None. #[prop_or_default] @@ -81,4 +130,18 @@ pub struct LinkProps { #[prop_or(true)] active: bool, } + +impl LinkProps { + /// Notice that this function receives href and text as String + /// We can use `AttrValue::from` to convert it to a `AttrValue` + pub fn new_link_with_size(href: String, text: String, size: u32) -> Self { + // highlight-start + props! {LinkProps { + href: AttrValue::from(href), + text: AttrValue::from(text), + size, + }} + // highlight-end + } +} ``` diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx index bfaf178cc4b..0380d32adf1 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx @@ -7,18 +7,48 @@ description: Out-of-band DOM access これは、`view` ライフサイクルメソッドの外で DOM に変更を加えるために使用できます。 これは、キャンバスの要素を取得したり、ページの異なるセクションにスクロールしたりするのに便利です。 +For example, using a `NodeRef` in a component's `rendered` method allows you to make draw calls to +a canvas element after it has been rendered from `view`. 構文は以下の通りです: ```rust -// In create -self.node_ref = NodeRef::default(); +use web_sys::Element; +use yew::{html, Component, Context, Html, NodeRef}; -// In view -html! { -
+struct Comp { + node_ref: NodeRef, } -// In update -let has_attributes = self.node_ref.try_into::().has_attributes(); +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + // highlight-next-line + node_ref: NodeRef::default(), + } + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + // highlight-next-line +
+ } + } + + fn rendered(&mut self, _ctx: &Context, _first_render: bool) { + // highlight-start + let has_attributes = self.node_ref + .cast::() + .unwrap() + .has_attributes(); + // highlight-end + } +} ``` + +## Relevant examples + +- [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx new file mode 100644 index 00000000000..db4f5974fa7 --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx @@ -0,0 +1,81 @@ +--- +title: 'Scope' +description: "Component's Scope" +--- + +## Component's `Scope<_>` API + +The component "`Scope`" is the mechanism through which components can create callbacks and update themselves +using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. + +### `send_message` + +Sends a message to the component. +Messages are handled by the `update` method which determines whether the component should re-render. + +### `send_message_batch` + +Sends multiple messages to the component at the same time. +This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, +the component will re-render after all messages in the batch have been processed. + +If the given vector is empty, this function does nothing. + +### `callback` + +Create a callback that will send a message to the component when it is executed. +Under the hood, it will call `send_message` with the message returned by the provided closure. + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Text(String), +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback that accepts some text and sends it + // to the component as the `Msg::Text` message variant. + // highlight-next-line + let cb = ctx.link().callback(|text: String| Msg::Text(text)); + + // The previous line is needlessly verbose to make it clearer. + // It can be simplified it to this: + // highlight-next-line + let cb = ctx.link().callback(Msg::Text); + + // Will send `Msg::Text("Hello World!")` to the component. + // highlight-next-line + cb.emit("Hello World!".to_owned()); + + html! { + // html here + } + } +} +``` + +### `batch_callback` + +Create a callback that will send a batch of messages to the component when it is executed. +The difference to `callback` is that the closure passed to this method doesn't have to return a message. +Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. + +`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. + +`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. +This can be used in cases where, depending on the situation, an update isn't required. + +This is achieved using the `SendAsMessage` trait which is only implemented for these types. +You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx new file mode 100644 index 00000000000..afaced2c086 --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx @@ -0,0 +1,196 @@ +--- +title: 'Contexts' +sidebar_label: Contexts +description: 'Using contexts to pass deeply nested data' +--- + +Usually, data is passed from a parent component to a child component via props. +But passing props can become verbose and annoying if you have to pass them through many components in the middle, +or if many components in your app need the same information. Context solves this problem by allowing a +parent component to make data available to _any_ component in the tree below it, no matter how deep, +without having to pass it down with props. + +## The problem with props: "Prop Drilling" + +Passing [props](./function-components/properties.mdx) is a great way to pass data directly from a parent to a child. +They become cumbersome to pass down through deeply nested component trees or when multiple components share the same data. +A common solution to data sharing is lifting the data to a common ancestor and making the children take it as props. +However, this can lead to cases where the prop has to go through multiple components to reach the component that needs it. +This situation is called "Prop Drilling". + +Consider the following example which passes down the theme using props: + +```rust +use yew::{html, Component, Context, Html, Properties, function_component}; + +#[derive(Clone, PartialEq)] +pub struct Theme { + foreground: String, + background: String, +} + +#[derive(PartialEq, Properties)] +pub struct NavbarProps { + theme: Theme, +} + +#[function_component] +fn Navbar(props: &NavbarProps) -> Html { + html! { +
+ + { "App title" } + + + { "Somewhere" } + +
+ } +} + +#[derive(PartialEq, Properties)] +pub struct ThemeProps { + theme: Theme, + children: Html, +} + +#[function_component] +fn Title(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +#[function_component] +fn NavButton(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +/// App root +#[function_component] +fn App() -> Html { + let theme = Theme { + foreground: "yellow".to_owned(), + background: "pink".to_owned(), + }; + + html! { + + } +} +``` + +We "drill" the theme prop through `Navbar` so that it can reach `Title` and `NavButton`. +It would be nice if `Title` and `NavButton`, the components that need access to the theme, can just access the theme +without having to pass it to them as a prop. Contexts solve this problem by allowing a parent to pass data, theme in this case, +to its children. + +## Using Contexts + +### Step 1: Providing the context + +A context provider is required to consume the context. `ContextProvider`, where `T` is the context struct used as the provider. +`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them. +The children are re-rendered when the context changes. A struct is used to define what data is to be passed. The `ContextProvider` can be used as: + +```rust +use yew::prelude::*; + + +/// App theme +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +/// Main component +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + // `ctx` is type `Rc>` while we need `Theme` + // so we deref it. + // It derefs to `&Theme`, hence the clone + context={(*ctx).clone()}> + // Every child here and their children will have access to this context. + + > + } +} + +/// The toolbar. +/// This component has access to the context +#[function_component] +pub fn Toolbar() -> Html { + html! { +
+ +
+ } +} + +/// Button placed in `Toolbar`. +/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access +/// to the context. +#[function_component] +pub fn ThemedButton() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! { + + } +} +``` + +### Step 2: Consuming context + +#### Function components + +`use_context` hook is used to consume contexts in function components. +See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use_context.html) to learn more. + +#### Struct components + +We have 2 options to consume contexts in struct components: + +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) + +## Use cases + +Generally, if some data is needed by distant components in different parts of the tree, context will likely help you. +Here are some examples of such cases: + +- **Theming**: You can put a context at the top of the app that holds your app theme and use it to adjust the visual appearance, as shown in the above example. +- **Current user account**: In many cases, components need to know the currently logged-in user. You can use a context to provide the current user object to the components. + +### Considerations to make before using contexts + +Contexts are very easy to use. That makes them very easy to misuse/overuse. +Just because you can use a context to share props to components multiple levels deep, does not mean that you should. + +For example, you may be able to extract a component and pass that component as a child to another component. For example, +you may have a `Layout` component that takes `articles` as a prop and passes it down to `ArticleList` component. +You should refactor the `Layout` component to take children as props and display ` `. + +## Mutating the context value of a child + +Because of Rust's ownership rules, a context cannot have a method that takes `&mut self` that can be called by children. +To mutate a context's value, we must combine it with a reducer. This is done by using the +[`use_reducer`](https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer.html) hook. + +The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) demonstrates mutable contexts +with the help of contexts + +## Further reading + +- The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx new file mode 100644 index 00000000000..028b7f5b7be --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx @@ -0,0 +1,76 @@ +--- +title: 'Function Components' +slug: /concepts/function-components +--- + +Let's revisit this previous statement: + +> Yew centrally operates on the idea of keeping everything that a reusable piece of +> UI may need in one place - rust files. + +We will refine this statement, by introducing the concept that will define the logic and +presentation behavior of an application: "components". + +## What are Components? + +Components are the building blocks of Yew. + +They: + +- Take arguments in form of [Props](./properties.mdx) +- Can have their own state +- Compute pieces of HTML visible to the user (DOM) + +## Two flavors of Yew Components + +You are currently reading about function components - the recommended way to write components +when starting with Yew and when writing simple presentation logic. + +There is a more advanced, but less accessible, way to write components - [Struct components](advanced-topics/struct-components/introduction.mdx). +They allow very detailed control, though you will not need that level of detail most of the time. + +## Creating function components + +To create a function component add the `#[function_component]` attribute to a function. +By convention, the function is named in PascalCase, like all components, to contrast its +use to normal html elements inside the `html!` macro. + +```rust +use yew::{function_component, html, Html}; + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// Then somewhere else you can use the component inside `html!` +#[function_component] +fn App() -> Html { + html! { } +} +``` + +## What happens to components + +When rendering, Yew will build a virtual tree of these components. +It will call the view function of each (function) component to compute a virtual version (VDOM) of the DOM +that you as the library user see as the `Html` type. +For the previous example, this would look like this: + +```xhtml + + +

"Hello world"

+ +
+``` + +When an update is necessary, Yew will again call the view function and reconcile the new virtual DOM with its +previous version and only propagate the new/changed/necessary parts to the actual DOM. +This is what we call **rendering**. + +:::note + +Behind the scenes, `Html` is just an alias for `VNode` - a virtual node. + +::: diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx new file mode 100644 index 00000000000..d4b00a3c89d --- /dev/null +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx @@ -0,0 +1,291 @@ +--- +title: 'プロパティ (Properties)' +description: '親子コンポーネントの通信' +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +:::note + +プロパティ (Properties) は通常 "Props" と略されます。 + +::: + +プロパティ (Properties) はコンポーネントのパラメータであり、Yew はこれらのパラメータを監視できます。 + +コンポーネントのプロパティで型を使用する前に、その型は `Properties` トレイトを実装している必要があります。 + +## リアクティブ性 + +再レンダリング時に、Yew は仮想DOMを調整する際にプロパティが変更されたかどうかを確認し、ネストされたコンポーネントを再レンダリングする必要があるかどうかを判断します。これにより、Yew は非常にリアクティブなフレームワークと見なされます。親コンポーネントからの変更は常に下位に伝播し、ビューはプロパティ/状態からのデータと常に同期します。 + +:::tip + +まだ [チュートリアル](../../tutorial) を完了していない場合は、このリアクティブ性を自分でテストしてみてください! + +::: + +## 派生マクロ + +Yew は、構造体に `Properties` トレイトを簡単に実装できる派生マクロを提供します。 + +`Properties` を派生する型は、Yew がデータ比較を行えるように `PartialEq` も実装している必要があります。 + +```rust +use yew::Properties; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} +``` + +## 関数コンポーネントでの使用 + +属性 `#[function_component]` は、関数の引数で Props を選択的に受け取ることを可能にします。それらを提供するには、`html!` マクロ内の属性を通じて割り当てることができます。 + + + + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +} + +// そしてプロパティを提供します +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +```rust +use yew::{function_component, html, Html}; + + + + + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// 提供するプロパティはありません +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +## 派生マクロフィールド属性 + +`Properties` を派生する際、デフォルトではすべてのフィールドが必須です。 +以下の属性を使用すると、親コンポーネントがそれらを設定しなかった場合にデフォルト値を提供することができます。 + +:::tip +属性は Rustdoc によって生成されたドキュメントには表示されません。属性のドキュメント文字列には、その属性がオプションであるかどうか、および特定のデフォルト値があるかどうかを記載する必要があります。 +::: + + + + +`Default` トレイトを使用して、フィールド型のデフォルト値でプロパティ値を初期化します。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_default] + // highlight-end + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + if props.is_loading.clone() { + html! { "Loading" } + } else { + html! { "Hello world" } + } +} + +// デフォルト値を使用する +#[function_component] +fn Case1() -> Html { + html! {} +} +// またはデフォルト値を上書きしない +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +`value` を使用してプロパティ値を初期化します。`value` はフィールド型を返す任意の式である可能性があります。 +例えば、ブールプロパティをデフォルトで `true` にするには、属性 `#[prop_or(true)]` を使用します。プロパティが構築されるときに、式が評価され、明示的な値が与えられていない場合に適用されます。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or("Bob".to_string())] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// デフォルト値を使用する +#[function_component] +fn Case1() -> Html { + html! {} +} +// またはデフォルト値を上書きしない +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +属性値を初期化するために `function` を呼び出します。`function` は `FnMut() -> T` シグネチャを持つ必要があり、ここで `T` はフィールドの型です。このプロパティに明示的な値が与えられていない場合、その関数が呼び出されます。 + +```rust +use yew::{function_component, html, Html, Properties}; + +fn create_default_name() -> String { + "Bob".to_string() +} + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_else(create_default_name)] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// デフォルト値を使用する +#[function_component] +fn Case1() -> Html { + html! {} +} +// またはデフォルト値を上書きしない +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +## Properties のパフォーマンスオーバーヘッド + +内部プロパティは参照カウントされたスマートポインタとして渡されます。これにより、コンポーネントツリー内のプロパティに対して共有ポインタが1つだけ渡されるため、プロパティ全体をクローンする高コストを節約できます。 + +:::tip +`AttrValue` はプロパティ値に使用するカスタムタイプであり、これにより String やその他のクローンコストが高いタイプとして定義する必要がなくなります。 +::: + +## Props マクロ + +`yew::props!` マクロを使用すると、`html!` マクロと同じ方法でプロパティを構築できます。 + +このマクロは構造体の式と同じ構文を使用しますが、プロパティや基本式 (`Foo { ..base }`) を使用することはできません。タイプパスはプロパティ (`path::to::Props`) に直接指すことも、コンポーネントの関連プロパティ (`MyComp::Properties`) に指すこともできます。 + +```rust +use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue}; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or(AttrValue::from("Bob"))] + pub name: AttrValue, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +#[function_component] +fn App() -> Html { + // highlight-start + let pre_made_props = props! { + Props {} // 名前属性を指定する必要はありません + }; + // highlight-end + html! {} +} +``` + +## 評価順序 + +属性は指定された順序で評価されます。以下の例を参照してください: + +```rust +#[derive(yew::Properties, PartialEq)] +struct Props { first: usize, second: usize, last: usize } + +fn main() { + let mut g = 1..=3; + let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() }); + + assert_eq!(props.first, 1); + assert_eq!(props.second, 2); + assert_eq!(props.last, 3); +} +``` + +## アンチパターン + +ほとんどのRust型はプロパティとして渡すことができますが、避けるべきアンチパターンがいくつかあります。これらには以下が含まれますが、これに限定されません: + +1. `String` 型を `AttrValue` の代わりに使用する。
+ **なぜ悪いのか?** `String` のクローンは高コストです。プロパティ値がフックやコールバックと一緒に使用される場合、通常クローンが必要です。`AttrValue` は参照カウントされた文字列 (`Rc`) または `&'static str` であり、非常に安価にクローンできます。
+ **注意**:`AttrValue` は内部的には [implicit-clone](https://crates.io/crates/implicit-clone) からの `IString` です。詳細はそのパッケージを参照してください。 +2. 内部可変性を使用する。
+ **なぜ悪いのか?** 内部可変性(例えば `RefCell`、`Mutex` など)は _通常_ 避けるべきです。これにより再レンダリングの問題が発生する可能性があり(Yewは状態が変更されたことを認識しません)、手動で再レンダリングを強制する必要があるかもしれません。すべてのものと同様に、適切な使用場所があります。慎重に使用してください。 +3. `Vec` 型を `IArray` の代わりに使用する。
+ **なぜ悪いのか?** `Vec` も `String` と同様にクローンのコストが高いです。`IArray` は参照カウントされたスライス (`Rc<[T]>`) または `&'static [T]` であり、非常に安価にクローンできます。
+ **注意**:`IArray` は [implicit-clone](https://crates.io/crates/implicit-clone) からインポートできます。詳細はそのパッケージを参照してください。 +4. 新しい発見があるかもしれません。早く知っておきたかったエッジケースに遭遇しましたか?問題を作成するか、このドキュメントに修正のPRを提供してください。 + +## yew-autoprops + +[yew-autoprops](https://crates.io/crates/yew-autoprops) は実験的なパッケージで、関数の引数に基づいて動的にProps構造体を作成することを可能にします。プロパティ構造体が再利用されない場合、これは有用かもしれません。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx index 091f848446e..5ea0688dae2 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ React と同様に、プロパティは祖先から子孫に伝播されます イミュータブルタイプは、コンポーネント間でプロパティの値を低コストでクローンできるため、プロパティの値を保持するのに最適です。 +## 一般的なイミュータブルタイプ + +Yew は `implicit-clone` クレートから以下のイミュータブルタイプの使用を推奨しています: + +- `IString`(Yew では `AttrValue` としてエイリアス化)- `String` の代わりに文字列用 +- `IArray` - `Vec` の代わりに配列・ベクター用 +- `IMap` - `HashMap` の代わりにマップ用 + +これらのタイプは参照カウント(`Rc`)または静的参照のいずれかであり、非常に安価にクローンできます。 + ## さらに読む - [イミュータブルの例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx index f57bdb00dd6..503df32c2ea 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 構造体コンポーネント内でコンテキストを使用するには、2つの方法があります: -- [高階コンポーネント](../advanced-topics/struct-components/hoc.mdx):高階関数コンポーネントがコンテキストを使用し、必要なデータを構造体コンポーネントに渡します。 +- [高階コンポーネント](../advanced-topics/struct-components/hoc):高階関数コンポーネントがコンテキストを使用し、必要なデータを構造体コンポーネントに渡します。 - 構造体コンポーネント内で直接コンテキストを使用します。詳細については、[構造体コンポーネントのコンシューマーとしての例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) を参照してください。 ## 使用シナリオ diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx index 9c5dbe8254f..c1c600ee455 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` は内部的には [implicit-clone](https://crates.io/crates/implicit-clone) からの `IString` です。詳細はそのパッケージを参照してください。 2. 内部可変性を使用する。
**なぜ悪いのか?** 内部可変性(例えば `RefCell`、`Mutex` など)は _通常_ 避けるべきです。これにより再レンダリングの問題が発生する可能性があり(Yewは状態が変更されたことを認識しません)、手動で再レンダリングを強制する必要があるかもしれません。すべてのものと同様に、適切な使用場所があります。慎重に使用してください。 -3. `Vec` 型を `IArray` の代わりに使用する。
- **なぜ悪いのか?** `Vec` も `String` と同様にクローンのコストが高いです。`IArray` は参照カウントされたスライス (`Rc`) または `&'static [T]` であり、非常に安価にクローンできます。
+3. `Vec` 型を `IArray` の代わりに使用する。
+ **なぜ悪いのか?** `Vec` も `String` と同様にクローンのコストが高いです。`IArray` は参照カウントされたスライス (`Rc<[T]>`) または `&'static [T]` であり、非常に安価にクローンできます。
**注意**:`IArray` は [implicit-clone](https://crates.io/crates/implicit-clone) からインポートできます。詳細はそのパッケージを参照してください。 4. 新しい発見があるかもしれません。早く知っておきたかったエッジケースに遭遇しましたか?問題を作成するか、このドキュメントに修正のPRを提供してください。 diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx index 139649bcbc0..2e85be40f9b 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx +++ b/website/i18n/ja/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## イベントキャプチャ {#event-bubbling} -Yew がディスパッチするイベントは仮想 DOM 階層に従い、リスナーに向かってバブルアップします。現在、リスナーのバブルフェーズのみがサポートされています。仮想 DOM 階層は通常(ただし常にではありません)実際の DOM 階層と同じです。[ポータル](../../advanced-topics/portals.mdx)やその他の高度な技術を扱う際には、この違いが重要です。よく設計されたコンポーネントでは、直感的にイベントは子コンポーネントから親コンポーネントにバブルアップするはずです。これにより、`html!` で記述した階層がイベントハンドラによって観察される階層となります。 +Yew がディスパッチするイベントは仮想 DOM 階層に従い、リスナーに向かってバブルアップします。現在、リスナーのバブルフェーズのみがサポートされています。仮想 DOM 階層は通常(ただし常にではありません)実際の DOM 階層と同じです。[ポータル](../../advanced-topics/portals)やその他の高度な技術を扱う際には、この違いが重要です。よく設計されたコンポーネントでは、直感的にイベントは子コンポーネントから親コンポーネントにバブルアップするはずです。これにより、`html!` で記述した階層がイベントハンドラによって観察される階層となります。 イベントのバブルアップを避けたい場合は、アプリケーションを起動する前に以下のコードを呼び出すことができます diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx index 4b3da26e0a9..026ff0c77df 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ description: 'Yew 的不可变数据结构' 不可变类型非常适合保存属性的值,因为它们可以在从组件传递到组件时以很低的成本克隆。 +## 常见的不可变类型 + +Yew 推荐使用来自 `implicit-clone` crate 的以下不可变类型: + +- `IString`(在 Yew 中别名为 `AttrValue`)- 用于字符串而不是 `String` +- `IArray` - 用于数组/向量而不是 `Vec` +- `IMap` - 用于映射而不是 `HashMap` + +这些类型是引用计数(`Rc`)或静态引用,使它们的克隆成本非常低。 + ## 进一步阅读 - [不可变示例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/contexts.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/contexts.mdx index 9c07b61952c..f6f012f77e8 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/contexts.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 我们有两种选择在结构体组件中使用上下文: -- [高阶组件](../advanced-topics/struct-components/hoc.mdx):一个高阶函数组件将使用上下文并将数据传递给需要它的结构体组件。 +- [高阶组件](../advanced-topics/struct-components/hoc):一个高阶函数组件将使用上下文并将数据传递给需要它的结构体组件。 - 直接在结构体组件中使用上下文。请参阅 [结构体组件作为消费者的示例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## 使用场景 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx index 0dd3b7665c2..b1d7cdd786f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` 在内部是来自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看该包以了解更多信息。 2. 使用内部可变性。
**为什么不好?** 内部可变性(例如 `RefCell`、`Mutex` 等)应该 _通常_ 避免使用。它可能会导致重新渲染问题(Yew 不知道状态何时发生了变化),因此您可能需要手动强制重新渲染。就像所有事物一样,它有其用武之地。请谨慎使用。 -3. 使用 `Vec` 类型而不是 `IArray`。
- **为什么不好?** `Vec`,就像 `String` 一样,克隆成本也很高。`IArray` 是一个引用计数的切片 (`Rc`) 或一个 `&'static [T]`,因此非常便宜克隆。
+3. 使用 `Vec` 类型而不是 `IArray`。
+ **为什么不好?** `Vec`,就像 `String` 一样,克隆成本也很高。`IArray` 是一个引用计数的切片 (`Rc<[T]>`) 或一个 `&'static [T]`,因此非常便宜克隆。
**注意**:`IArray` 可以从 [implicit-clone](https://crates.io/crates/implicit-clone) 导入。查看该包以了解更多信息。 4. 您发觉可能的新内容。您是否遇到了一个希望早点了解清楚的边缘情况?请随时创建一个问题或向本文档提供修复的 PR。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/events.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/events.mdx index 8db17d4ea1b..00bcc99d3d9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/events.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## 事件捕获 {#event-bubbling} -Yew 调度的事件遵循虚拟 DOM 层次结构,向上冒泡到监听器。目前,仅支持监听器的冒泡阶段。请注意,虚拟 DOM 层次结构通常(但并非总是)与实际 DOM 层次结构相同。在处理[传送门](../../advanced-topics/portals.mdx)和其他更高级技术时,这一区别很重要。对于良好实现的组件,直觉应该是事件从子组件冒泡到父组件。这样,您在 `html!` 中编写的层次结构就是事件处理程序观察到的层次结构。 +Yew 调度的事件遵循虚拟 DOM 层次结构,向上冒泡到监听器。目前,仅支持监听器的冒泡阶段。请注意,虚拟 DOM 层次结构通常(但并非总是)与实际 DOM 层次结构相同。在处理[传送门](../../advanced-topics/portals)和其他更高级技术时,这一区别很重要。对于良好实现的组件,直觉应该是事件从子组件冒泡到父组件。这样,您在 `html!` 中编写的层次结构就是事件处理程序观察到的层次结构。 如果您不想要事件冒泡,可以通过调用 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx new file mode 100644 index 00000000000..f54f9c02eb4 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx @@ -0,0 +1,318 @@ +--- +title: 'Children' +--- + +:::caution + +Inspecting and manipulating `Children` can often result in surprising and hard-to-explain behaviours in your application. +This can lead to edge cases and often does not yield expected result. +You should consider other approaches if you are trying to manipulate `Children`. + +Yew supports using `Html` as the type of the children prop. +You should use `Html` as children if you do not need `Children` or `ChildrenRenderer`. +It doesn't have the drawbacks of `Children` and has a lower performance overhead. + +::: + +## General usage + +_Most of the time,_ when allowing a component to have children, you don't care +what type of children the component has. In such cases, the below example will +suffice. + +```rust +use yew::{html, Component, Context, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: Html, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ {ctx.props().children.clone()} +
+ } + } +} +``` + +## Advanced usage + +### Typed children + +In cases where you want one type of component to be passed as children to your component, +you can use `yew::html::ChildrenWithProps`. + +```rust +use yew::{html, ChildrenWithProps, Component, Context, Html, Properties}; + +pub struct Item; + +impl Component for Item { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "item" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenWithProps, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +## Nested Children with Props + +Nested component properties can be accessed and mutated if the containing component types its children. + +```rust +use std::rc::Rc; +use yew::prelude::*; + +#[derive(Clone, PartialEq, Properties)] +pub struct ListItemProps { + value: String, +} + +#[function_component] +fn ListItem(props: &ListItemProps) -> Html { + let ListItemProps { value } = props.clone(); + html! { + + {value} + + } +} + +#[derive(PartialEq, Properties)] +pub struct Props { + pub children: ChildrenWithProps, +} + +#[function_component] +fn List(props: &Props) -> Html { + let modified_children = props.children.iter().map(|mut item| { + let mut props = Rc::make_mut(&mut item.props); + props.value = format!("item-{}", props.value); + item + }); + html! { for modified_children } +} + +html! { + + + + + +}; +``` + +### Enum typed children + +Of course, sometimes you might need to restrict the children to a few different +components. In these cases, you have to get a little more hands-on with Yew. + +The [`derive_more`](https://github.com/JelteF/derive_more) crate is used here +for better ergonomics. If you don't want to use it, you can manually implement +`From` for each variant. + +```rust +use yew::{ + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, +}; + +pub struct Primary; + +impl Component for Primary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Primary" } + } + } +} + +pub struct Secondary; + +impl Component for Secondary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Secondary" } + } + } +} + +#[derive(Clone, derive_more::From, PartialEq)] +pub enum Item { + Primary(VChild), + Secondary(VChild), +} + +// Now, we implement `Into` so that yew knows how to render `Item`. +#[allow(clippy::from_over_into)] +impl Into for Item { + fn into(self) -> Html { + match self { + Self::Primary(child) => child.into(), + Self::Secondary(child) => child.into(), + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenRenderer, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +### Optional typed child + +You can also have a single optional child component of a specific type too: + +```rust +use yew::{ + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties +}; + +pub struct PageSideBar; + +impl Component for PageSideBar { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "sidebar" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct PageProps { + #[prop_or_default] + pub sidebar: Option>, +} + +struct Page; + +impl Component for Page { + type Message = (); + type Properties = PageProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } + // ... page content +
+ } + } +} + +// The page component can be called either with the sidebar or without: + +pub fn render_page(with_sidebar: bool) -> Html { + if with_sidebar { + // Page with sidebar + html! { + + }} /> + } + } else { + // Page without sidebar + html! { + + } + } +} +``` + +## Further Reading + +- For a real-world example of this pattern, check out the yew-router source code. For a more advanced example, check out the [nested-list example](https://github.com/yewstack/yew/tree/master/examples/nested_list) in the main yew repository. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx index c7645fe767c..0d05a9c7a19 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx @@ -1,7 +1,74 @@ --- -description: 有关框架的底层细节 +title: 'How it works' +description: '有关框架的底层细节' --- # 底层库的内部细节 -组件生命周期状态机,虚拟 dom diff 算法。 +## Under the hood of the `html!` macro + +The `html!` macro turns code written in a custom HTML-like syntax into valid Rust code. Using this +macro is not necessary for developing Yew applications, but it is recommended. The code generated +by this macro makes use of the public Yew library API which can be used directly if you wish. Note +that some methods used are undocumented intentionally to avoid accidental misuse. With each +update of `yew-macro`, the generated code will be more efficient and handle any breaking changes +without many (if any) modifications to the `html!` syntax. + +Because the `html!` macro allows you to write code in a declarative style, your UI layout code will +closely match the HTML that is generated for the page. This becomes increasingly useful as your +application gets more interactive and your codebase gets larger. Rather than manually writing +all of the code to manipulate the DOM yourself, the macro will handle it for you. + +Using the `html!` macro can feel pretty magical, but it has nothing to hide. If you are curious about +how it works, try expanding the `html!` macro calls in your program. There is a useful command called +`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` does not ship with +`cargo` by default so you will need to install it with `cargo install cargo-expand` if you have not +already. [Rust-Analyzer](https://rust-analyzer.github.io/) also provides a mechanism for +[obtaining macro output from within an IDE](https://rust-analyzer.github.io/manual.html#expand-macro-recursively). + +Output from the `html!` macro is often pretty terse! This is a feature: machine-generated code can +sometimes clash with other code in an application. To prevent issues, `proc_macro` +"hygiene" is adhered to. Some examples include: + +1. Instead of using `yew::` the macro generates `::yew::` to make sure that the + Yew package is referenced correctly. This is also why `::alloc::vec::Vec::new()` is called instead + of just `Vec::new()`. +2. Due to potential trait method name collisions, `` is used to make sure that we are + using members from the correct trait. + +## What is a virtual DOM? + +The DOM ("document object model") is a representation of the HTML content that is managed by the browser +for your web page. A "virtual" DOM is simply a copy of the DOM that is held in application memory. Managing +a virtual DOM results in a higher memory overhead, but allows for batching and faster reads by avoiding +or delaying the use of browser APIs. + +Having a copy of the DOM in memory can be helpful for libraries that promote the use of +declarative UIs. Rather than needing specific code for describing how the DOM should be modified +in response to a user event, the library can use a generalized approach with DOM "diffing". When a Yew +component is updated and wants to change how it is rendered, the Yew library will build a second copy +of the virtual DOM and directly compare it to a virtual DOM which mirrors what is currently on screen. +The "diff" (or difference) between the two can be broken down into incremental updates and applied in +a batch with browser APIs. Once the updates are applied, the old virtual DOM copy is discarded and the +new copy is saved for future diff checks. + +This "diff" algorithm can be optimized over time to improve the performance of complex applications. +Since Yew applications are run with WebAssembly, we believe that Yew has a competitive edge to adopt +more sophisticated algorithms in the future. + +The Yew virtual DOM is not exactly one-to-one with the browser DOM. It also includes "lists" and +"components" for organizing DOM elements. A list can simply be an ordered list of elements but can +also be much more powerful. By annotating each list element with a "key", application developers +can help Yew make additional optimizations to ensure that when a list changes, the least amount +of work is done to calculate the diff update. Similarly, components provide custom logic to +indicate whether a re-render is required to help with performance. + +## Yew scheduler and component-scoped event loop + +_Contribute to the docs – explain how `yew::scheduler` and `yew::html::scope` work in depth_ + +## Further reading + +- [More information about macros from the Rust Book](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) +- [More information about `cargo-expand`](https://github.com/dtolnay/cargo-expand) +- [The API documentation for `yew::virtual_dom`](https://docs.rs/yew/*/yew/virtual_dom/index.html) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx new file mode 100644 index 00000000000..4fcb17e2c4b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx @@ -0,0 +1,34 @@ +--- +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. 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. + +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + +## Further reading + +- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) +- [Crate `implicit-clone`](https://docs.rs/implicit-clone/) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx index 90c6bb16b9b..3dedd0b9dbf 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx @@ -1,89 +1,163 @@ --- +title: '性能优化与最佳实践' +sidebar_label: Optimizations description: 加速你的应用程序 --- -# 性能优化与最佳实践 - -## neq_assign - -当组件从它的父组件接收 props 时,`change` 方法将被调用。除了允许你更新组件的状态,还允许你返回一个布尔类型的值 `ShouldRender` 来指示组件是否应该响应 props 的更改而重新渲染自身。 - -重新渲染的开销很大,你应该尽量避免。一个通用的法则是,你只应该在 props 实际更改时重新渲染。以下代码块展示了此法则,如果 props 和先前的 props 不同,则返回 `true`: - -```rust -fn change(&mut self, props: Self::Properties) -> ShouldRender { - if self.props != &props { - *self.props = props; - true - } else { - false - } -} -``` - -但是我们可以更进一步!对于任何实现了 `PartialEq` 的项,可以使用一个 trait 和一个 blanket implementation 将这六行样板代码减少到一行。 - -```rust title="neq_assign.rs" -pub trait NeqAssign { - fn neq_assign(&mut self, new: Self) -> ShouldRender; -} -impl NeqAssign for T { - fn neq_assign(&mut self, new: T) -> ShouldRender { - if self != &new { - *self = new; - true - } else { - false - } - } -} - -// ... -fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.props.neq_assign(props) -} +## Using smart pointers effectively + +**Note: if you're unsure about some of the terms used in this section, the Rust Book has a useful +[chapter about smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html).** + +To avoid cloning large amounts of data to create props when re-rendering, we can use +smart pointers to only clone a reference to the data instead of the data itself. If you pass +references to the relevant data in your props and child components instead of the actual data you +can avoid cloning any data until you need to modify it in the child component, where you can +use `Rc::make_mut` to clone and obtain a mutable reference to the data you want to alter. + +This brings further benefits in `Component::changed` when working out whether prop changes require +the component to re-render. This is because instead of comparing the value of the data the +underlying pointer addresses (i.e. the position in a machine's memory where the data is stored) can +instead be compared; if two pointers point to the same data then the value of the data they point to +must be the same. Note that the inverse might not be true! Even if two pointer addresses differ the +underlying data might still be the same - in this case you should compare the underlying data. + +To do this comparison you'll need to use `Rc::ptr_eq` instead of just using `PartialEq` (which is +automatically used when comparing data using the equality operator `==`). The Rust documentation +has [more details about `Rc::ptr_eq`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq). + +This optimization is most useful for data types that don't implement `Copy`. If you can copy your +data cheaply, then it isn't worth putting it behind a smart pointer. For structures that +can be data-heavy like `Vec`s, `HashMap`s, and `String`s using smart pointers is likely to bring +performance improvements. + +This optimization works best if the values are never updated by the children, and even better if +they are rarely updated by parents. This makes `Rc<_>s` a good choice for wrapping property values +in pure components. + +However, it must be noted that unless you need to clone the data yourself in the child component, +this optimization is not only useless, but it also adds the unnecessary cost of reference counting. Props +in Yew are already reference counted and no data clones occur internally. + +## View functions + +For code readability reasons, it often makes sense to migrate sections of `html!` to their own +functions. Not only does this make your code more readable because it reduces the amount of +indentation present, it also encourages good design patterns – particularly around building +composable applications because these functions can be called in multiple places which reduces the +amount of code that has to be written. + +## Pure Components + +Pure components are components that don't mutate their state, only displaying content and +propagating messages up to normal, mutable components. They differ from view functions in that they +can be used from within the `html!` macro using the component syntax \(``\) +instead of expression syntax \(`{some_view_function()}`\), and that depending on their +implementation, they can be memoized (this means that once a function is called its value is "saved" +so that if it's called with the same arguments more than once it doesn't have to recompute its value +and can just return the saved value from the first function call) - preventing re-renders for +identical props. Yew compares the props internally and so the UI is only re-rendered if the props change. + +## Reducing compile time using workspaces + +Arguably, the largest drawback to using Yew is the long time it takes to compile Yew apps. The time +taken to compile a project seems to be related to the quantity of code passed to the `html!` macro. +This tends to not be much of an issue for smaller projects, but for larger applications, it makes +sense to split code across multiple crates to minimize the amount of work the compiler has to do for +each change made to the application. + +One possible approach is to make your main crate handle routing/page selection, and then make a +different crate for each page, where each page could be a different component or just a big +function that produces `Html`. Code that is shared between the crates containing different parts of +the application could be stored in a separate crate which the project depends on. +In the best-case scenario, you go from rebuilding all of your code on each compile to rebuilding +only the main crate, and one of your page crates. In the worst case, where you edit something in the +"common" crate, you will be right back to where you started: compiling all code that depends on that +commonly shared crate, which is probably everything else. + +If your main crate is too heavyweight, or you want to rapidly iterate on a deeply nested page \(eg. +a page that renders on top of another page\), you can use an example crate to create a simplified +implementation of the main page and additionally render the component you are working on. + +## Reducing binary sizes + +- optimize Rust code +- `cargo.toml` \( defining release profile \) +- optimize wasm code using `wasm-opt` + +**Note: more information about reducing binary sizes can be found in the +[Rust Wasm Book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).** + +### Cargo.toml + +It is possible to configure release builds to be smaller using the available settings in the +`[profile.release]` section of your `Cargo.toml`. + +```toml, title=Cargo.toml +[profile.release] +# less code to include into binary +panic = 'abort' +# optimization over all codebase ( better optimization, slower build ) +codegen-units = 1 +# optimization for size ( more aggressive ) +opt-level = 'z' +# optimization for size +# opt-level = 's' +# link time optimization using using whole-program analysis +lto = true ``` -该 trait 称为 `NeqAssign` 是因为如果目标值和新值不相等,它将赋为新值。 +### Nightly Cargo configuration -这比简单的实现还要短: +You can also gain additional benefits from experimental nightly features of rust and +cargo. To use the nightly toolchain with `trunk`, set the `RUSTUP_TOOLCHAIN="nightly"` environment +variable. Then, you can configure unstable rustc features in your `.cargo/config.toml`. +Refer to the doc of [unstable features], specifically the section about [`build-std`] and +[`build-std-features`], to understand the configuration. -```rust -// 不要这样做,除非你无法避免。 -fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.props = props; - true -} +```toml, title=".cargo/config.toml" +[unstable] +# Requires the rust-src component. `rustup +nightly component add rust-src` +build-std = ["std", "panic_abort"] +build-std-features = ["panic_immediate_abort"] ``` -你不仅限在 `change` 函数中使用它。通常,在 `update` 函数中执行此操作也是有意义的,尽管性能提升在那里不太明显。 +[unstable features]: https://doc.rust-lang.org/cargo/reference/unstable.html +[`build-std`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std +[`build-std-features`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std-features -## RC +:::caution +The nightly rust compiler can contain bugs, such as [this one](https://github.com/yewstack/yew/issues/2696), +that require occasional attention and tweaking. Use these experimental options with care. +::: -为了避免在重新渲染时为了创建 props 而克隆大块数据,我们可以使用智能指针来只克隆指针。如果在 props 和子组件中使用 `Rc<_>` 而不是普通未装箱的值,则可以延迟克隆直到需要修改子组件中的数据为止,在该组件中可以使用 `Rc::make_mut` 来对要更改数据进行克隆和获取可变引用。通过在要修改前不进行克隆,子组件可以在几乎没有性能成本的情况下拒绝与它们在 `Component::change` 中拥有状态的 props 相同的 props,这与数据本身需要先复制到父级 props 结构体中,然后在子级中进行比较和拒绝的情况相反。 +### wasm-opt -对于不是 `Copy` 类型的数据,这种优化是最有用的。如果你能轻松地拷贝数据,那么将其放入智能指针中可能是不值得的。对于可以包含大量数据的结构,例如 `Vec`,`HashMap` 和 `String`,这种优化应该是值得的。 +Further, it is possible to optimize the size of `wasm` code. -如果子组件从不更新组件的值,则这种优化效果最好,如果父组件很少更新组件的值,则效果更好。这使得 `Rc<_>s` 是包装纯组件属性值的不错选择。 +The Rust Wasm Book has a section about reducing the size of Wasm binaries: +[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html) -## 视图函数 +- using `wasm-pack` which by default optimizes `wasm` code in release builds +- using `wasm-opt` directly on `wasm` files. -出于代码可读性的原因,将 `html!` 各个部分的代码迁移到他们自己的函数中通常是有意义的,这样就可以避免在深层嵌套的 HTML 中出现代码块向右偏移。 - -## 纯组件 / 函数式组件 - -纯组件是不会修改它们状态的组件,它们仅展示内容和向普通可变组件传递消息。它们与视图函数不同之处在于他们可以使用组件语法(``)而不是表达式语法(`{some_view_function()}`)来在 `html!` 宏中使用,并且根据它们的实现,它们可以被记忆化 - 使用前面提到的 `neq_assign` 逻辑来防止因为相同的 props 而重新渲染。 - -Yew 没有原生支持纯组件或者函数式组件,但是可以通过外部库获取它们。 - -函数式组件尚不存在,但是从理论上来讲,可以通过使用 proc 宏和标注函数生成纯组件。 +```text +wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm +``` -## Keyed DOM nodes when they arrive +#### Build size of 'minimal' example in yew/examples/ -## 使用 Cargo Workspaces 进行编译速度优化 +Note: `wasm-pack` combines optimization for Rust and Wasm code. `wasm-bindgen` is used in this example without any Rust size optimization. -可以说,使用 Yew 的最大缺点是编译时间长。编译时间似乎与 `html!` 宏块中的代码量相关。对于较小的项目,这通常不是什么大问题,但是对于跨多个页面的 web 应用程序,将代码拆分为多个 crates 以最大程度地减少编译器要做的工作通常是有意义的。 +| used tool | size | +| :-------------------------- | :---- | +| wasm-bindgen | 158KB | +| wasm-bindgen + wasm-opt -Os | 116KB | +| wasm-pack | 99 KB | -你应该尝试让主 crate 处理路由和页面选择,将所有公用的代码移动到另一个 crate,然后为每一个页面创建一个不同的 crate,其中每个页面可能是一个不同的组件,或者只是一个产生 `Html` 的大函数。在最好的情况下,你将从重新构建所有代码到只重新构建主 crate 和一个页面的 crate。在最糟糕的情况下,当你在“公共” crate 中编辑内容时,你将回到起点:编译所有依赖此公用 crate 的代码,这可能就是除此之外的所有代码。 +## Further reading: -如果你的主 crate 过于庞大,或者你想在深层嵌套的页面(例如,在另一个页面顶部渲染的页面)中快速迭代,则可以使用一个示例 crate 创建一个更简单的主页面实现并在之上渲染你正在开发的组件。 +- [The Rust Book's chapter on smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) +- [Information from the Rust Wasm Book about reducing binary sizes](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size) +- [Documentation about Rust profiles](https://doc.rust-lang.org/cargo/reference/profiles.html) +- [binaryen project](https://github.com/WebAssembly/binaryen) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx new file mode 100644 index 00000000000..c3b734dbb8b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx @@ -0,0 +1,64 @@ +--- +title: 'Portals' +description: 'Rendering into out-of-tree DOM nodes' +--- + +## What is a portal? + +Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. +`yew::create_portal(child, host)` returns an `Html` value that renders `child` not hierarchically under its parent component, +but as a child of the `host` element. + +## Usage + +Typical uses of portals can include modal dialogs and hovercards, as well as more technical applications +such as controlling the contents of an element's +[`shadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot), appending +stylesheets to the surrounding document's `` and collecting referenced elements inside a +central `` element of an ``. + +Note that `yew::create_portal` is a low-level building block. Libraries should use it to implement +higher-level APIs which can then be consumed by applications. For example, here is a +simple modal dialogue that renders its `children` into an element outside `yew`'s control, +identified by the `id="modal_host"`. + +```rust +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct ModalProps { + #[prop_or_default] + pub children: Html, +} + +#[function_component] +fn Modal(props: &ModalProps) -> Html { + let modal_host = gloo::utils::document() + .get_element_by_id("modal_host") + .expect("Expected to find a #modal_host element"); + + create_portal( + props.children.clone(), + modal_host.into(), + ) +} +``` + +## Event handling + +Events emitted on elements inside portals follow the virtual DOM when bubbling up. That is, +if a portal is rendered as the child of an element, then an event listener on that element +will catch events dispatched from inside the portal, even if the portal renders its contents +in an unrelated location in the actual DOM. + +This allows developers to be oblivious of whether a component they consume, is implemented with +or without portals. Events fired on its children will bubble up regardless. + +A known issue is that events from portals into **closed** shadow roots will be dispatched twice, +once targeting the element inside the shadow root and once targeting the host element itself. Keep +in mind that **open** shadow roots work fine. If this impacts you, feel free to open a bug report +about it. + +## Further reading + +- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md new file mode 100644 index 00000000000..6d3789ff33c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md @@ -0,0 +1,203 @@ +--- +title: 'Server-side Rendering' +description: 'Render Yew on the server-side.' +--- + +# Server-side Rendering + +By default, Yew components render on the client side. When a viewer +visits a website, the server sends a skeleton HTML file without any actual +content and a WebAssembly bundle to the browser. +Everything is rendered on the client side by the WebAssembly +bundle. This is known as client-side rendering. + +This approach works fine for most websites, with some caveats: + +1. Users will not be able to see anything until the entire WebAssembly + bundle is downloaded and the initial render has been completed. + This can result in a poor experience for users on a slow network. +2. Some search engines do not support dynamically rendered web content and + those who do usually rank dynamic websites lower in the search results. + +To solve these problems, we can render our website on the server side. + +## How it Works + +Yew provides a `ServerRenderer` to render pages on the +server side. + +To render Yew components on the server side, you can create a renderer +with `ServerRenderer::::new()` and call `renderer.render().await` +to render `` into a `String`. + +```rust +use yew::prelude::*; +use yew::ServerRenderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +// we use `flavor = "current_thread"` so this snippet can be tested in CI, +// where tests are run in a WASM environment. You likely want to use +// the (default) `multi_thread` favor as: +// #[tokio::main] +#[tokio::main(flavor = "current_thread")] +async fn no_main() { + let renderer = ServerRenderer::::new(); + + let rendered = renderer.render().await; + + // Prints:
Hello, World!
+ println!("{}", rendered); +} +``` + +## Component Lifecycle + +The recommended way of working with server-side rendering is +function components. + +All hooks other than `use_effect` (and `use_effect_with`) +will function normally until a component successfully renders into `Html` +for the first time. + +:::caution Web APIs are not available! + +Web APIs such as `web_sys` are not available when your component is +rendering on the server side. +Your application will panic if you try to use them. +You should isolate logics that need Web APIs in `use_effect` or +`use_effect_with` as effects are not executed during server-side rendering. + +::: + +:::danger Struct Components + +While it is possible to use Struct Components with server-side rendering, +there are no clear boundaries between client-side safe logic like the +`use_effect` hook for function components and lifecycle events are invoked +in a different order than the client side. + +In addition, Struct Components will continue to accept messages until all of its +children are rendered and `destroy` method is called. Developers need to +make sure no messages possibly passed to components would link to logic +that makes use of Web APIs. + +When designing an application with server-side rendering support, +prefer function components unless you have a good reason not to. + +::: + +## Data Fetching during Server-side Rendering + +Data fetching is one of the difficult points with server-side rendering and hydration. + +Traditionally, when a component renders, it is instantly available +(outputs a virtual DOM to be rendered). This works fine when the +component does not want to fetch any data. But what happens if the component +wants to fetch some data during rendering? + +In the past, there was no mechanism for Yew to detect whether a component is still +fetching data. The data-fetching client is responsible to implement +a solution to detect what is being requested during the initial render and triggers +a second render after requests are fulfilled. The server repeats this process until +no more pending requests are added during a render before returning a response. + +This not only wastes CPU resources by repeatedly rendering components, +but the data client also needs to provide a way to make the data fetched on the +server side available during the hydration process to make sure that the +virtual DOM returned by the initial render is consistent with the +server-side rendered DOM tree which can be hard to implement. + +Yew takes a different approach by trying to solve this issue with ``. + +Suspense is a special component that when used on the client side, provides a +way to show a fallback UI while the component is fetching +data (suspended) and resumes to normal UI when the data fetching completes. + +When the application is rendered on the server side, Yew waits until a +component is no longer suspended before serializing it into the string +buffer. + +During the hydration process, elements within a `` component +remains dehydrated until all of its child components are no longer +suspended. + +With this approach, developers can build a client-agnostic, SSR-ready +application with data fetching with very little effort. + +## SSR Hydration + +Hydration is the process that connects a Yew application to the +server-side generated HTML file. By default, `ServerRender` prints +hydratable HTML string which includes additional information to facilitate hydration. +When the `Renderer::hydrate` method is called, instead of starting rendering from +scratch, Yew will reconcile the Virtual DOM generated by the application +with the HTML string generated by the server renderer. + +:::caution + +To successfully hydrate an HTML representation created by the +`ServerRenderer`, the client must produce a Virtual DOM layout that +exactly matches the one used for SSR including components that do not +contain any elements. If you have any component that is only useful in +one implementation, you may want to use a `PhantomComponent` to fill the +position of the extra component. +::: + +:::warning + +The hydration can only succeed if the real DOM matches the expected DOM +after initial render of the SSR output (static HTML) by browser. If your HTML is +not spec-compliant, the hydration _may_ fail. Browsers may change the DOM structure +of the incorrect HTML, causing the actual DOM to be different from the expected DOM. +For example, [if you have a `
` without a ``, the browser may add a `` to the DOM](https://github.com/yewstack/yew/issues/2684) +::: + +## Component Lifecycle during hydration + +During Hydration, components schedule 2 consecutive renders after it is +created. Any effects are called after the second render completes. +It is important to make sure that the render function of your +component is free of side effects. It should not mutate any states or trigger +additional renders. If your component currently mutates states or triggers +additional renders, move them into a `use_effect` hook. + +It is possible to use Struct Components with server-side rendering in +hydration, the view function will be called +multiple times before the rendered function will be called. +The DOM is considered as not connected until the rendered function is called, +you should prevent any access to rendered nodes +until `rendered()` method is called. + +## Example + +```rust ,ignore +use yew::prelude::*; +use yew::Renderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +fn main() { + let renderer = Renderer::::new(); + + // hydrates everything under body element, removes trailing + // elements (if any). + renderer.hydrate(); +} +``` + +Example: [simple_ssr](https://github.com/yewstack/yew/tree/master/examples/simple_ssr) +Example: [ssr_router](https://github.com/yewstack/yew/tree/master/examples/ssr_router) + +:::caution + +Server-side rendering is currently experimental. If you find a bug, please file +an issue on [GitHub](https://github.com/yewstack/yew/issues/new?assignees=&labels=bug&template=bug_report.md&title=). + +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx index 15b9f0961bb..dccc22a217f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx @@ -1,27 +1,86 @@ --- -description: ComponentLink 和 Callbacks. +title: '回调(Callbacks)' +description: 'ComponentLink 和 Callbacks' --- -# 回调(Callbacks) +## Callbacks -组件“link”是一种机制,通过该机制,组件可以注册回调并自行更新。 +Callbacks 用于与 Yew 中的 services,agents 和父组件进行通信。它们仅仅是个 `Fn`,并由 `Rc` 包裹以允许被克隆。 -## ComponentLink API +它们有一个 `emit` 函数,该函数将它的 `` 类型作为参数并将其转换为目标所期望的消息。如果一个回调从父组件中通过 props 提供给子组件,则子组件可以在其 `update` 生命周期钩子中对该回调调用 `emit`,以将消息发送回父组件。在 `html!` 宏内被提供作为 props 的闭包或函数会自动转换为 Callbacks。 -### callback +A simple use of a callback might look something like this: -注册一个回调,该回调将在执行时将消息发送到组件的更新机制。在内部,它将使用提供的闭包返回的消息调用 `send_self`。提供 `Fn(IN) -> Vec`,返回 `Callback`。 +```rust +use yew::{html, Component, Context, Html}; -### send_message +enum Msg { + Clicked, +} -当前循环结束后立即向组件发送消息,导致另一个更新循环启动。 +struct Comp; -### send_message_batch +impl Component for Comp { -注册一个回调,该回调在执行时立即发送一批消息。如果其中任何一个消息将导致组件重新渲染,那么组件会在该批次所有消息被处理后重新渲染。提供 `Fn(IN) -> COMP::Message`,返回 `Callback`。 + type Message = Msg; + type Properties = (); -## Callbacks + fn create(_ctx: &Context) -> Self { + Self + } -Callbacks 用于与 Yew 中的 services,agents 和父组件进行通信。它们仅仅是个 `Fn`,并由 `Rc` 包裹以允许被克隆。 + fn view(&self, ctx: &Context) -> Html { + // highlight-next-line + let onclick = ctx.link().callback(|_| Msg::Clicked); + html! { + // highlight-next-line + + } + } +} +``` -它们有一个 `emit` 函数,该函数将它的 `` 类型作为参数并将其转换为目标所期望的消息。如果一个回调从父组件中通过 props 提供给子组件,则子组件可以在其 `update` 生命周期钩子中对该回调调用 `emit`,以将消息发送回父组件。在 `html!` 宏内被提供作为 props 的闭包或函数会自动转换为 Callbacks。 +The function passed to `callback` must always take a parameter. For example, the `onclick` handler requires a function that takes a parameter of type `MouseEvent`. The handler can then decide what kind of message should be sent to the component. This message is scheduled for the next update loop unconditionally. + +If you need a callback that might not need to cause an update, use `batch_callback`. + +```rust +use yew::{events::KeyboardEvent, html, Component, Context, Html}; + +enum Msg { + Submit, +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // highlight-start + let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| { + if event.key() == "Enter" { + Some(Msg::Submit) + } else { + None + } + }); + + html! { + + } + // highlight-end + } +} +``` + +## Relevant examples + +- [Counter](https://github.com/yewstack/yew/tree/master/examples/counter) +- [Timer](https://github.com/yewstack/yew/tree/master/examples/timer) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx new file mode 100644 index 00000000000..eea2038c116 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx @@ -0,0 +1,82 @@ +--- +title: 'Higher Order Components' +--- + +There are several cases where Struct components do not directly support a feature (ex. Suspense) or require a lot of boilerplate code to use the features (ex. Context). + +In those cases, it is recommended to create function components that are higher-order components. + +## Higher Order Components Definition + +Higher Order Components are components that do not add any new HTML and only wrap some other components to provide extra functionality. + +### Example + +Hook into Context and pass it down to a struct component + +```rust +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + context={(*ctx).clone()}> + + > + } +} + +// highlight-start +#[function_component] +pub fn ThemedButtonHOC() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! {} +} +// highlight-end + +#[derive(Properties, PartialEq)] +pub struct Props { + pub theme: Theme, +} + +struct ThemedButtonStructComponent; + +impl Component for ThemedButtonStructComponent { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let theme = &ctx.props().theme; + html! { + + } + } +} + + + + +``` diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx new file mode 100644 index 00000000000..311fe6ae5a1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: 'Introduction' +description: 'Components in Yew' +--- + +## What are Components? + +Components are the building blocks of Yew. They manage an internal state and can render elements to the DOM. +Components are created by implementing the `Component` trait for a type. + +## Writing Component's markup + +Yew uses Virtual DOM to render elements to the DOM. The Virtual DOM tree can be constructed by using the +`html!` macro. `html!` uses a syntax which is similar to HTML but is not the same. The rules are also +much stricter. It also provides superpowers like conditional rendering and rendering of lists using iterators. + +:::info +[Learn more about the `html!` macro, how it is used and its syntax](concepts/html/introduction.mdx) +::: + +## Passing data to a component + +Yew components use _props_ to communicate between parents and children. A parent component may pass any data as props to +its children. Props are similar to HTML attributes but any Rust type can be passed as props. + +:::info +[Learn more about the props](advanced-topics/struct-components/properties.mdx) +::: + +:::info +For other than parent/child communication, use [contexts](../../concepts/contexts.mdx) +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx index 563663ad1e6..7f918fe1abe 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx @@ -1,156 +1,262 @@ --- -description: 组件及其生命周期钩子 +title: '生命周期' +description: '组件及其生命周期钩子' --- -# 组件(Components) - -## 组件是什么? - -组件是 Yew 的基石。它们管理自己的状态,并可以渲染为 DOM。组件是通过实现描述组件生命周期的 `Component` trait 来创建的。 +`Component` trait 有许多需要实现的方法;Yew 会在组件生命周期的不同阶段调用这些方法。 ## 生命周期 -:::note -`为我们的文档做出贡献:`[添加组件的生命周期图示](https://github.com/yewstack/docs/issues/22) +:::important contribute +`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/yew/issues/1915) ::: ## 生命周期方法 ### Create -当一个组件被创建时,它会从其父组件以及一个 `ComponentLink` 接收属性(properties)。属性(properties)可用于初始化组件的状态,“link”可用于注册回调或向组件发送消息。 - -通常将 props 和 link 存储在组件的结构体中,如下所示: +当一个组件被创建时,它会从其父组件接收属性(properties)并存储在传递给 `create` 方法的 `Context` 中。 +属性(properties)可用于初始化组件的状态,"link"可用于注册回调或向组件发送消息。 ```rust -pub struct MyComponent { - props: Props, - link: ComponentLink, -} +use yew::{Component, Context, html, Html, Properties}; + +#[derive(PartialEq, Properties)] +pub struct Props; + +pub struct MyComponent; impl Component for MyComponent { + type Message = (); type Properties = Props; - // ... - fn create(props: Self::Properties, link: ComponentLink) -> Self { - MyComponent { props, link } + // highlight-start + fn create(ctx: &Context) -> Self { + MyComponent } + // highlight-end - // ... + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } } ``` ### View -组件在 `view()` 方法中声明它的布局。Yew 提供了 `html!` 宏来声明 HTML 和 SVG 节点和它们的监听器及其子组件。这个宏的行为很像 React 中的 JSX,但是使用的是 Rust 表达式而不是 JavaScript。 +`view` 方法允许你描述组件应该如何渲染到 DOM。使用 Rust 函数编写类似 HTML 的代码可能会变得相当混乱, +因此 Yew 提供了一个叫做 `html!` 的宏用于声明 HTML 和 SVG 节点(以及附加属性和事件监听器), +并提供了一种方便的方式来渲染子组件。这个宏在某种程度上类似于 React 的 JSX(撇开编程语言的差异不谈)。 +一个区别是 Yew 提供了类似 Svelte 的属性简写语法,你可以直接写 `{onclick}`,而不是写 `onclick={onclick}`。 ```rust +use yew::{Component, Context, html, Html, Properties}; + +enum Msg { + Click, +} + +#[derive(PartialEq, Properties)] +struct Props { + button_text: String, +} + +struct MyComponent; + impl Component for MyComponent { - // ... + type Message = Msg; + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } - fn view(&self) -> Html { - let onclick = self.link.callback(|_| Msg::Click); + // highlight-start + fn view(&self, ctx: &Context) -> Html { + let onclick = ctx.link().callback(|_| Msg::Click); html! { - + } } + // highlight-end } ``` -有关用法的详细信息,请查看 [`html!` 宏指南](concepts/html/introduction.mdx)] +有关用法的详细信息,请查看 [`html!` 宏指南](concepts/html/introduction.mdx)。 -### Mounted +### Rendered -`mounted()` 组件生命周期方法调用是在 `view()` 被处理并且 Yew 已经把组件挂载到 DOM 上之后,浏览器刷新页面之前。组件通常希望实现此方法以执行只能在组件渲染元素之后才能执行的操作。如果你想在做出一些更改后重新渲染组件,返回 `true` 就可以了。 +`rendered` 组件生命周期方法在 `view` 被调用并且 Yew 已将结果渲染到 DOM 之后,但在浏览器刷新页面之前被调用。 +当你想要执行只能在组件渲染元素之后才能完成的操作时,这个方法非常有用。 +还有一个名为 `first_render` 的参数,可用于确定此函数是在第一次渲染时被调用,还是在后续渲染时被调用。 ```rust -use stdweb::web::html_element::InputElement; -use stdweb::web::IHtmlElement; -use yew::prelude::*; +use web_sys::HtmlInputElement; +use yew::{ + Component, Context, html, Html, NodeRef, +}; pub struct MyComponent { node_ref: NodeRef, } impl Component for MyComponent { - // ... + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + node_ref: NodeRef::default(), + } + } - fn view(&self) -> Html { + fn view(&self, ctx: &Context) -> Html { html! { } } - fn mounted(&mut self) -> ShouldRender { - if let Some(input) = self.node_ref.cast::() { - input.focus(); + // highlight-start + fn rendered(&mut self, _ctx: &Context, first_render: bool) { + if first_render { + if let Some(input) = self.node_ref.cast::() { + input.focus(); + } } - false } + // highlight-end } ``` -:::note -请注意,此生命周期方法不要求必须实现,默认情况下不会执行任何操作。 +:::tip note +注意,此生命周期方法不需要实现,默认情况下不会执行任何操作。 ::: ### Update -组件是动态的,可以注册以接收异步信息。`update()` 生命周期方法对于每个消息都会被调用。这使得组件可以根据消息的内容来更新自身,并决定是否需要重新渲染自己。消息可以由 HTML 元素监听器触发,或者由子组件,Agents,Services 或 Futures 发送。 +与组件的通信主要通过消息进行,这些消息由 `update` 生命周期方法处理。 +这允许组件根据消息的内容更新自身,并决定是否需要重新渲染。 +消息可以由事件监听器、子组件、Agents、Services 或 Futures 发送。 -`update()` 可能看起来像下面这个例子: +以下是 `update` 实现的示例: ```rust +use yew::{Component, Context, html, Html}; + +// highlight-start pub enum Msg { SetInputEnabled(bool) } +// highlight-end + +struct MyComponent { + input_enabled: bool, +} impl Component for MyComponent { + // highlight-next-line type Message = Msg; + type Properties = (); - // ... + fn create(_ctx: &Context) -> Self { + Self { + input_enabled: false, + } + } + + // highlight-start + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SetInputEnabled(enabled) => { + if self.input_enabled != enabled { + self.input_enabled = enabled; + true // Re-render + } else { + false + } + } + } + } + // highlight-end - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::SetInputEnabled(enabled) => { - if self.input_enabled != enabled { - self.input_enabled = enabled; - true // 重新渲染 - } else { - false - } - } - } + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } } + } ``` -### Change +### Changed + +组件可能会被其父组件重新渲染。当这种情况发生时,它们可能会收到新的属性并需要重新渲染。 +这种设计通过简单地改变属性值来促进父组件到子组件的通信。 +有一个默认实现会在属性发生变化时重新渲染组件。 + +### Destroy + +组件从 DOM 卸载后,Yew 会调用 `destroy` 生命周期方法; +如果你需要在组件销毁之前清理组件先前操作的内容,这是必要的。 +此方法是可选的,默认情况下不执行任何操作。 + +### 无限循环 -组件可能被其父节点重新渲染。发生这种情况时,它们可以接收新的属性(properties)并选择重新渲染。这种设计通过更改属性(properties)来促进父子组件之间的通信。你不是必须实现 `change()`,但是如果想在组件被创建后通过 props 来更新组件,则可能要这么做。 +使用 Yew 的生命周期方法可能会出现无限循环,但只有在尝试在每次渲染后更新同一组件, +并且该更新也请求组件重新渲染时才会发生。 -一个原始的实现可能看起来像: +一个简单的示例如下: ```rust -impl Component for MyComponent { - // ... +use yew::{Context, Component, Html}; - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.props = props; - true // 当提供了新的 props 将始终重新渲染。 +struct Comp; + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + // We are going to always request to re-render on any msg + true + } + + fn view(&self, _ctx: &Context) -> Html { + // For this example it doesn't matter what is rendered + Html::default() + } + + fn rendered(&mut self, ctx: &Context, _first_render: bool) { + // Request that the component is updated with this new msg + ctx.link().send_message(()); } } ``` -### Destroy +让我们来看看这里发生了什么: + +1. 使用 `create` 函数创建组件。 +2. 调用 `view` 方法,以便 Yew 知道要渲染什么到浏览器 DOM。 +3. 调用 `rendered` 方法,它使用 `Context` 链接安排了一个更新消息。 +4. Yew 完成后渲染阶段。 +5. Yew 检查预定的事件,发现更新消息队列不为空,因此处理这些消息。 +6. 调用 `update` 方法,它返回 `true` 表示有内容已更改,组件需要重新渲染。 +7. 跳回步骤 2。 -组件从 DOM 上被卸载后,Yew 调用 `destroy()` 生命周期方法来支持任何必要的清理操作。这个方法是可选的,默认情况下不执行任何操作。 +你仍然可以在 `rendered` 方法中安排更新,这通常很有用, +但在这样做时要考虑你的组件将如何终止这个循环。 ## 关联类型 `Component` trait 有两个关联类型:`Message` 和 `Properties`。 -```rust +```rust ,ignore impl Component for MyComponent { type Message = Msg; type Properties = Props; @@ -159,12 +265,30 @@ impl Component for MyComponent { } ``` -`Message` 表示组件可以处理以触发某些副作用的各种消息。例如,你可能有一条 `Click` 消息,该消息触发 API 请求或者切换 UI 组件的外观。通常的做法是在组件模块中创建一个叫做 `Msg` 的枚举并将其用作组件中的消息类型。通常将“message”缩写为“msg”。 +`Message` 类型用于在事件发生后向组件发送消息; +例如,你可能希望在用户点击按钮或向下滚动页面时执行某些操作。 +由于组件通常需要响应多个事件,`Message` 类型通常是一个枚举, +其中每个变体都是要处理的事件。 + +在组织代码库时,明智的做法是将 `Message` 类型的定义包含在定义组件的同一模块中。 +你可能会发现采用一致的消息类型命名约定很有帮助。 +一种选择(尽管不是唯一的)是将类型命名为 `ComponentNameMsg`, +例如,如果你的组件叫 `Homepage`,那么你可能会将类型命名为 `HomepageMsg`。 ```rust enum Msg { Click, + FormInput(String) } ``` -`Properties` 表示从父级传递到组件的信息。此类型必须实现 `Properties` trait(通常通过派生),并且可以指定某些属性(properties)是必需的还是可选的。创建和更新组件时使用此类型。通常的做法是在组件模块中创建一个叫做 `Props` 的结构体并将其用作组件的 `Properties` 类型。通常将“properties”缩写为“props”。由于 props 是从父组件传递下来的,因此应用程序的根组件通常有一个类型为 `()` 的 `Properties`。如果你希望为根组件指定属性(properties),请使用 `App::mount_with_props` 方法。 +`Properties` 表示从父组件传递给组件的信息。此类型必须实现 `Properties` trait(通常通过派生它)并可以指定某些属性是必需的还是可选的。此类型在创建和更新组件时使用。常见的做法是在组件模块中创建一个名为 `Props` 的结构体,并将其用作组件的 `Properties` 类型。通常将"properties"缩写为"props"。由于 props 是从父组件传递下来的,应用程序的根组件通常具有 `()` 类型的 `Properties`。如果你希望为根组件指定属性,请使用 `App::mount_with_props` 方法。 + +:::info +[了解更多关于属性的信息](./properties) +::: + +## 生命周期上下文 + +所有组件生命周期方法都接受一个上下文对象。此对象提供了对组件作用域的引用, +允许向组件发送消息以及访问传递给组件的 props。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx index f07a81fa927..33bf7b723f5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx @@ -1,43 +1,64 @@ --- -description: 父组件到子组件的通信 +title: '属性(Properties)' +description: '父组件到子组件的通信' --- -# 属性(Properties) - -如“组件(Components)”页面所述,Properties 用于父级到子组件的通信。 +Properties 用于父级到子组件的通信。 +每个组件都有一个关联的属性类型,描述了从父级传递下来的内容。 +理论上,这可以是任何实现了 `Properties` trait 的类型,但实际上, +没有理由不使用一个结构体,其中每个字段都代表一个属性。 ## 派生宏 不要尝试自己去实现 `Properties`,而是通过使用 `#[derive(Properties)]` 来派生它。 +派生 `Properties` 的类型还必须实现 `PartialEq`。 -### 必需属性 +### Field attributes -默认情况下,实现了 `Properties` 的结构体中的字段是必需的。当缺少了该字段并且在 `html!` 宏中创建了组件时,将返回编译错误。对于具有可选属性的字段,使用 `#[prop_or_default]` 来使用该类型的默认值。要指定一个值,请使用 `#[prop_or_else(value)]`,其中 value 是该属性的默认值。例如,要将一个布尔值的默认值设置为 `true`,请使用属性 `#[prop_or_else(true)]`。可选属性通常使用 `Option`,其默认值为 `None`。 +When deriving `Properties`, all fields are required by default. +The following attributes allow you to give your props initial values which will be used unless they are set to another value. -### PartialEq +:::tip +Attributes aren't visible in Rustdoc generated documentation. +The doc strings of your properties should mention whether a prop is optional and if it has a special default value. +::: -如果可以的话,在你的 props 上派生 `PartialEq` 通常是很有意义的。这使用了一个**性能优化与最佳实践**部分解释了的技巧,可以更轻松地避免重新渲染。 +#### `#[prop_or_default]` -## Properties 的内存/速度开销 +Initialize the prop value with the default value of the field's type using the `Default` trait. -记住组件的 `view` 函数签名: +#### `#[prop_or(value)]` -```rust -fn view(&self) -> Html -``` +Use `value` to initialize the prop value. `value` can be any expression that returns the field's type. +For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`. + +#### `#[prop_or_else(function)]` -你对组件的状态取了一个引用,并用来创建 `Html`。但是 properties 是有所有权的值(owned values)。这意味着为了创造它们并且将它们传递给子组件,我们需要获取 `view` 函数里提供的引用的所有权。这是在将引用传递给组件时隐式克隆引用完成的,以获得构成其 props 的有所有权的值。 +Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type. -这意味着每个组件都有从其父级传递来的状态的独特副本,而且,每当你重新渲染一个组件时,该重新渲染组件的所有子组件的 props 都将被克隆。 +## `PartialEq` -这意味着如果你将 _大量_ 数据作为 props(大小为 10 KB 的字符串)向下传递,则可能需要考虑将子组件转换为在父级运行返回 `Html` 的函数,因为这样就不会被强制克隆你的数据。 +`Properties` require `PartialEq` to be implemented. This is so that they can be compared by Yew to call the `changed` method +only when they change. -另外,如果你不需要修改作为 props 传递的大数据,而只需要显示它,则可以将其包装在 `Rc` 中,以便仅克隆一个引用计数的指针,而不是数据本身。 +## Memory/speed overhead of using Properties -## 示例 +Internally properties are reference counted. This means that only a pointer is passed down the component tree for props. +It saves us from the cost of having to clone the entire props, which might be expensive. + +:::tip +Make use of `AttrValue` which is our custom type for attribute values instead of defining them as String or another similar type. +::: + +## Example ```rust -pub struct LinkColor { +use yew::Properties; +/// Importing the AttrValue from virtual_dom +use yew::virtual_dom::AttrValue; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { Blue, Red, Green, @@ -45,28 +66,79 @@ pub struct LinkColor { Purple, } -impl Default for LinkColor { - fn default() -> Self { - // 除非另有说明,否则链接的颜色将为蓝色 - LinkColor::Blue - } +fn create_default_link_color() -> LinkColor { + LinkColor::Blue } #[derive(Properties, PartialEq)] pub struct LinkProps { - /// 链接必须有一个目标地址 - href: String, - /// 如果链接文本很大,这将使得复制字符串开销更小 - /// 除非有性能问题,否则通常不建议这么做 - text: Rc, - /// 链接的颜色 + /// The link must have a target. + href: AttrValue, + /// Also notice that we are using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] + color: LinkColor, + /// The view function will not specify a size if this is None. #[prop_or_default] + size: Option, + /// When the view function does not specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} +``` + +## Props macro + +The `yew::props!` macro allows you to build properties the same way the `html!` macro does it. + +The macro uses the same syntax as a struct expression except that you cannot use attributes or a base expression (`Foo { ..base }`). +The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). + +```rust +use yew::{props, Properties, virtual_dom::AttrValue}; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +fn create_default_link_color() -> LinkColor { + LinkColor::Blue +} + +#[derive(Properties, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: AttrValue, + /// Also notice that we're using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] color: LinkColor, - /// 如果为 None,则 view 函数将不指定大小 + /// The view function will not specify a size if this is None. #[prop_or_default] - size: Option - /// 当 view 函数没有指定 active,其默认为 true - #[prop_or_else(true)] + size: Option, + /// When the view function doesn't specify active, it defaults to true. + #[prop_or(true)] active: bool, } + +impl LinkProps { + /// Notice that this function receives href and text as String + /// We can use `AttrValue::from` to convert it to a `AttrValue` + pub fn new_link_with_size(href: String, text: String, size: u32) -> Self { + // highlight-start + props! {LinkProps { + href: AttrValue::from(href), + text: AttrValue::from(text), + size, + }} + // highlight-end + } +} ``` diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx index 75e9e1d9fd9..2226b3a2060 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx @@ -6,18 +6,48 @@ description: 超出界限的 DOM 访问 `ref` 关键词可被用在任何 HTML 元素或组件内部以获得该项所附加到的 DOM 元素。这可被用于在 `view` 生命周期方法之外来对 DOM 进行更改。 这对于获取 canvas 元素或者滚动到页面的不同部分是有用的。 +For example, using a `NodeRef` in a component's `rendered` method allows you to make draw calls to +a canvas element after it has been rendered from `view`. 语法如下: ```rust -// 在 create 中 -self.node_ref = NodeRef::default(); +use web_sys::Element; +use yew::{html, Component, Context, Html, NodeRef}; -// 在 view 中 -html! { -
+struct Comp { + node_ref: NodeRef, } -// 在 update 中 -let has_attributes = self.node_ref.cast::().unwrap().has_attributes(); +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + // highlight-next-line + node_ref: NodeRef::default(), + } + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + // highlight-next-line +
+ } + } + + fn rendered(&mut self, _ctx: &Context, _first_render: bool) { + // highlight-start + let has_attributes = self.node_ref + .cast::() + .unwrap() + .has_attributes(); + // highlight-end + } +} ``` + +## Relevant examples + +- [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx new file mode 100644 index 00000000000..db4f5974fa7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx @@ -0,0 +1,81 @@ +--- +title: 'Scope' +description: "Component's Scope" +--- + +## Component's `Scope<_>` API + +The component "`Scope`" is the mechanism through which components can create callbacks and update themselves +using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. + +### `send_message` + +Sends a message to the component. +Messages are handled by the `update` method which determines whether the component should re-render. + +### `send_message_batch` + +Sends multiple messages to the component at the same time. +This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, +the component will re-render after all messages in the batch have been processed. + +If the given vector is empty, this function does nothing. + +### `callback` + +Create a callback that will send a message to the component when it is executed. +Under the hood, it will call `send_message` with the message returned by the provided closure. + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Text(String), +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback that accepts some text and sends it + // to the component as the `Msg::Text` message variant. + // highlight-next-line + let cb = ctx.link().callback(|text: String| Msg::Text(text)); + + // The previous line is needlessly verbose to make it clearer. + // It can be simplified it to this: + // highlight-next-line + let cb = ctx.link().callback(Msg::Text); + + // Will send `Msg::Text("Hello World!")` to the component. + // highlight-next-line + cb.emit("Hello World!".to_owned()); + + html! { + // html here + } + } +} +``` + +### `batch_callback` + +Create a callback that will send a batch of messages to the component when it is executed. +The difference to `callback` is that the closure passed to this method doesn't have to return a message. +Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. + +`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. + +`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. +This can be used in cases where, depending on the situation, an update isn't required. + +This is achieved using the `SendAsMessage` trait which is only implemented for these types. +You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx new file mode 100644 index 00000000000..afaced2c086 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx @@ -0,0 +1,196 @@ +--- +title: 'Contexts' +sidebar_label: Contexts +description: 'Using contexts to pass deeply nested data' +--- + +Usually, data is passed from a parent component to a child component via props. +But passing props can become verbose and annoying if you have to pass them through many components in the middle, +or if many components in your app need the same information. Context solves this problem by allowing a +parent component to make data available to _any_ component in the tree below it, no matter how deep, +without having to pass it down with props. + +## The problem with props: "Prop Drilling" + +Passing [props](./function-components/properties.mdx) is a great way to pass data directly from a parent to a child. +They become cumbersome to pass down through deeply nested component trees or when multiple components share the same data. +A common solution to data sharing is lifting the data to a common ancestor and making the children take it as props. +However, this can lead to cases where the prop has to go through multiple components to reach the component that needs it. +This situation is called "Prop Drilling". + +Consider the following example which passes down the theme using props: + +```rust +use yew::{html, Component, Context, Html, Properties, function_component}; + +#[derive(Clone, PartialEq)] +pub struct Theme { + foreground: String, + background: String, +} + +#[derive(PartialEq, Properties)] +pub struct NavbarProps { + theme: Theme, +} + +#[function_component] +fn Navbar(props: &NavbarProps) -> Html { + html! { +
+ + { "App title" } + + + { "Somewhere" } + +
+ } +} + +#[derive(PartialEq, Properties)] +pub struct ThemeProps { + theme: Theme, + children: Html, +} + +#[function_component] +fn Title(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +#[function_component] +fn NavButton(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +/// App root +#[function_component] +fn App() -> Html { + let theme = Theme { + foreground: "yellow".to_owned(), + background: "pink".to_owned(), + }; + + html! { + + } +} +``` + +We "drill" the theme prop through `Navbar` so that it can reach `Title` and `NavButton`. +It would be nice if `Title` and `NavButton`, the components that need access to the theme, can just access the theme +without having to pass it to them as a prop. Contexts solve this problem by allowing a parent to pass data, theme in this case, +to its children. + +## Using Contexts + +### Step 1: Providing the context + +A context provider is required to consume the context. `ContextProvider`, where `T` is the context struct used as the provider. +`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them. +The children are re-rendered when the context changes. A struct is used to define what data is to be passed. The `ContextProvider` can be used as: + +```rust +use yew::prelude::*; + + +/// App theme +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +/// Main component +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + // `ctx` is type `Rc>` while we need `Theme` + // so we deref it. + // It derefs to `&Theme`, hence the clone + context={(*ctx).clone()}> + // Every child here and their children will have access to this context. + + > + } +} + +/// The toolbar. +/// This component has access to the context +#[function_component] +pub fn Toolbar() -> Html { + html! { +
+ +
+ } +} + +/// Button placed in `Toolbar`. +/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access +/// to the context. +#[function_component] +pub fn ThemedButton() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! { + + } +} +``` + +### Step 2: Consuming context + +#### Function components + +`use_context` hook is used to consume contexts in function components. +See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use_context.html) to learn more. + +#### Struct components + +We have 2 options to consume contexts in struct components: + +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) + +## Use cases + +Generally, if some data is needed by distant components in different parts of the tree, context will likely help you. +Here are some examples of such cases: + +- **Theming**: You can put a context at the top of the app that holds your app theme and use it to adjust the visual appearance, as shown in the above example. +- **Current user account**: In many cases, components need to know the currently logged-in user. You can use a context to provide the current user object to the components. + +### Considerations to make before using contexts + +Contexts are very easy to use. That makes them very easy to misuse/overuse. +Just because you can use a context to share props to components multiple levels deep, does not mean that you should. + +For example, you may be able to extract a component and pass that component as a child to another component. For example, +you may have a `Layout` component that takes `articles` as a prop and passes it down to `ArticleList` component. +You should refactor the `Layout` component to take children as props and display ` `. + +## Mutating the context value of a child + +Because of Rust's ownership rules, a context cannot have a method that takes `&mut self` that can be called by children. +To mutate a context's value, we must combine it with a reducer. This is done by using the +[`use_reducer`](https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer.html) hook. + +The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) demonstrates mutable contexts +with the help of contexts + +## Further reading + +- The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx index babad678459..55565450f96 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx @@ -1,27 +1,76 @@ --- -title: 函数式组件 -sidebar_label: 简介 -description: 介绍函数式组件 +title: '函数式组件' slug: /concepts/function-components --- -函数式组件是普通组件的简化版本。它们由一个接收 props 的函数组成,并通过返回`Html`来确定应该呈现什么。基本上,它是一个简化为`view`方法的组件。就其本身而言,这将是相当有限的,因为您只能创建纯组件,而这就是 Hook 大展身手的地方。Hook 允许函数组件无需实现`Component` trait,就可以使用状态(state)和其他 Yew 功能。 +Let's revisit this previous statement: -## 创建函数式组件 +> Yew centrally operates on the idea of keeping everything that a reusable piece of +> UI may need in one place - rust files. -创建函数式组件的最简单方法是在函数前添加`#[function_component]`属性。 +We will refine this statement, by introducing the concept that will define the logic and +presentation behavior of an application: "components". + +## What are Components? + +Components are the building blocks of Yew. + +They: + +- Take arguments in form of [Props](./properties.mdx) +- Can have their own state +- Compute pieces of HTML visible to the user (DOM) + +## Two flavors of Yew Components + +You are currently reading about function components - the recommended way to write components +when starting with Yew and when writing simple presentation logic. + +There is a more advanced, but less accessible, way to write components - [Struct components](advanced-topics/struct-components/introduction.mdx). +They allow very detailed control, though you will not need that level of detail most of the time. + +## Creating function components + +To create a function component add the `#[function_component]` attribute to a function. +By convention, the function is named in PascalCase, like all components, to contrast its +use to normal html elements inside the `html!` macro. ```rust -#[function_component(HelloWorld)] -fn hello_world() -> Html { +use yew::{function_component, html, Html}; + +#[function_component] +fn HelloWorld() -> Html { html! { "Hello world" } } + +// Then somewhere else you can use the component inside `html!` +#[function_component] +fn App() -> Html { + html! { } +} +``` + +## What happens to components + +When rendering, Yew will build a virtual tree of these components. +It will call the view function of each (function) component to compute a virtual version (VDOM) of the DOM +that you as the library user see as the `Html` type. +For the previous example, this would look like this: + +```xhtml + + +

"Hello world"

+ +
``` -### 更多细节 +When an update is necessary, Yew will again call the view function and reconcile the new virtual DOM with its +previous version and only propagate the new/changed/necessary parts to the actual DOM. +This is what we call **rendering**. -函数式组件由两部分组成。首先, `FunctionProvider` trait 与`Component` trait 差不多,但它只有一个名为`run`方法。之后是`FunctionComponent`结构体,它封装了`FunctionProvider`类型并将其转换为实际的`Component` 。 `#[function_component]`属性本质上只是`FunctionProvider`并将其暴露在`FunctionComponent` 。 +:::note -### 钩子(Hooks) +Behind the scenes, `Html` is just an alias for `VNode` - a virtual node. -钩子(Hooks)就是让您“钩住”组件的状态(state)和/或生命周期并执行操作的函数。 除了 Yew 自带的一些预定义的 Hook。您也可以创建自己的。 +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx new file mode 100644 index 00000000000..b8be8a6e83a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx @@ -0,0 +1,291 @@ +--- +title: '属性 (Properties)' +description: '父子组件通信' +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +:::note + +属性 (Properties) 通常被简称为 "Props"。 + +::: + +属性 (Properties) 本质上是 Yew 可以监视的组件参数。 + +一个类型必须先实现 `Properties` 特征才能被用作组件的属性。 + +## 响应性 + +Yew 在重新渲染时会在协调虚拟 DOM 时检查 props 是否已更改,以了解是否需要重新渲染嵌套组件。这样,Yew 可以被认为是一个非常响应式的框架,因为来自父组件的更改总是会向下传播,视图永远不会与来自 props/状态的数据不同步。 + +:::tip + +如果您还没有完成 [教程](../../tutorial),请尝试一下并自己测试这种响应性! + +::: + +## 派生宏 + +Yew 提供了一个派生宏来轻松地在结构体上实现 `Properties` 特征。 + +派生 `Properties` 的类型还必须实现 `PartialEq`,以便 Yew 可以进行数据比较。 + +```rust +use yew::Properties; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} +``` + +## 在函数组件中使用 + +属性 `#[function_component]` 允许可选地在函数参数中接收 Props。要提供它们,它们通过 `html!` 宏中的属性分配。 + + + + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +} + +// 然后提供属性 +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +```rust +use yew::{function_component, html, Html}; + + + + + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// 没有要提供的属性 +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +## 派生宏字段属性 + +派生 `Properties` 时,默认情况下所有字段都是必需的。 +以下属性允许您为属性提供默认值,当父组件没有设置它们时将使用这些默认值。 + +:::tip +属性在 Rustdoc 生成的文档中不可见。您的属性的文档字符串应该提及属性是否是可选的以及是否有特殊的默认值。 +::: + + + + +使用 `Default` 特征用字段类型的默认值初始化属性值。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_default] + // highlight-end + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + if props.is_loading.clone() { + html! { "Loading" } + } else { + html! { "Hello world" } + } +} + +// 然后像这样使用默认值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆盖默认值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +使用 `value` 来初始化属性值。`value` 可以是任何返回字段类型的表达式。 +例如,要将布尔属性默认为 `true`,请使用属性 `#[prop_or(true)]`。表达式在构造属性时被评估,并且没有给出明确值时应用。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or("Bob".to_string())] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// 然后像这样使用默认值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆盖默认值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +调用 `function` 来初始化属性值。`function` 应该有签名 `FnMut() -> T`,其中 `T` 是字段类型。当没有为该属性给出明确值时,该函数被调用。 + +```rust +use yew::{function_component, html, Html, Properties}; + +fn create_default_name() -> String { + "Bob".to_string() +} + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_else(create_default_name)] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// 然后像这样使用默认值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆盖默认值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +## 使用 Properties 的内存/速度开销 + +内部属性是引用计数的。这意味着只有一个共享指针会沿着组件树向下传递给 props。这节省了我们不得不克隆整个 props 的成本,这可能很昂贵。 + +:::tip +使用 `AttrValue`,这是我们用于属性值的自定义类型,而不是将它们定义为 String 或其他类似类型。 +::: + +## Props 宏 + +`yew::props!` 宏允许您以与 `html!` 宏相同的方式构建属性。 + +宏使用与结构体表达式相同的语法,除了您不能使用属性或基本表达式(`Foo { ..base }`)。类型路径可以直接指向 props(`path::to::Props`)或指向组件的关联属性(`MyComp::Properties`)。 + +```rust +use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue}; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or(AttrValue::from("Bob"))] + pub name: AttrValue, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +#[function_component] +fn App() -> Html { + // highlight-start + let pre_made_props = props! { + Props {} // 注意我们不需要指定 name 属性 + }; + // highlight-end + html! {} +} +``` + +## 评估顺序 + +Props 按指定的顺序进行评估,如以下示例所示: + +```rust +#[derive(yew::Properties, PartialEq)] +struct Props { first: usize, second: usize, last: usize } + +fn main() { + let mut g = 1..=3; + let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() }); + + assert_eq!(props.first, 1); + assert_eq!(props.second, 2); + assert_eq!(props.last, 3); +} +``` + +## 反模式 + +虽然几乎任何 Rust 类型都可以作为属性传递,但有一些应该避免的反模式。这些包括但不限于: + +1. 使用 `String` 类型而不是 `AttrValue`。
+ **为什么不好?** `String` 克隆成本很高。当属性值与钩子和回调一起使用时,通常需要克隆。`AttrValue` 是一个引用计数的字符串 (`Rc`) 或一个 `&'static str`,因此非常便宜克隆。
+ **注意**:`AttrValue` 内部是来自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看该包以了解更多信息。 +2. 使用内部可变性。
+ **为什么不好?** 内部可变性(例如 `RefCell`、`Mutex` 等)应该 _通常_ 避免使用。它可能会导致重新渲染问题(Yew 不知道状态何时发生了变化),因此您可能需要手动强制重新渲染。就像所有事物一样,它有其用武之地。请谨慎使用。 +3. 使用 `Vec` 类型而不是 `IArray`。
+ **为什么不好?** `Vec`,就像 `String` 一样,克隆成本也很高。`IArray` 是一个引用计数的切片 (`Rc<[T]>`) 或一个 `&'static [T]`,因此非常便宜克隆。
+ **注意**:`IArray` 可以从 [implicit-clone](https://crates.io/crates/implicit-clone) 导入。查看该包以了解更多信息。 +4. 您发觉可能的新内容。您是否遇到了一个希望早点了解清楚的边缘情况?请随时创建一个问题或向本文档提供修复的 PR。 + +## yew-autoprops + +[yew-autoprops](https://crates.io/crates/yew-autoprops) 是一个实验性包,允许您根据函数的参数动态创建 Props 结构体。如果属性结构体永远不会被重用,这可能会很有用。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx index 4b3da26e0a9..026ff0c77df 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ description: 'Yew 的不可变数据结构' 不可变类型非常适合保存属性的值,因为它们可以在从组件传递到组件时以很低的成本克隆。 +## 常见的不可变类型 + +Yew 推荐使用来自 `implicit-clone` crate 的以下不可变类型: + +- `IString`(在 Yew 中别名为 `AttrValue`)- 用于字符串而不是 `String` +- `IArray` - 用于数组/向量而不是 `Vec` +- `IMap` - 用于映射而不是 `HashMap` + +这些类型是引用计数(`Rc`)或静态引用,使它们的克隆成本非常低。 + ## 进一步阅读 - [不可变示例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx index 9c07b61952c..f6f012f77e8 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 我们有两种选择在结构体组件中使用上下文: -- [高阶组件](../advanced-topics/struct-components/hoc.mdx):一个高阶函数组件将使用上下文并将数据传递给需要它的结构体组件。 +- [高阶组件](../advanced-topics/struct-components/hoc):一个高阶函数组件将使用上下文并将数据传递给需要它的结构体组件。 - 直接在结构体组件中使用上下文。请参阅 [结构体组件作为消费者的示例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## 使用场景 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx index 0dd3b7665c2..b1d7cdd786f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` 在内部是来自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看该包以了解更多信息。 2. 使用内部可变性。
**为什么不好?** 内部可变性(例如 `RefCell`、`Mutex` 等)应该 _通常_ 避免使用。它可能会导致重新渲染问题(Yew 不知道状态何时发生了变化),因此您可能需要手动强制重新渲染。就像所有事物一样,它有其用武之地。请谨慎使用。 -3. 使用 `Vec` 类型而不是 `IArray`。
- **为什么不好?** `Vec`,就像 `String` 一样,克隆成本也很高。`IArray` 是一个引用计数的切片 (`Rc`) 或一个 `&'static [T]`,因此非常便宜克隆。
+3. 使用 `Vec` 类型而不是 `IArray`。
+ **为什么不好?** `Vec`,就像 `String` 一样,克隆成本也很高。`IArray` 是一个引用计数的切片 (`Rc<[T]>`) 或一个 `&'static [T]`,因此非常便宜克隆。
**注意**:`IArray` 可以从 [implicit-clone](https://crates.io/crates/implicit-clone) 导入。查看该包以了解更多信息。 4. 您发觉可能的新内容。您是否遇到了一个希望早点了解清楚的边缘情况?请随时创建一个问题或向本文档提供修复的 PR。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx index 8db17d4ea1b..00bcc99d3d9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## 事件捕获 {#event-bubbling} -Yew 调度的事件遵循虚拟 DOM 层次结构,向上冒泡到监听器。目前,仅支持监听器的冒泡阶段。请注意,虚拟 DOM 层次结构通常(但并非总是)与实际 DOM 层次结构相同。在处理[传送门](../../advanced-topics/portals.mdx)和其他更高级技术时,这一区别很重要。对于良好实现的组件,直觉应该是事件从子组件冒泡到父组件。这样,您在 `html!` 中编写的层次结构就是事件处理程序观察到的层次结构。 +Yew 调度的事件遵循虚拟 DOM 层次结构,向上冒泡到监听器。目前,仅支持监听器的冒泡阶段。请注意,虚拟 DOM 层次结构通常(但并非总是)与实际 DOM 层次结构相同。在处理[传送门](../../advanced-topics/portals)和其他更高级技术时,这一区别很重要。对于良好实现的组件,直觉应该是事件从子组件冒泡到父组件。这样,您在 `html!` 中编写的层次结构就是事件处理程序观察到的层次结构。 如果您不想要事件冒泡,可以通过调用 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx index 9d6a9344853..61c67da5e15 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ description: 'Yew 的不可變資料結構' 不可變類型非常適合保存屬性的值,因為它們可以在從組件傳遞到組件時以很低的成本克隆。 +## 常見的不可變型別 + +Yew 推薦使用來自 `implicit-clone` crate 的以下不可變型別: + +- `IString`(在 Yew 中別名為 `AttrValue`)- 用於字串而不是 `String` +- `IArray` - 用於陣列/向量而不是 `Vec` +- `IMap` - 用於映射而不是 `HashMap` + +這些型別是引用計數(`Rc`)或靜態引用,使它們的克隆成本非常低。 + ## 進一步閱讀 - [不可變範例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/contexts.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/contexts.mdx index 2cccc51dd36..0a3cab9be88 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/contexts.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 我們有兩種選擇在結構體組件中使用上下文: -- [高階元件](../advanced-topics/struct-components/hoc.mdx):高階函數元件將使用上下文並將資料傳遞給需要它的結構體元件。 +- [高階元件](../advanced-topics/struct-components/hoc):高階函數元件將使用上下文並將資料傳遞給需要它的結構體元件。 - 直接在結構體組件中使用上下文。請參閱 [結構體組件作​​為消費者的範例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## 使用場景 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx index c73aa80ef1f..de2dd008ce3 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` 在內部是來自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看該包以了解更多資訊。 2. 使用內部可變性。
**為什麼不好? ** 內部可變性(例如 `RefCell`、`Mutex` 等)應該 _通常_ 避免使用。它可能會導致重新渲染問題(Yew 不知道狀態何時發生了變化),因此您可能需要手動強制重新渲染。就像所有事物一樣,它有其用武之地。請謹慎使用。 -3. 使用 `Vec` 型別而不是 `IArray`。
- **為什麼不好? ** `Vec`,就像 `String` 一樣,克隆成本也很高。 `IArray` 是一個引用計數的切片 (`Rc`) 或一個 `&'static [T]`,因此非常便宜克隆。
+3. 使用 `Vec` 型別而不是 `IArray`。
+ **為什麼不好? ** `Vec`,就像 `String` 一樣,克隆成本也很高。 `IArray` 是一個引用計數的切片 (`Rc<[T]>`) 或一個 `&'static [T]`,因此非常便宜克隆。
**注意**:`IArray` 可以從 [implicit-clone](https://crates.io/crates/implicit-clone) 匯入。查看該包以了解更多資訊。 4. 您發覺可能的新內容。您是否遇到了一個希望早點了解清楚的邊緣情況?請隨時建立一個問題或向本文檔提供修復的 PR。 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/events.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/events.mdx index 95ccf2e6d68..09a05190bad 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/events.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## 事件捕獲 {#event-bubbling} -Yew 調度的事件遵循虛擬 DOM 層次結構,向上冒泡到監聽器。目前,僅支援監聽器的冒泡階段。請注意,虛擬 DOM 層次結構通常(但並非總是)與實際 DOM 層次結構相同。在處理[傳送門](../../advanced-topics/portals.mdx)和其他更高級技術時,這一區別很重要。對於良好實現的元件,直覺應該是事件從子元件冒泡到父元件。這樣,您在 `html!` 中所寫的層次結構就是事件處理程序觀察到的層次結構。 +Yew 調度的事件遵循虛擬 DOM 層次結構,向上冒泡到監聽器。目前,僅支援監聽器的冒泡階段。請注意,虛擬 DOM 層次結構通常(但並非總是)與實際 DOM 層次結構相同。在處理[傳送門](../../advanced-topics/portals)和其他更高級技術時,這一區別很重要。對於良好實現的元件,直覺應該是事件從子元件冒泡到父元件。這樣,您在 `html!` 中所寫的層次結構就是事件處理程序觀察到的層次結構。 如果您不想要事件冒泡,可以透過呼叫 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx new file mode 100644 index 00000000000..f54f9c02eb4 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/children.mdx @@ -0,0 +1,318 @@ +--- +title: 'Children' +--- + +:::caution + +Inspecting and manipulating `Children` can often result in surprising and hard-to-explain behaviours in your application. +This can lead to edge cases and often does not yield expected result. +You should consider other approaches if you are trying to manipulate `Children`. + +Yew supports using `Html` as the type of the children prop. +You should use `Html` as children if you do not need `Children` or `ChildrenRenderer`. +It doesn't have the drawbacks of `Children` and has a lower performance overhead. + +::: + +## General usage + +_Most of the time,_ when allowing a component to have children, you don't care +what type of children the component has. In such cases, the below example will +suffice. + +```rust +use yew::{html, Component, Context, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: Html, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ {ctx.props().children.clone()} +
+ } + } +} +``` + +## Advanced usage + +### Typed children + +In cases where you want one type of component to be passed as children to your component, +you can use `yew::html::ChildrenWithProps`. + +```rust +use yew::{html, ChildrenWithProps, Component, Context, Html, Properties}; + +pub struct Item; + +impl Component for Item { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "item" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenWithProps, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +## Nested Children with Props + +Nested component properties can be accessed and mutated if the containing component types its children. + +```rust +use std::rc::Rc; +use yew::prelude::*; + +#[derive(Clone, PartialEq, Properties)] +pub struct ListItemProps { + value: String, +} + +#[function_component] +fn ListItem(props: &ListItemProps) -> Html { + let ListItemProps { value } = props.clone(); + html! { + + {value} + + } +} + +#[derive(PartialEq, Properties)] +pub struct Props { + pub children: ChildrenWithProps, +} + +#[function_component] +fn List(props: &Props) -> Html { + let modified_children = props.children.iter().map(|mut item| { + let mut props = Rc::make_mut(&mut item.props); + props.value = format!("item-{}", props.value); + item + }); + html! { for modified_children } +} + +html! { + + + + + +}; +``` + +### Enum typed children + +Of course, sometimes you might need to restrict the children to a few different +components. In these cases, you have to get a little more hands-on with Yew. + +The [`derive_more`](https://github.com/JelteF/derive_more) crate is used here +for better ergonomics. If you don't want to use it, you can manually implement +`From` for each variant. + +```rust +use yew::{ + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, +}; + +pub struct Primary; + +impl Component for Primary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Primary" } + } + } +} + +pub struct Secondary; + +impl Component for Secondary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Secondary" } + } + } +} + +#[derive(Clone, derive_more::From, PartialEq)] +pub enum Item { + Primary(VChild), + Secondary(VChild), +} + +// Now, we implement `Into` so that yew knows how to render `Item`. +#[allow(clippy::from_over_into)] +impl Into for Item { + fn into(self) -> Html { + match self { + Self::Primary(child) => child.into(), + Self::Secondary(child) => child.into(), + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenRenderer, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +### Optional typed child + +You can also have a single optional child component of a specific type too: + +```rust +use yew::{ + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties +}; + +pub struct PageSideBar; + +impl Component for PageSideBar { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "sidebar" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct PageProps { + #[prop_or_default] + pub sidebar: Option>, +} + +struct Page; + +impl Component for Page { + type Message = (); + type Properties = PageProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } + // ... page content +
+ } + } +} + +// The page component can be called either with the sidebar or without: + +pub fn render_page(with_sidebar: bool) -> Html { + if with_sidebar { + // Page with sidebar + html! { + + }} /> + } + } else { + // Page without sidebar + html! { + + } + } +} +``` + +## Further Reading + +- For a real-world example of this pattern, check out the yew-router source code. For a more advanced example, check out the [nested-list example](https://github.com/yewstack/yew/tree/master/examples/nested_list) in the main yew repository. diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx index 66ec109f8d7..6f486adf924 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/how-it-works.mdx @@ -1,7 +1,74 @@ --- -description: 關於框架的底層細節 +title: 'How it works' +description: '關於框架的底層細節' --- # 內部底層的 library -元件生命周期的狀態機與 vdom diff 演算法 +## Under the hood of the `html!` macro + +The `html!` macro turns code written in a custom HTML-like syntax into valid Rust code. Using this +macro is not necessary for developing Yew applications, but it is recommended. The code generated +by this macro makes use of the public Yew library API which can be used directly if you wish. Note +that some methods used are undocumented intentionally to avoid accidental misuse. With each +update of `yew-macro`, the generated code will be more efficient and handle any breaking changes +without many (if any) modifications to the `html!` syntax. + +Because the `html!` macro allows you to write code in a declarative style, your UI layout code will +closely match the HTML that is generated for the page. This becomes increasingly useful as your +application gets more interactive and your codebase gets larger. Rather than manually writing +all of the code to manipulate the DOM yourself, the macro will handle it for you. + +Using the `html!` macro can feel pretty magical, but it has nothing to hide. If you are curious about +how it works, try expanding the `html!` macro calls in your program. There is a useful command called +`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` does not ship with +`cargo` by default so you will need to install it with `cargo install cargo-expand` if you have not +already. [Rust-Analyzer](https://rust-analyzer.github.io/) also provides a mechanism for +[obtaining macro output from within an IDE](https://rust-analyzer.github.io/manual.html#expand-macro-recursively). + +Output from the `html!` macro is often pretty terse! This is a feature: machine-generated code can +sometimes clash with other code in an application. To prevent issues, `proc_macro` +"hygiene" is adhered to. Some examples include: + +1. Instead of using `yew::` the macro generates `::yew::` to make sure that the + Yew package is referenced correctly. This is also why `::alloc::vec::Vec::new()` is called instead + of just `Vec::new()`. +2. Due to potential trait method name collisions, `` is used to make sure that we are + using members from the correct trait. + +## What is a virtual DOM? + +The DOM ("document object model") is a representation of the HTML content that is managed by the browser +for your web page. A "virtual" DOM is simply a copy of the DOM that is held in application memory. Managing +a virtual DOM results in a higher memory overhead, but allows for batching and faster reads by avoiding +or delaying the use of browser APIs. + +Having a copy of the DOM in memory can be helpful for libraries that promote the use of +declarative UIs. Rather than needing specific code for describing how the DOM should be modified +in response to a user event, the library can use a generalized approach with DOM "diffing". When a Yew +component is updated and wants to change how it is rendered, the Yew library will build a second copy +of the virtual DOM and directly compare it to a virtual DOM which mirrors what is currently on screen. +The "diff" (or difference) between the two can be broken down into incremental updates and applied in +a batch with browser APIs. Once the updates are applied, the old virtual DOM copy is discarded and the +new copy is saved for future diff checks. + +This "diff" algorithm can be optimized over time to improve the performance of complex applications. +Since Yew applications are run with WebAssembly, we believe that Yew has a competitive edge to adopt +more sophisticated algorithms in the future. + +The Yew virtual DOM is not exactly one-to-one with the browser DOM. It also includes "lists" and +"components" for organizing DOM elements. A list can simply be an ordered list of elements but can +also be much more powerful. By annotating each list element with a "key", application developers +can help Yew make additional optimizations to ensure that when a list changes, the least amount +of work is done to calculate the diff update. Similarly, components provide custom logic to +indicate whether a re-render is required to help with performance. + +## Yew scheduler and component-scoped event loop + +_Contribute to the docs – explain how `yew::scheduler` and `yew::html::scope` work in depth_ + +## Further reading + +- [More information about macros from the Rust Book](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) +- [More information about `cargo-expand`](https://github.com/dtolnay/cargo-expand) +- [The API documentation for `yew::virtual_dom`](https://docs.rs/yew/*/yew/virtual_dom/index.html) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx new file mode 100644 index 00000000000..4fcb17e2c4b --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/immutable.mdx @@ -0,0 +1,34 @@ +--- +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. 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. + +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + +## Further reading + +- [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) +- [Crate `implicit-clone`](https://docs.rs/implicit-clone/) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx index f6fea28eee5..678d9b02416 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/optimizations.mdx @@ -1,90 +1,105 @@ --- -description: 加速你的專案 +title: '優化與最佳實例' +sidebar_label: Optimizations +description: '加速你的專案' --- -# 優化與最佳實例 +## Using smart pointers effectively -## neq_assign +**Note: if you're unsure about some of the terms used in this section, the Rust Book has a useful +[chapter about smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html).** -當元件從父元件接收到屬性時, `change` 的方法就會被呼叫。除了讓你更新元件的狀態,也讓你回傳,決定元件是否要在屬性改變時,重新渲染自己的布林值 `ShouldRender` 。 +To avoid cloning large amounts of data to create props when re-rendering, we can use +smart pointers to only clone a reference to the data instead of the data itself. If you pass +references to the relevant data in your props and child components instead of the actual data you +can avoid cloning any data until you need to modify it in the child component, where you can +use `Rc::make_mut` to clone and obtain a mutable reference to the data you want to alter. -重新渲染是很浪費效能的,儘可能避免這麼做。一般來說,只有在屬性真的改變時,才重新渲染。下面的程式碼是體現這個原則的例子,當屬性改變時,才回傳 `true`: +This brings further benefits in `Component::changed` when working out whether prop changes require +the component to re-render. This is because instead of comparing the value of the data the +underlying pointer addresses (i.e. the position in a machine's memory where the data is stored) can +instead be compared; if two pointers point to the same data then the value of the data they point to +must be the same. Note that the inverse might not be true! Even if two pointer addresses differ the +underlying data might still be the same - in this case you should compare the underlying data. -```rust -use yew::ShouldRender; +To do this comparison you'll need to use `Rc::ptr_eq` instead of just using `PartialEq` (which is +automatically used when comparing data using the equality operator `==`). The Rust documentation +has [more details about `Rc::ptr_eq`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq). -#[derive(PartialEq)] -struct ExampleProps; +This optimization is most useful for data types that don't implement `Copy`. If you can copy your +data cheaply, then it isn't worth putting it behind a smart pointer. For structures that +can be data-heavy like `Vec`s, `HashMap`s, and `String`s using smart pointers is likely to bring +performance improvements. -struct Example { - props: ExampleProps, -}; +This optimization works best if the values are never updated by the children, and even better if +they are rarely updated by parents. This makes `Rc<_>s` a good choice for wrapping property values +in pure components. -impl Example { - fn change(&mut self, props: ExampleProps) -> ShouldRender { - if self.props != props { - self.props = props; - true - } else { - false - } - } -} -``` - -但我們可以走的更遠!這六行的模板,使用一個 trait 和一個 實作了 `PartialEq` 的 blanket implementation ,可以被縮短至一行。請參考[這裡](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html), `yewtil` 的 crate 裡的 `NeqAssign` trait。 - -## RC - -為了避免重新渲染時,複製大量的資料來建立屬性,我們可以使用智慧指針來讓程式只複製指針。如果你使用 `Rc<_>` 來封裝你的屬性,而不是未封裝的值,你可以使用 `Rc::make_mut`,去複製與存取你想要改變的資料的可變參考,這做到了延遲複製,直到你需要更動子元件的資料。透過避免複製直到有值改變,子元件可以在 `Component::change` 拒絕與他狀態中的屬性相同值的屬性,而且這樣不會有任何效能成本。另外,這個方式,資料必須在與子元件比較與被拒絕之前,被複製進父元件的屬性中。 - -這個優化最那些無法 `Copy` 的資料型別最有用。如果你可以輕易複製你的資料,那把資料放進智慧指針裡面似乎就沒有這麼值得。對於那些包含很多像是 `Vec` 、 `HashMap` 與 `String` 的結構,這個優化對他們會更值得。 - -如果子元件幾乎不會更新值,那這個優化效果會很好,甚至如果父元件也很少更新,那效果會更好。上面的情況,使在純元件中使用 `Rc<_>s` 是一個封裝屬性值很好的選擇。 - -## View 方法 - -出於程式碼的可讀性,通常會寫方法包裝複雜的 `html!`,這樣你可以避免巢狀的 HTML 造成過多的向右縮排。 - -## 純元件/函數式元件 +However, it must be noted that unless you need to clone the data yourself in the child component, +this optimization is not only useless, but it also adds the unnecessary cost of reference counting. Props +in Yew are already reference counted and no data clones occur internally. -純元件 是一種不會改變自己狀態的元件,他們只單純顯示內容或是向普通可變的元件傳送訊息。他們和 view 方法不同的地方在於們可以在 `html!` 巨集中使用,語法會像(``),而不是表達式語法(`{some_view_function()}`),而且根據他們的實作方式,他們可以被 memoized,這樣可以套用前面所述的 `neq_assign` 的邏輯避免重新渲染。 +## View functions -Yew 本身不支援純元件或是函數式元件,但是你可以透過 external crates 使用。 +For code readability reasons, it often makes sense to migrate sections of `html!` to their own +functions. Not only does this make your code more readable because it reduces the amount of +indentation present, it also encourages good design patterns – particularly around building +composable applications because these functions can be called in multiple places which reduces the +amount of code that has to be written. -函數式元件還不存在,但是理論上純元件可以透過巨集與宣告方法產生。 +## Pure Components -## Keyed DOM nodes when they arrive +Pure components are components that don't mutate their state, only displaying content and +propagating messages up to normal, mutable components. They differ from view functions in that they +can be used from within the `html!` macro using the component syntax \(``\) +instead of expression syntax \(`{some_view_function()}`\), and that depending on their +implementation, they can be memoized (this means that once a function is called its value is "saved" +so that if it's called with the same arguments more than once it doesn't have to recompute its value +and can just return the saved value from the first function call) - preventing re-renders for +identical props. Yew compares the props internally and so the UI is only re-rendered if the props change. -## 使用 Cargo Workspaces 加速編譯 +## Reducing compile time using workspaces -Yew 最大的缺點就是花太多時間在編譯上了。編譯時間似乎和 `html!` 巨集中的程式碼質量相同。 對於小專案來說,這應該不是什麼大問題,但是對於有很多頁面的大型網頁應用程式來說,就必須要將程式碼封裝成很多 crates 以減少編譯所花的時間。 +Arguably, the largest drawback to using Yew is the long time it takes to compile Yew apps. The time +taken to compile a project seems to be related to the quantity of code passed to the `html!` macro. +This tends to not be much of an issue for smaller projects, but for larger applications, it makes +sense to split code across multiple crates to minimize the amount of work the compiler has to do for +each change made to the application. -你應該將路由與頁面區塊封裝成一個 main crate,然後將共用的程式碼與元件封裝成另一個 crate,將每個頁面會用到的不同的元件,各自封裝到不同的 crate 中,或是只產生 `Html` 的大方法中。最好的狀況,你只需要重新編譯你 main crate 與修改的頁面的 crate 的程式碼;而最壞的情況,你編輯了共用的 crate,你就必須重新編譯所有依賴這個共用 crate 的程式碼。 +One possible approach is to make your main crate handle routing/page selection, and then make a +different crate for each page, where each page could be a different component or just a big +function that produces `Html`. Code that is shared between the crates containing different parts of +the application could be stored in a separate crate which the project depends on. +In the best-case scenario, you go from rebuilding all of your code on each compile to rebuilding +only the main crate, and one of your page crates. In the worst case, where you edit something in the +"common" crate, you will be right back to where you started: compiling all code that depends on that +commonly shared crate, which is probably everything else. -如果你的 main crate 太過龐大,或是你希望快速迭代深層巢狀的頁面(一個頁面渲染另一個頁面的頂層),你可以使用範例的 crate ,在一個簡單的主頁面上編輯你未完成的元件。 +If your main crate is too heavyweight, or you want to rapidly iterate on a deeply nested page \(eg. +a page that renders on top of another page\), you can use an example crate to create a simplified +implementation of the main page and additionally render the component you are working on. -## 編譯大小的優化 +## Reducing binary sizes -- 優化 Rust 的程式碼 -- `cargo.toml` (定義釋出的設定檔) -- 使用 `wasm-opt` 優化 wasm 程式碼 +- optimize Rust code +- `cargo.toml` \( defining release profile \) +- optimize wasm code using `wasm-opt` -更多關於程式碼大小的資訊,請參考: [rustwasm book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size) +**Note: more information about reducing binary sizes can be found in the +[Rust Wasm Book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).** -### Cargo.toml +### Cargo.toml -你可以設定你的發行版本更小的檔案大小,透過設定 `Cargo.toml` 的 `[profile.release]` 。 +It is possible to configure release builds to be smaller using the available settings in the +`[profile.release]` section of your `Cargo.toml`. -[Rust profiles documentation](https://doc.rust-lang.org/cargo/reference/profiles.html) - -```rust +```toml, title=Cargo.toml [profile.release] # less code to include into binary panic = 'abort' # optimization over all codebase ( better optimization, slower build ) codegen-units = 1 -# optimization for size ( more aggresive ) +# optimization for size ( more aggressive ) opt-level = 'z' # optimization for size # opt-level = 's' @@ -92,27 +107,57 @@ opt-level = 'z' lto = true ``` -### wasm-opt +### Nightly Cargo configuration + +You can also gain additional benefits from experimental nightly features of rust and +cargo. To use the nightly toolchain with `trunk`, set the `RUSTUP_TOOLCHAIN="nightly"` environment +variable. Then, you can configure unstable rustc features in your `.cargo/config.toml`. +Refer to the doc of [unstable features], specifically the section about [`build-std`] and +[`build-std-features`], to understand the configuration. + +```toml, title=".cargo/config.toml" +[unstable] +# Requires the rust-src component. `rustup +nightly component add rust-src` +build-std = ["std", "panic_abort"] +build-std-features = ["panic_immediate_abort"] +``` + +[unstable features]: https://doc.rust-lang.org/cargo/reference/unstable.html +[`build-std`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std +[`build-std-features`]: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std-features + +:::caution +The nightly rust compiler can contain bugs, such as [this one](https://github.com/yewstack/yew/issues/2696), +that require occasional attention and tweaking. Use these experimental options with care. +::: -更多優化 `wasm` 程式碼大小的方法。 +### wasm-opt -wasm-opt 資訊: [binaryen project](https://github.com/WebAssembly/binaryen) +Further, it is possible to optimize the size of `wasm` code. -Rust Wasm 中有一個關於減少 Wasm 二進位檔大小的章節:[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html) +The Rust Wasm Book has a section about reducing the size of Wasm binaries: +[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html) -- 使用`wasm-pack` 預設在發行版本編譯時優化 `wasm` 程式碼 -- 直接在 wasm 檔案上使用 `wasm-opt` 。 +- using `wasm-pack` which by default optimizes `wasm` code in release builds +- using `wasm-opt` directly on `wasm` files. -```rust +```text wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm ``` -#### 編譯 yew/examples/ 中 最小的例子 +#### Build size of 'minimal' example in yew/examples/ -注意: `wasm-pack` 包含對 Rust 與 wasm 程式碼的優化。而`wasm-bindgen` 只是一個單純的例子,沒有對 `Rust` 做任何優化。 +Note: `wasm-pack` combines optimization for Rust and Wasm code. `wasm-bindgen` is used in this example without any Rust size optimization. | used tool | size | | :-------------------------- | :---- | | wasm-bindgen | 158KB | -| wasm-binggen + wasm-opt -Os | 116KB | -| wasm-pack | 99KB | +| wasm-bindgen + wasm-opt -Os | 116KB | +| wasm-pack | 99 KB | + +## Further reading: + +- [The Rust Book's chapter on smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) +- [Information from the Rust Wasm Book about reducing binary sizes](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size) +- [Documentation about Rust profiles](https://doc.rust-lang.org/cargo/reference/profiles.html) +- [binaryen project](https://github.com/WebAssembly/binaryen) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx new file mode 100644 index 00000000000..c3b734dbb8b --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/portals.mdx @@ -0,0 +1,64 @@ +--- +title: 'Portals' +description: 'Rendering into out-of-tree DOM nodes' +--- + +## What is a portal? + +Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. +`yew::create_portal(child, host)` returns an `Html` value that renders `child` not hierarchically under its parent component, +but as a child of the `host` element. + +## Usage + +Typical uses of portals can include modal dialogs and hovercards, as well as more technical applications +such as controlling the contents of an element's +[`shadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot), appending +stylesheets to the surrounding document's `` and collecting referenced elements inside a +central `` element of an ``. + +Note that `yew::create_portal` is a low-level building block. Libraries should use it to implement +higher-level APIs which can then be consumed by applications. For example, here is a +simple modal dialogue that renders its `children` into an element outside `yew`'s control, +identified by the `id="modal_host"`. + +```rust +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct ModalProps { + #[prop_or_default] + pub children: Html, +} + +#[function_component] +fn Modal(props: &ModalProps) -> Html { + let modal_host = gloo::utils::document() + .get_element_by_id("modal_host") + .expect("Expected to find a #modal_host element"); + + create_portal( + props.children.clone(), + modal_host.into(), + ) +} +``` + +## Event handling + +Events emitted on elements inside portals follow the virtual DOM when bubbling up. That is, +if a portal is rendered as the child of an element, then an event listener on that element +will catch events dispatched from inside the portal, even if the portal renders its contents +in an unrelated location in the actual DOM. + +This allows developers to be oblivious of whether a component they consume, is implemented with +or without portals. Events fired on its children will bubble up regardless. + +A known issue is that events from portals into **closed** shadow roots will be dispatched twice, +once targeting the element inside the shadow root and once targeting the host element itself. Keep +in mind that **open** shadow roots work fine. If this impacts you, feel free to open a bug report +about it. + +## Further reading + +- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md new file mode 100644 index 00000000000..6d3789ff33c --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/server-side-rendering.md @@ -0,0 +1,203 @@ +--- +title: 'Server-side Rendering' +description: 'Render Yew on the server-side.' +--- + +# Server-side Rendering + +By default, Yew components render on the client side. When a viewer +visits a website, the server sends a skeleton HTML file without any actual +content and a WebAssembly bundle to the browser. +Everything is rendered on the client side by the WebAssembly +bundle. This is known as client-side rendering. + +This approach works fine for most websites, with some caveats: + +1. Users will not be able to see anything until the entire WebAssembly + bundle is downloaded and the initial render has been completed. + This can result in a poor experience for users on a slow network. +2. Some search engines do not support dynamically rendered web content and + those who do usually rank dynamic websites lower in the search results. + +To solve these problems, we can render our website on the server side. + +## How it Works + +Yew provides a `ServerRenderer` to render pages on the +server side. + +To render Yew components on the server side, you can create a renderer +with `ServerRenderer::::new()` and call `renderer.render().await` +to render `` into a `String`. + +```rust +use yew::prelude::*; +use yew::ServerRenderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +// we use `flavor = "current_thread"` so this snippet can be tested in CI, +// where tests are run in a WASM environment. You likely want to use +// the (default) `multi_thread` favor as: +// #[tokio::main] +#[tokio::main(flavor = "current_thread")] +async fn no_main() { + let renderer = ServerRenderer::::new(); + + let rendered = renderer.render().await; + + // Prints:
Hello, World!
+ println!("{}", rendered); +} +``` + +## Component Lifecycle + +The recommended way of working with server-side rendering is +function components. + +All hooks other than `use_effect` (and `use_effect_with`) +will function normally until a component successfully renders into `Html` +for the first time. + +:::caution Web APIs are not available! + +Web APIs such as `web_sys` are not available when your component is +rendering on the server side. +Your application will panic if you try to use them. +You should isolate logics that need Web APIs in `use_effect` or +`use_effect_with` as effects are not executed during server-side rendering. + +::: + +:::danger Struct Components + +While it is possible to use Struct Components with server-side rendering, +there are no clear boundaries between client-side safe logic like the +`use_effect` hook for function components and lifecycle events are invoked +in a different order than the client side. + +In addition, Struct Components will continue to accept messages until all of its +children are rendered and `destroy` method is called. Developers need to +make sure no messages possibly passed to components would link to logic +that makes use of Web APIs. + +When designing an application with server-side rendering support, +prefer function components unless you have a good reason not to. + +::: + +## Data Fetching during Server-side Rendering + +Data fetching is one of the difficult points with server-side rendering and hydration. + +Traditionally, when a component renders, it is instantly available +(outputs a virtual DOM to be rendered). This works fine when the +component does not want to fetch any data. But what happens if the component +wants to fetch some data during rendering? + +In the past, there was no mechanism for Yew to detect whether a component is still +fetching data. The data-fetching client is responsible to implement +a solution to detect what is being requested during the initial render and triggers +a second render after requests are fulfilled. The server repeats this process until +no more pending requests are added during a render before returning a response. + +This not only wastes CPU resources by repeatedly rendering components, +but the data client also needs to provide a way to make the data fetched on the +server side available during the hydration process to make sure that the +virtual DOM returned by the initial render is consistent with the +server-side rendered DOM tree which can be hard to implement. + +Yew takes a different approach by trying to solve this issue with ``. + +Suspense is a special component that when used on the client side, provides a +way to show a fallback UI while the component is fetching +data (suspended) and resumes to normal UI when the data fetching completes. + +When the application is rendered on the server side, Yew waits until a +component is no longer suspended before serializing it into the string +buffer. + +During the hydration process, elements within a `` component +remains dehydrated until all of its child components are no longer +suspended. + +With this approach, developers can build a client-agnostic, SSR-ready +application with data fetching with very little effort. + +## SSR Hydration + +Hydration is the process that connects a Yew application to the +server-side generated HTML file. By default, `ServerRender` prints +hydratable HTML string which includes additional information to facilitate hydration. +When the `Renderer::hydrate` method is called, instead of starting rendering from +scratch, Yew will reconcile the Virtual DOM generated by the application +with the HTML string generated by the server renderer. + +:::caution + +To successfully hydrate an HTML representation created by the +`ServerRenderer`, the client must produce a Virtual DOM layout that +exactly matches the one used for SSR including components that do not +contain any elements. If you have any component that is only useful in +one implementation, you may want to use a `PhantomComponent` to fill the +position of the extra component. +::: + +:::warning + +The hydration can only succeed if the real DOM matches the expected DOM +after initial render of the SSR output (static HTML) by browser. If your HTML is +not spec-compliant, the hydration _may_ fail. Browsers may change the DOM structure +of the incorrect HTML, causing the actual DOM to be different from the expected DOM. +For example, [if you have a `
` without a ``, the browser may add a `` to the DOM](https://github.com/yewstack/yew/issues/2684) +::: + +## Component Lifecycle during hydration + +During Hydration, components schedule 2 consecutive renders after it is +created. Any effects are called after the second render completes. +It is important to make sure that the render function of your +component is free of side effects. It should not mutate any states or trigger +additional renders. If your component currently mutates states or triggers +additional renders, move them into a `use_effect` hook. + +It is possible to use Struct Components with server-side rendering in +hydration, the view function will be called +multiple times before the rendered function will be called. +The DOM is considered as not connected until the rendered function is called, +you should prevent any access to rendered nodes +until `rendered()` method is called. + +## Example + +```rust ,ignore +use yew::prelude::*; +use yew::Renderer; + +#[function_component] +fn App() -> Html { + html! {
{"Hello, World!"}
} +} + +fn main() { + let renderer = Renderer::::new(); + + // hydrates everything under body element, removes trailing + // elements (if any). + renderer.hydrate(); +} +``` + +Example: [simple_ssr](https://github.com/yewstack/yew/tree/master/examples/simple_ssr) +Example: [ssr_router](https://github.com/yewstack/yew/tree/master/examples/ssr_router) + +:::caution + +Server-side rendering is currently experimental. If you find a bug, please file +an issue on [GitHub](https://github.com/yewstack/yew/issues/new?assignees=&labels=bug&template=bug_report.md&title=). + +::: diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx index 45ce4763f35..8a24e9b821b 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/callbacks.mdx @@ -1,33 +1,87 @@ --- -description: ComponentLink 與 Callbacks. +title: 'Callbacks' +description: 'ComponentLink 與 Callbacks' --- -# Callbacks +## Callbacks -元件的「link」是一個讓元件註冊 callbacks 並自我更新的機制。 +Callbacks are used to communicate with services, agents, and parent components within Yew. +Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned. -## ComponentLink API +They have an `emit` function that takes their `` type as an argument and converts that to a message expected by its destination. If a callback from a parent is provided in props to a child component, the child can call `emit` on the callback in its `update` lifecycle hook to send a message back to its parent. Closures or Functions provided as props inside the `html!` macro are automatically converted to Callbacks. -### callback +A simple use of a callback might look something like this: -註冊一個 callback 後,當這個 callback 被執行時,會發送一個訊息給元件的更新機制。在生命周期的勾子下,他會呼叫 `send_self` 並將被閉包回傳的訊息帶給他。 +```rust +use yew::{html, Component, Context, Html}; -提供一個 `Fn(IN) -> Vec` 並回傳一個 `Callback` 。 +enum Msg { + Clicked, +} -### send_message +struct Comp; -當現在的迴圈結束後,向元件發送訊息,並且開啟另一個迴圈。 +impl Component for Comp { -### send_message_batch + type Message = Msg; + type Properties = (); -註冊一個 callback,當這個 callback 被執行時,這個 callback 會一次送很多訊息。如果有任何一個訊息導致元件被重新渲染,元件會在所有批次送來的訊息都被處理完後,再重新渲染。 + fn create(_ctx: &Context) -> Self { + Self + } -提供一個 `Fn(IN) -> COMP::Message` 並回傳一個 `Callback` 。 + fn view(&self, ctx: &Context) -> Html { + // highlight-next-line + let onclick = ctx.link().callback(|_| Msg::Clicked); + html! { + // highlight-next-line + + } + } +} +``` -## Callbacks +The function passed to `callback` must always take a parameter. For example, the `onclick` handler requires a function that takes a parameter of type `MouseEvent`. The handler can then decide what kind of message should be sent to the component. This message is scheduled for the next update loop unconditionally. + +If you need a callback that might not need to cause an update, use `batch_callback`. + +```rust +use yew::{events::KeyboardEvent, html, Component, Context, Html}; + +enum Msg { + Submit, +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // highlight-start + let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| { + if event.key() == "Enter" { + Some(Msg::Submit) + } else { + None + } + }); -_(他可能需要一個獨立的短頁來介紹)_ + html! { + + } + // highlight-end + } +} +``` -Callbacks 被用來當作 services 、 agents 與父元件跟 Yew 溝通的方式。他們只是一個被 `Rc` 包裹著的 `Fn`,好讓他們可以被複製。 +## Relevant examples -他們有一個 `emit` 方法,這個方法拿他們的 `` 型別當作參數,並且轉換他作為目的地所期望的訊息。如果一個從父元件來的 callback 被提供作為子元件的屬性,子元件可以在他的 update 生命周期中,呼叫 callback 中的 emit 以傳遞訊息回給父元件。 在 `html!` 巨集中的閉包與方法如果被當作屬性傳遞,會被自動轉為 Callbacks。 +- [Counter](https://github.com/yewstack/yew/tree/master/examples/counter) +- [Timer](https://github.com/yewstack/yew/tree/master/examples/timer) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx new file mode 100644 index 00000000000..eea2038c116 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/hoc.mdx @@ -0,0 +1,82 @@ +--- +title: 'Higher Order Components' +--- + +There are several cases where Struct components do not directly support a feature (ex. Suspense) or require a lot of boilerplate code to use the features (ex. Context). + +In those cases, it is recommended to create function components that are higher-order components. + +## Higher Order Components Definition + +Higher Order Components are components that do not add any new HTML and only wrap some other components to provide extra functionality. + +### Example + +Hook into Context and pass it down to a struct component + +```rust +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + context={(*ctx).clone()}> + + > + } +} + +// highlight-start +#[function_component] +pub fn ThemedButtonHOC() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! {} +} +// highlight-end + +#[derive(Properties, PartialEq)] +pub struct Props { + pub theme: Theme, +} + +struct ThemedButtonStructComponent; + +impl Component for ThemedButtonStructComponent { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let theme = &ctx.props().theme; + html! { + + } + } +} + + + + +``` diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx new file mode 100644 index 00000000000..311fe6ae5a1 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: 'Introduction' +description: 'Components in Yew' +--- + +## What are Components? + +Components are the building blocks of Yew. They manage an internal state and can render elements to the DOM. +Components are created by implementing the `Component` trait for a type. + +## Writing Component's markup + +Yew uses Virtual DOM to render elements to the DOM. The Virtual DOM tree can be constructed by using the +`html!` macro. `html!` uses a syntax which is similar to HTML but is not the same. The rules are also +much stricter. It also provides superpowers like conditional rendering and rendering of lists using iterators. + +:::info +[Learn more about the `html!` macro, how it is used and its syntax](concepts/html/introduction.mdx) +::: + +## Passing data to a component + +Yew components use _props_ to communicate between parents and children. A parent component may pass any data as props to +its children. Props are similar to HTML attributes but any Rust type can be passed as props. + +:::info +[Learn more about the props](advanced-topics/struct-components/properties.mdx) +::: + +:::info +For other than parent/child communication, use [contexts](../../concepts/contexts.mdx) +::: diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx index 295cf053a01..6204fe022a0 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/lifecycle.mdx @@ -1,161 +1,272 @@ --- -description: 元件,以及生命周期鉤子 +title: '生命週期' +description: '元件,以及生命周期鉤子' --- -# 元件 +The `Component` trait has a number of methods which need to be implemented; Yew will call these at different +stages in the lifecycle of a component. -## 什麼是元件? +## Lifecycle -元件是 Yew 的基石。他們管理自己的狀態,可以渲染自己成為 DOM。元件可以透過實作,描述元件生命周期的 `Component` trait 來建立。 - -## 生命周期 - -:::note -`歡迎來貢獻我們的文件:` [Add a diagram of the component lifecycle](https://github.com/yewstack/docs/issues/22) +:::important contribute +`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/yew/issues/1915) ::: -## 生命周期的方法 +## Lifecycle Methods ### Create -當一個元件被建立,他會接收從父元件,也就是 `ComponentLink` ,傳下來的屬性。 這些屬性用來初始化元件的狀態,此外,「link」可以用來註冊回調函式或傳訊息給元件。 - -通常,你的元件 struct 會儲存 props 與 link,就像下面的例子: +When a component is created, it receives properties from its parent component and is stored within +the `Context` that is passed down to the `create` method. The properties can be used to +initialize the component's state and the "link" can be used to register callbacks or send messages to the component. ```rust -pub struct MyComponent { - props: Props, - link: ComponentLink, -} +use yew::{Component, Context, html, Html, Properties}; + +#[derive(PartialEq, Properties)] +pub struct Props; + +pub struct MyComponent; impl Component for MyComponent { + type Message = (); type Properties = Props; - // ... - fn create(props: Self::Properties, link: ComponentLink) -> Self { - MyComponent { props, link } + // highlight-start + fn create(ctx: &Context) -> Self { + MyComponent } + // highlight-end - // ... + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } } ``` ### View -元件會在 `view()` 方法中宣告佈局。Yew 提供 `html!` 巨集來宣告 HTML 合 SVG 的結點,包含他們的監聽事件與子結點。這個巨集扮演像是 React 的 JSX 的角色,但是是使用 Rust 的表達式,而不是 JavaScript 的。 +The `view` method allows you to describe how a component should be rendered to the DOM. Writing +HTML-like code using Rust functions can become quite messy, so Yew provides a macro called `html!` +for declaring HTML and SVG nodes (as well as attaching attributes and event listeners to them) and a +convenient way to render child components. The macro is somewhat similar to React's JSX (the +differences in programming language aside). +One difference is that Yew provides a shorthand syntax for properties, similar to Svelte, where instead of writing `onclick={onclick}`, you can just write `{onclick}`. ```rust +use yew::{Component, Context, html, Html, Properties}; + +enum Msg { + Click, +} + +#[derive(PartialEq, Properties)] +struct Props { + button_text: String, +} + +struct MyComponent; + impl Component for MyComponent { - // ... + type Message = Msg; + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } - fn view(&self) -> Html { - let onclick = self.link.callback(|_| Msg::Click); + // highlight-start + fn view(&self, ctx: &Context) -> Html { + let onclick = ctx.link().callback(|_| Msg::Click); html! { - + } } + // highlight-end } ``` -更多使用細節,請參考 [`html!` 教學](concepts/html/introduction.mdx)。 +For usage details, check out [the `html!` guide](concepts/html/introduction.mdx). ### Rendered -`rendered()` 生命周期的方法會,在 `view()` 處理完並且 Yew 渲染完你的元件之後,與瀏覽器刷新頁面之前,被呼叫。一個元件可能希望實作這個方法,去執行只能在元件被渲染完元素才能做的事情。 你可以透過 `first_render` 變數來確認這個元件是不是第一次被渲染。 +The `rendered` component lifecycle method is called once `view` has been called and Yew has rendered +the results to the DOM, but before the browser refreshes the page. This method is useful when you +want to perform actions that can only be completed after the component has rendered elements. There +is also a parameter called `first_render` which can be used to determine whether this function is +being called on the first render, or instead a subsequent one. ```rust -use stdweb::web::html_element::InputElement; -use stdweb::web::IHtmlElement; -use yew::prelude::*; +use web_sys::HtmlInputElement; +use yew::{ + Component, Context, html, Html, NodeRef, +}; pub struct MyComponent { node_ref: NodeRef, } impl Component for MyComponent { - // ... + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + node_ref: NodeRef::default(), + } + } - fn view(&self) -> Html { + fn view(&self, ctx: &Context) -> Html { html! { } } - fn rendered(&mut self, first_render: bool) { + // highlight-start + fn rendered(&mut self, _ctx: &Context, first_render: bool) { if first_render { - if let Some(input) = self.node_ref.cast::() { + if let Some(input) = self.node_ref.cast::() { input.focus(); } } } + // highlight-end } ``` -:::note -注意,這個生命周期方法,不是一定要被實作,預設的行為是不做任何事情。 +:::tip note +Note that this lifecycle method does not require implementation and will do nothing by default. ::: ### Update -元件是可動態更新且可以註冊接收非同步的訊息。 `update()` 生命周期方法會被每個訊息呼叫。他基於訊息是什麼,來允許元件更新自己,且會決定是否需要重新渲染。 訊息可以被 HTML 元素的監聽器觸發,或被子元件、Agents、Services 或 Futures 傳送。 +Communication with components happens primarily through messages which are handled by the +`update` lifecycle method. This allows the component to update itself +based on what the message was, and determine if it needs to re-render itself. Messages can be sent +by event listeners, child components, Agents, Services, or Futures. -`update()` 應用範例: +Here is an example of what an implementation of `update` could look like: ```rust +use yew::{Component, Context, html, Html}; + +// highlight-start pub enum Msg { SetInputEnabled(bool) } +// highlight-end + +struct MyComponent { + input_enabled: bool, +} impl Component for MyComponent { + // highlight-next-line type Message = Msg; + type Properties = (); - // ... + fn create(_ctx: &Context) -> Self { + Self { + input_enabled: false, + } + } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::SetInputEnabled(enabled) => { - if self.input_enabled != enabled { - self.input_enabled = enabled; - true // 重新渲染 - } else { - false - } - } - } + // highlight-start + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SetInputEnabled(enabled) => { + if self.input_enabled != enabled { + self.input_enabled = enabled; + true // Re-render + } else { + false + } + } + } } + // highlight-end + + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } + } ``` -### Change +### Changed -元件可能會被他的父元件重新渲染。當他被父元件重新渲染時,他會收到新的屬性,然後決定要不要再渲染一次。 這設計是讓父元件透過便於跟子元件溝通。 +Components may be re-rendered by their parents. When this happens, they could receive new properties +and need to re-render. This design facilitates parent-to-child component communication by just +changing the values of a property. There is a default implementation that re-renders the component +when props are changed. -一個簡單的實作方式像: +### Destroy + +After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is +necessary if you need to undertake operations to clean up after earlier actions of a component +before it is destroyed. This method is optional and does nothing by default. + +### Infinite loops + +Infinite loops are possible with Yew's lifecycle methods but are only caused when trying to update +the same component after every render, when that update also requests the component to be rendered. + +A simple example can be seen below: ```rust -impl Component for MyComponent { - // ... +use yew::{Context, Component, Html}; - fn change(&mut self, props: Self::Properties) -> ShouldRender { - if self.props != props { - self.props = props; - true - } else { - false - } +struct Comp; + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + // We are going to always request to re-render on any msg + true + } + + fn view(&self, _ctx: &Context) -> Html { + // For this example it doesn't matter what is rendered + Html::default() + } + + fn rendered(&mut self, ctx: &Context, _first_render: bool) { + // Request that the component is updated with this new msg + ctx.link().send_message(()); } } ``` -### Destroy +Let's run through what happens here: -當元件從 DOM 上被解除掛載,Yew 會呼叫 `destroy()` 生命周期方法以提供任何需要清理的操作。這個方法是不一定要被實作的,預設不會做設任何事。 +1. Component is created using the `create` function. +2. The `view` method is called so Yew knows what to render to the browser DOM. +3. The `rendered` method is called, which schedules an update message using the `Context` link. +4. Yew finishes the post-render phase. +5. Yew checks for scheduled events and sees the update message queue is not empty so works through + the messages. +6. The `update` method is called which returns `true` to indicate something has changed and the + component needs to re-render. +7. Jump back to 2. -## 相關的型別 +You can still schedule updates in the `rendered` method and it is often useful to do so, but +consider how your component will terminate this loop when you do. -`Component` trait 有兩個相關的型別:`Message` 與 `Properties`。 +## Associated Types -```rust +The `Component` trait has two associated types: `Message` and `Properties`. + +```rust ,ignore impl Component for MyComponent { type Message = Msg; type Properties = Props; @@ -164,12 +275,31 @@ impl Component for MyComponent { } ``` -`Message` 負責各式各樣的訊息,他可能被元件處理去觸發各種影響。舉例來說,你可能有一個 `Click` 的訊息,他會觸發 API 請求,或是切換 UI 元件的樣貌。下面是一個常見的實作,在你的元件模組中,創建一個叫作 `Msg` 的 enum,然後把他當作元件裡的 Message 型別。通常 message 會縮寫成 msg。 +The `Message` type is used to send messages to a component after an event has taken place; for +example, you might want to undertake some action when a user clicks a button or scrolls down the +page. Because components tend to have to respond to more than one event, the `Message` type will +normally be an enum, where each variant is an event to be handled. + +When organizing your codebase, it is sensible to include the definition of the `Message` type in the +same module in which your component is defined. You may find it helpful to adopt a consistent naming +convention for message types. One option (though not the only one) is to name the types +`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type +`HomepageMsg`. ```rust enum Msg { Click, + FormInput(String) } ``` -`Properties` 代表要從父員件傳遞到子元件的資訊。這個型別必須實作 `Properties` trait (通常會 deriving 他)並且可以決定某個屬性是必要的屬性,或是可選的屬性。這個型別會在創建元件的時候,或是更新元件的時候被使用到。常見的實作會在你的元件模組中,建立一個叫作 `Props` 的 struct,然後把他當作元件的`Properties` 型別。通常 properties 或縮寫成 props。因為屬性是從父原件被傳下來的,所以應用程式中的根元件的 `Properties` 原則上都是 `()`。如果你希望你的根元件有特定的屬性,可以使用 `App::mount_with_props` 的方法。 +`Properties` represents the information passed to a component from its parent. This type must implement the `Properties` trait \(usually by deriving it\) and can specify whether certain properties are required or optional. This type is used when creating and updating a component. It is common practice to create a struct called `Props` in your component's module and use that as the component's `Properties` type. It is common to shorten "properties" to "props". Since props are handed down from parent components, the root component of your application typically has a `Properties` type of `()`. If you wish to specify properties for your root component, use the `App::mount_with_props` method. + +:::info +[Learn more about properties](./properties) +::: + +## Lifecycle Context + +All component lifecycle methods take a context object. This object provides a reference to the component's scope, which +allows sending messages to a component and the props passed to the component. diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx index 60c7ca69786..30cd33dccd2 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/properties.mdx @@ -1,38 +1,62 @@ --- -description: 父元件與子元件的溝通橋樑 +title: 'Properties' +description: '父元件與子元件的溝通橋樑' --- -# Properties - 屬性讓子元件與父元件可以互相溝通。 +Every component has an associated properties type which describes what is passed down from the parent. +In theory, this can be any type that implements the `Properties` trait, but in practice, there is no +reason for it to be anything but a struct where each field represents a property. ## Derive macro -不要嘗試自己實作 `Properties`,而是用`#[derive(Properties)]`derive 他。 +Instead of implementing the `Properties` trait yourself, you should use `#[derive(Properties)]` to +automatically generate the implementation instead. +Types for which you derive `Properties` must also implement `PartialEq`. + +### Field attributes + +When deriving `Properties`, all fields are required by default. +The following attributes allow you to give your props initial values which will be used unless they are set to another value. + +:::tip +Attributes aren't visible in Rustdoc generated documentation. +The doc strings of your properties should mention whether a prop is optional and if it has a special default value. +::: + +#### `#[prop_or_default]` -### 必填的欄位 +Initialize the prop value with the default value of the field's type using the `Default` trait. -預設所有在 `Properties` struct 裡的欄位都是必填的。當必填的欄位沒有值,而元件在 `html!` 巨集中又被建立,編譯器就會報錯。如果希望欄位是可選的,可以使用 `#[prop_or_default]` 來讓該欄位有預設值。如果希望欄位預設特定值,可以使用 `#[prop_or_else(value)]` ,裡面的 value 就會是這個欄位的預設值。舉例來說,希望預設值是 `true`可以在欄位宣告上面這樣寫 `#[prop_or_else(true)]`. 通常可選的屬性,會用 `Option` ,且預設值為`None`。 +#### `#[prop_or(value)]` -### PartialEq +Use `value` to initialize the prop value. `value` can be any expression that returns the field's type. +For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`. -如果可以,最好在你的屬性上面 derive `PartialEq` 。他可以避免畫面多餘的渲染,更細節的內容請參考,**優化與最佳實例**的區塊。 +#### `#[prop_or_else(function)]` -## 屬性的記憶體與速度的開銷 +Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type. -在 `Component::view`,裡,你可以拿到元件狀態的參考,且用他來建立 `Html` 。但是屬性是有所有權的。這代表著為了建立屬性,並且將他們傳遞給子元件,我們必須取得被 `view` 方法拿走的所有權。 當將參考傳給元件時,可以透過隱式的複製來做到得到所有權。 +## `PartialEq` -這意味著,每個元件,都有從父元件傳遞下來的獨有的狀態複本,且每當你重新渲染一次元件,被重新渲染的元件的所有的子元件的屬性就會被重新複製一次。 +`Properties` require `PartialEq` to be implemented. This is so that they can be compared by Yew to call the `changed` method +only when they change. -代表如果你要在屬性中傳遞*大量*的資料(大於 10 KB 的字串之類的),你可能需要考慮將你的子元件變成一個回傳 `Html` 的方法,讓父元件呼叫,以避免資料被複製。 +## Memory/speed overhead of using Properties -如果你不需要改變傳下去的資料,你可以用 `Rc` 將資料包裝起來,這樣就會只複製參考的指針,而不是資料本身。 +Internally properties are reference counted. This means that only a pointer is passed down the component tree for props. +It saves us from the cost of having to clone the entire props, which might be expensive. -## 範例 +:::tip +Make use of `AttrValue` which is our custom type for attribute values instead of defining them as String or another similar type. +::: + +## Example ```rust -use std::rc::Rc; use yew::Properties; +/// Importing the AttrValue from virtual_dom +use yew::virtual_dom::AttrValue; #[derive(Clone, PartialEq)] pub enum LinkColor { @@ -43,28 +67,79 @@ pub enum LinkColor { Purple, } -impl Default for LinkColor { - fn default() -> Self { - // 除非有指定,否則預設是藍色 - LinkColor::Blue - } +fn create_default_link_color() -> LinkColor { + LinkColor::Blue } -#[derive(Properties, Clone, PartialEq)] +#[derive(Properties, PartialEq)] pub struct LinkProps { - /// 連結必須要有一個目標 - href: String, - /// 如果連結文字很大,複製字串的參考可以減少記憶體的開銷 - /// 但除非效能已經成為嚴重的問題,否則通常不建議這麼做 - text: Rc, - /// 連結的顏色 + /// The link must have a target. + href: AttrValue, + /// Also notice that we are using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] + color: LinkColor, + /// The view function will not specify a size if this is None. #[prop_or_default] + size: Option, + /// When the view function does not specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} +``` + +## Props macro + +The `yew::props!` macro allows you to build properties the same way the `html!` macro does it. + +The macro uses the same syntax as a struct expression except that you cannot use attributes or a base expression (`Foo { ..base }`). +The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). + +```rust +use yew::{props, Properties, virtual_dom::AttrValue}; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +fn create_default_link_color() -> LinkColor { + LinkColor::Blue +} + +#[derive(Properties, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: AttrValue, + /// Also notice that we're using AttrValue instead of String + text: AttrValue, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] color: LinkColor, - /// 如果為 None,那 view 方法將不會指定 size + /// The view function will not specify a size if this is None. #[prop_or_default] size: Option, - /// 當沒有指定 active,預設為 true + /// When the view function doesn't specify active, it defaults to true. #[prop_or(true)] active: bool, } + +impl LinkProps { + /// Notice that this function receives href and text as String + /// We can use `AttrValue::from` to convert it to a `AttrValue` + pub fn new_link_with_size(href: String, text: String, size: u32) -> Self { + // highlight-start + props! {LinkProps { + href: AttrValue::from(href), + text: AttrValue::from(text), + size, + }} + // highlight-end + } +} ``` diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx index ba1342891dc..e0c4082c1c6 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/refs.mdx @@ -1,26 +1,53 @@ --- -description: 外帶 DOM 的存取 +title: 'Refs' +description: '外帶 DOM 的存取' --- -# Refs - -## Refs - `ref` 關鍵字可以被使用在任何 HTML 的元素或是元件,用來得到那個物件附加的 DOM `Element`。這個可以在 view 生命周期方法之外,改變 DOM。 對於要存取 canvas 元素,或滾動到頁面不同的區塊,很有幫助。 +For example, using a `NodeRef` in a component's `rendered` method allows you to make draw calls to +a canvas element after it has been rendered from `view`. 語法可以這樣使用: ```rust -// 建立 -self.node_ref = NodeRef::default(); +use web_sys::Element; +use yew::{html, Component, Context, Html, NodeRef}; -// 在 view 裡 -html! { -
+struct Comp { + node_ref: NodeRef, } -// 更新 -let has_attributes = self.node_ref.cast::().unwrap().has_attributes(); +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + // highlight-next-line + node_ref: NodeRef::default(), + } + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + // highlight-next-line +
+ } + } + + fn rendered(&mut self, _ctx: &Context, _first_render: bool) { + // highlight-start + let has_attributes = self.node_ref + .cast::() + .unwrap() + .has_attributes(); + // highlight-end + } +} ``` + +## Relevant examples + +- [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx new file mode 100644 index 00000000000..db4f5974fa7 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/advanced-topics/struct-components/scope.mdx @@ -0,0 +1,81 @@ +--- +title: 'Scope' +description: "Component's Scope" +--- + +## Component's `Scope<_>` API + +The component "`Scope`" is the mechanism through which components can create callbacks and update themselves +using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. + +### `send_message` + +Sends a message to the component. +Messages are handled by the `update` method which determines whether the component should re-render. + +### `send_message_batch` + +Sends multiple messages to the component at the same time. +This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, +the component will re-render after all messages in the batch have been processed. + +If the given vector is empty, this function does nothing. + +### `callback` + +Create a callback that will send a message to the component when it is executed. +Under the hood, it will call `send_message` with the message returned by the provided closure. + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Text(String), +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback that accepts some text and sends it + // to the component as the `Msg::Text` message variant. + // highlight-next-line + let cb = ctx.link().callback(|text: String| Msg::Text(text)); + + // The previous line is needlessly verbose to make it clearer. + // It can be simplified it to this: + // highlight-next-line + let cb = ctx.link().callback(Msg::Text); + + // Will send `Msg::Text("Hello World!")` to the component. + // highlight-next-line + cb.emit("Hello World!".to_owned()); + + html! { + // html here + } + } +} +``` + +### `batch_callback` + +Create a callback that will send a batch of messages to the component when it is executed. +The difference to `callback` is that the closure passed to this method doesn't have to return a message. +Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. + +`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. + +`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. +This can be used in cases where, depending on the situation, an update isn't required. + +This is achieved using the `SendAsMessage` trait which is only implemented for these types. +You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx new file mode 100644 index 00000000000..afaced2c086 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/contexts.mdx @@ -0,0 +1,196 @@ +--- +title: 'Contexts' +sidebar_label: Contexts +description: 'Using contexts to pass deeply nested data' +--- + +Usually, data is passed from a parent component to a child component via props. +But passing props can become verbose and annoying if you have to pass them through many components in the middle, +or if many components in your app need the same information. Context solves this problem by allowing a +parent component to make data available to _any_ component in the tree below it, no matter how deep, +without having to pass it down with props. + +## The problem with props: "Prop Drilling" + +Passing [props](./function-components/properties.mdx) is a great way to pass data directly from a parent to a child. +They become cumbersome to pass down through deeply nested component trees or when multiple components share the same data. +A common solution to data sharing is lifting the data to a common ancestor and making the children take it as props. +However, this can lead to cases where the prop has to go through multiple components to reach the component that needs it. +This situation is called "Prop Drilling". + +Consider the following example which passes down the theme using props: + +```rust +use yew::{html, Component, Context, Html, Properties, function_component}; + +#[derive(Clone, PartialEq)] +pub struct Theme { + foreground: String, + background: String, +} + +#[derive(PartialEq, Properties)] +pub struct NavbarProps { + theme: Theme, +} + +#[function_component] +fn Navbar(props: &NavbarProps) -> Html { + html! { +
+ + { "App title" } + + + { "Somewhere" } + +
+ } +} + +#[derive(PartialEq, Properties)] +pub struct ThemeProps { + theme: Theme, + children: Html, +} + +#[function_component] +fn Title(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +#[function_component] +fn NavButton(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +/// App root +#[function_component] +fn App() -> Html { + let theme = Theme { + foreground: "yellow".to_owned(), + background: "pink".to_owned(), + }; + + html! { + + } +} +``` + +We "drill" the theme prop through `Navbar` so that it can reach `Title` and `NavButton`. +It would be nice if `Title` and `NavButton`, the components that need access to the theme, can just access the theme +without having to pass it to them as a prop. Contexts solve this problem by allowing a parent to pass data, theme in this case, +to its children. + +## Using Contexts + +### Step 1: Providing the context + +A context provider is required to consume the context. `ContextProvider`, where `T` is the context struct used as the provider. +`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them. +The children are re-rendered when the context changes. A struct is used to define what data is to be passed. The `ContextProvider` can be used as: + +```rust +use yew::prelude::*; + + +/// App theme +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +/// Main component +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + // `ctx` is type `Rc>` while we need `Theme` + // so we deref it. + // It derefs to `&Theme`, hence the clone + context={(*ctx).clone()}> + // Every child here and their children will have access to this context. + + > + } +} + +/// The toolbar. +/// This component has access to the context +#[function_component] +pub fn Toolbar() -> Html { + html! { +
+ +
+ } +} + +/// Button placed in `Toolbar`. +/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access +/// to the context. +#[function_component] +pub fn ThemedButton() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! { + + } +} +``` + +### Step 2: Consuming context + +#### Function components + +`use_context` hook is used to consume contexts in function components. +See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use_context.html) to learn more. + +#### Struct components + +We have 2 options to consume contexts in struct components: + +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) + +## Use cases + +Generally, if some data is needed by distant components in different parts of the tree, context will likely help you. +Here are some examples of such cases: + +- **Theming**: You can put a context at the top of the app that holds your app theme and use it to adjust the visual appearance, as shown in the above example. +- **Current user account**: In many cases, components need to know the currently logged-in user. You can use a context to provide the current user object to the components. + +### Considerations to make before using contexts + +Contexts are very easy to use. That makes them very easy to misuse/overuse. +Just because you can use a context to share props to components multiple levels deep, does not mean that you should. + +For example, you may be able to extract a component and pass that component as a child to another component. For example, +you may have a `Layout` component that takes `articles` as a prop and passes it down to `ArticleList` component. +You should refactor the `Layout` component to take children as props and display ` `. + +## Mutating the context value of a child + +Because of Rust's ownership rules, a context cannot have a method that takes `&mut self` that can be called by children. +To mutate a context's value, we must combine it with a reducer. This is done by using the +[`use_reducer`](https://yew-rs-api.web.app/next/yew/functional/fn.use_reducer.html) hook. + +The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) demonstrates mutable contexts +with the help of contexts + +## Further reading + +- The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx new file mode 100644 index 00000000000..028b7f5b7be --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/introduction.mdx @@ -0,0 +1,76 @@ +--- +title: 'Function Components' +slug: /concepts/function-components +--- + +Let's revisit this previous statement: + +> Yew centrally operates on the idea of keeping everything that a reusable piece of +> UI may need in one place - rust files. + +We will refine this statement, by introducing the concept that will define the logic and +presentation behavior of an application: "components". + +## What are Components? + +Components are the building blocks of Yew. + +They: + +- Take arguments in form of [Props](./properties.mdx) +- Can have their own state +- Compute pieces of HTML visible to the user (DOM) + +## Two flavors of Yew Components + +You are currently reading about function components - the recommended way to write components +when starting with Yew and when writing simple presentation logic. + +There is a more advanced, but less accessible, way to write components - [Struct components](advanced-topics/struct-components/introduction.mdx). +They allow very detailed control, though you will not need that level of detail most of the time. + +## Creating function components + +To create a function component add the `#[function_component]` attribute to a function. +By convention, the function is named in PascalCase, like all components, to contrast its +use to normal html elements inside the `html!` macro. + +```rust +use yew::{function_component, html, Html}; + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// Then somewhere else you can use the component inside `html!` +#[function_component] +fn App() -> Html { + html! { } +} +``` + +## What happens to components + +When rendering, Yew will build a virtual tree of these components. +It will call the view function of each (function) component to compute a virtual version (VDOM) of the DOM +that you as the library user see as the `Html` type. +For the previous example, this would look like this: + +```xhtml + + +

"Hello world"

+ +
+``` + +When an update is necessary, Yew will again call the view function and reconcile the new virtual DOM with its +previous version and only propagate the new/changed/necessary parts to the actual DOM. +This is what we call **rendering**. + +:::note + +Behind the scenes, `Html` is just an alias for `VNode` - a virtual node. + +::: diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx new file mode 100644 index 00000000000..5d9d1b3f319 --- /dev/null +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.21/concepts/function-components/properties.mdx @@ -0,0 +1,291 @@ +--- +title: '屬性 (Properties)' +description: '父子元件通訊' +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +:::note + +屬性 (Properties) 通常被簡稱為 "Props"。 + +::: + +屬性 (Properties) 本質上是 Yew 可以監視的元件參數。 + +一個型別必須先實作 `Properties` 特徵才能被用作元件的屬性。 + +## 響應性 + +Yew 在重新渲染時會在協調虛擬 DOM 時檢查 props 是否已更改,以了解是否需要重新渲染嵌套元件。這樣,Yew 可以被認為是一個非常響應式的框架,因為來自父元件的更改總是會向下傳播,視圖永遠不會與來自 props/狀態的資料不同步。 + +:::tip + +如果您還沒有完成 [教程](../../tutorial),請嘗試一下並自己測試這種響應性! + +::: + +## 派生巨集 + +Yew 提供了一個派生巨集來輕鬆地在結構體上實作 `Properties` 特徵。 + +派生 `Properties` 的型別還必須實作 `PartialEq`,以便 Yew 可以進行資料比較。 + +```rust +use yew::Properties; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} +``` + +## 在函數元件中使用 + +屬性 `#[function_component]` 允許可選地在函數參數中接收 Props。要提供它們,它們透過 `html!` 巨集中的屬性分配。 + + + + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +} + +// 然後提供屬性 +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +```rust +use yew::{function_component, html, Html}; + + + + + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// 沒有要提供的屬性 +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +## 派生巨集欄位屬性 + +派生 `Properties` 時,預設情況下所有欄位都是必需的。 +以下屬性允許您為屬性提供預設值,當父元件沒有設定它們時將使用這些預設值。 + +:::tip +屬性在 Rustdoc 生成的文件中不可見。您的屬性的文件字串應該提及屬性是否是可選的以及是否有特殊的預設值。 +::: + + + + +使用 `Default` 特徵用欄位型別的預設值初始化屬性值。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_default] + // highlight-end + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + if props.is_loading.clone() { + html! { "Loading" } + } else { + html! { "Hello world" } + } +} + +// 然後像這樣使用預設值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆蓋預設值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +使用 `value` 來初始化屬性值。`value` 可以是任何返回欄位型別的表達式。 +例如,要將布林屬性預設為 `true`,請使用屬性 `#[prop_or(true)]`。表達式在建構屬性時被評估,並且沒有給出明確值時應用。 + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or("Bob".to_string())] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// 然後像這樣使用預設值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆蓋預設值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +呼叫 `function` 來初始化屬性值。`function` 應該有簽名 `FnMut() -> T`,其中 `T` 是欄位型別。當沒有為該屬性給出明確值時,該函數被呼叫。 + +```rust +use yew::{function_component, html, Html, Properties}; + +fn create_default_name() -> String { + "Bob".to_string() +} + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_else(create_default_name)] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// 然後像這樣使用預設值 +#[function_component] +fn Case1() -> Html { + html! {} +} +// 或者不覆蓋預設值 +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +## 使用 Properties 的記憶體/速度開銷 + +內部屬性是引用計數的。這意味著只有一個共享指標會沿著元件樹向下傳遞給 props。這節省了我們不得不複製整個 props 的成本,這可能很昂貴。 + +:::tip +使用 `AttrValue`,這是我們用於屬性值的自訂型別,而不是將它們定義為 String 或其他類似型別。 +::: + +## Props 巨集 + +`yew::props!` 巨集允許您以與 `html!` 巨集相同的方式建構屬性。 + +巨集使用與結構體表達式相同的語法,除了您不能使用屬性或基本表達式(`Foo { ..base }`)。型別路徑可以直接指向 props(`path::to::Props`)或指向元件的關聯屬性(`MyComp::Properties`)。 + +```rust +use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue}; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or(AttrValue::from("Bob"))] + pub name: AttrValue, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +#[function_component] +fn App() -> Html { + // highlight-start + let pre_made_props = props! { + Props {} // 注意我們不需要指定 name 屬性 + }; + // highlight-end + html! {} +} +``` + +## 評估順序 + +Props 按指定的順序進行評估,如以下示例所示: + +```rust +#[derive(yew::Properties, PartialEq)] +struct Props { first: usize, second: usize, last: usize } + +fn main() { + let mut g = 1..=3; + let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() }); + + assert_eq!(props.first, 1); + assert_eq!(props.second, 2); + assert_eq!(props.last, 3); +} +``` + +## 反模式 + +雖然幾乎任何 Rust 型別都可以作為屬性傳遞,但有一些應該避免的反模式。這些包括但不限於: + +1. 使用 `String` 型別而不是 `AttrValue`。
+ **為什麼不好?** `String` 複製成本很高。當屬性值與鉤子和回呼一起使用時,通常需要複製。`AttrValue` 是一個引用計數的字串 (`Rc`) 或一個 `&'static str`,因此非常便宜複製。
+ **注意**:`AttrValue` 內部是來自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看該包以了解更多資訊。 +2. 使用內部可變性。
+ **為什麼不好?** 內部可變性(例如 `RefCell`、`Mutex` 等)應該 _通常_ 避免使用。它可能會導致重新渲染問題(Yew 不知道狀態何時發生了變化),因此您可能需要手動強制重新渲染。就像所有事物一樣,它有其用武之地。請謹慎使用。 +3. 使用 `Vec` 型別而不是 `IArray`。
+ **為什麼不好?** `Vec`,就像 `String` 一樣,複製成本也很高。`IArray` 是一個引用計數的切片 (`Rc<[T]>`) 或一個 `&'static [T]`,因此非常便宜複製。
+ **注意**:`IArray` 可以從 [implicit-clone](https://crates.io/crates/implicit-clone) 匯入。查看該包以了解更多資訊。 +4. 您發覺可能的新內容。您是否遇到了一個希望早點了解清楚的邊緣情況?請隨時建立一個問題或向本文檔提供修復的 PR。 + +## yew-autoprops + +[yew-autoprops](https://crates.io/crates/yew-autoprops) 是一個實驗性包,可讓您根據函數的參數動態建立 Props 結構體。如果屬性結構體永遠不會被重複使用,這可能會很有用。 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx index 9d6a9344853..61c67da5e15 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/advanced-topics/immutable.mdx @@ -13,6 +13,16 @@ description: 'Yew 的不可變資料結構' 不可變類型非常適合保存屬性的值,因為它們可以在從組件傳遞到組件時以很低的成本克隆。 +## 常見的不可變型別 + +Yew 推薦使用來自 `implicit-clone` crate 的以下不可變型別: + +- `IString`(在 Yew 中別名為 `AttrValue`)- 用於字串而不是 `String` +- `IArray` - 用於陣列/向量而不是 `Vec` +- `IMap` - 用於映射而不是 `HashMap` + +這些型別是引用計數(`Rc`)或靜態引用,使它們的克隆成本非常低。 + ## 進一步閱讀 - [不可變範例](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx index 2cccc51dd36..0a3cab9be88 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/contexts.mdx @@ -156,7 +156,7 @@ pub fn ThemedButton() -> Html { 我們有兩種選擇在結構體組件中使用上下文: -- [高階元件](../advanced-topics/struct-components/hoc.mdx):高階函數元件將使用上下文並將資料傳遞給需要它的結構體元件。 +- [高階元件](../advanced-topics/struct-components/hoc):高階函數元件將使用上下文並將資料傳遞給需要它的結構體元件。 - 直接在結構體組件中使用上下文。請參閱 [結構體組件作​​為消費者的範例](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## 使用場景 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx index c73aa80ef1f..de2dd008ce3 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/function-components/properties.mdx @@ -329,8 +329,8 @@ fn main() { **注意**:`AttrValue` 在內部是來自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看該包以了解更多資訊。 2. 使用內部可變性。
**為什麼不好? ** 內部可變性(例如 `RefCell`、`Mutex` 等)應該 _通常_ 避免使用。它可能會導致重新渲染問題(Yew 不知道狀態何時發生了變化),因此您可能需要手動強制重新渲染。就像所有事物一樣,它有其用武之地。請謹慎使用。 -3. 使用 `Vec` 型別而不是 `IArray`。
- **為什麼不好? ** `Vec`,就像 `String` 一樣,克隆成本也很高。 `IArray` 是一個引用計數的切片 (`Rc`) 或一個 `&'static [T]`,因此非常便宜克隆。
+3. 使用 `Vec` 型別而不是 `IArray`。
+ **為什麼不好? ** `Vec`,就像 `String` 一樣,克隆成本也很高。 `IArray` 是一個引用計數的切片 (`Rc<[T]>`) 或一個 `&'static [T]`,因此非常便宜克隆。
**注意**:`IArray` 可以從 [implicit-clone](https://crates.io/crates/implicit-clone) 匯入。查看該包以了解更多資訊。 4. 您發覺可能的新內容。您是否遇到了一個希望早點了解清楚的邊緣情況?請隨時建立一個問題或向本文檔提供修復的 PR。 diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx index 95ccf2e6d68..09a05190bad 100644 --- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx +++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/version-0.22/concepts/html/events.mdx @@ -33,7 +33,7 @@ html! { ## 事件捕獲 {#event-bubbling} -Yew 調度的事件遵循虛擬 DOM 層次結構,向上冒泡到監聽器。目前,僅支援監聽器的冒泡階段。請注意,虛擬 DOM 層次結構通常(但並非總是)與實際 DOM 層次結構相同。在處理[傳送門](../../advanced-topics/portals.mdx)和其他更高級技術時,這一區別很重要。對於良好實現的元件,直覺應該是事件從子元件冒泡到父元件。這樣,您在 `html!` 中所寫的層次結構就是事件處理程序觀察到的層次結構。 +Yew 調度的事件遵循虛擬 DOM 層次結構,向上冒泡到監聽器。目前,僅支援監聽器的冒泡階段。請注意,虛擬 DOM 層次結構通常(但並非總是)與實際 DOM 層次結構相同。在處理[傳送門](../../advanced-topics/portals)和其他更高級技術時,這一區別很重要。對於良好實現的元件,直覺應該是事件從子元件冒泡到父元件。這樣,您在 `html!` 中所寫的層次結構就是事件處理程序觀察到的層次結構。 如果您不想要事件冒泡,可以透過呼叫 diff --git a/website/versioned_docs/version-0.20/concepts/contexts.mdx b/website/versioned_docs/version-0.20/concepts/contexts.mdx index 02e105cc3e4..90c56fb0099 100644 --- a/website/versioned_docs/version-0.20/concepts/contexts.mdx +++ b/website/versioned_docs/version-0.20/concepts/contexts.mdx @@ -140,7 +140,7 @@ See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use We have 2 options to consume contexts in struct components: -- [Higher Order Components](../advanced-topics/struct-components/hoc.mdx): A higher order function component will consume the context and pass the data to the struct component which requires it. +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher order function component will consume the context and pass the data to the struct component which requires it. - Consume context directly in struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/yew-v0.20.0/examples/contexts/src/struct_component_subscriber.rs) ## Use cases diff --git a/website/versioned_docs/version-0.20/concepts/html/events.mdx b/website/versioned_docs/version-0.20/concepts/html/events.mdx index 8820f1210ac..84d924f4bd7 100644 --- a/website/versioned_docs/version-0.20/concepts/html/events.mdx +++ b/website/versioned_docs/version-0.20/concepts/html/events.mdx @@ -40,7 +40,7 @@ listens for `click` events. See the end of this page for a [full list of availab Events dispatched by Yew follow the virtual DOM hierarchy when bubbling up to listeners. Currently, only the bubbling phase is supported for listeners. Note that the virtual DOM hierarchy is most often, but not always, identical to the actual -DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals.mdx) and other +DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals) and other more advanced techniques. The intuition for well implemented components should be that events bubble from children to parents, so that the hierarchy in your coded `html!` is the one observed by event handlers. diff --git a/website/versioned_docs/version-0.21/advanced-topics/immutable.mdx b/website/versioned_docs/version-0.21/advanced-topics/immutable.mdx index d2cb0d97122..4fcb17e2c4b 100644 --- a/website/versioned_docs/version-0.21/advanced-topics/immutable.mdx +++ b/website/versioned_docs/version-0.21/advanced-topics/immutable.mdx @@ -18,6 +18,16 @@ 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. +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + ## Further reading - [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/versioned_docs/version-0.21/concepts/contexts.mdx b/website/versioned_docs/version-0.21/concepts/contexts.mdx index 6a33c134827..afaced2c086 100644 --- a/website/versioned_docs/version-0.21/concepts/contexts.mdx +++ b/website/versioned_docs/version-0.21/concepts/contexts.mdx @@ -162,7 +162,7 @@ See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use We have 2 options to consume contexts in struct components: -- [Higher Order Components](../advanced-topics/struct-components/hoc.mdx): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. - Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## Use cases diff --git a/website/versioned_docs/version-0.21/concepts/function-components/properties.mdx b/website/versioned_docs/version-0.21/concepts/function-components/properties.mdx index d331425ec92..627c193da04 100644 --- a/website/versioned_docs/version-0.21/concepts/function-components/properties.mdx +++ b/website/versioned_docs/version-0.21/concepts/function-components/properties.mdx @@ -296,7 +296,12 @@ These include, but are not limited to: **Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should _generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed) so you may have to manually force a render. Like all things, it has its place. Use it with caution. -3. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue +3. Using `Vec` type instead of `IArray`.
+ **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either + a reference-counted slice (`Rc<[T]>`) or a `&'static [T]`, thus very cheap to clone.
+ **Note**: `IArray` can be imported from [implicit-clone](https://crates.io/crates/implicit-clone) + See that crate to learn more. +4. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue or PR a fix to this documentation. ## yew-autoprops diff --git a/website/versioned_docs/version-0.21/concepts/html/events.mdx b/website/versioned_docs/version-0.21/concepts/html/events.mdx index 4b6df5c575a..25825bdbd0c 100644 --- a/website/versioned_docs/version-0.21/concepts/html/events.mdx +++ b/website/versioned_docs/version-0.21/concepts/html/events.mdx @@ -40,7 +40,7 @@ listens for `click` events. See the end of this page for a [full list of availab Events dispatched by Yew follow the virtual DOM hierarchy when bubbling up to listeners. Currently, only the bubbling phase is supported for listeners. Note that the virtual DOM hierarchy is most often, but not always, identical to the actual -DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals.mdx) and other +DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals) and other more advanced techniques. The intuition for well-implemented components should be that events bubble from children to parents. In this way the hierarchy in your coded `html!` is the one observed by event handlers. diff --git a/website/versioned_docs/version-0.22/advanced-topics/immutable.mdx b/website/versioned_docs/version-0.22/advanced-topics/immutable.mdx index d2cb0d97122..4fcb17e2c4b 100644 --- a/website/versioned_docs/version-0.22/advanced-topics/immutable.mdx +++ b/website/versioned_docs/version-0.22/advanced-topics/immutable.mdx @@ -18,6 +18,16 @@ 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. +## Common Immutable Types + +Yew recommends using the following immutable types from the `implicit-clone` crate: + +- `IString` (aliased as `AttrValue` in Yew) - for strings instead of `String` +- `IArray` - for arrays/vectors instead of `Vec` +- `IMap` - for maps instead of `HashMap` + +These types are either reference-counted (`Rc`) or static references, making them very cheap to clone. + ## Further reading - [Immutable example](https://github.com/yewstack/yew/tree/master/examples/immutable) diff --git a/website/versioned_docs/version-0.22/concepts/contexts.mdx b/website/versioned_docs/version-0.22/concepts/contexts.mdx index 6a33c134827..afaced2c086 100644 --- a/website/versioned_docs/version-0.22/concepts/contexts.mdx +++ b/website/versioned_docs/version-0.22/concepts/contexts.mdx @@ -162,7 +162,7 @@ See [docs for use_context](https://yew-rs-api.web.app/next/yew/functional/fn.use We have 2 options to consume contexts in struct components: -- [Higher Order Components](../advanced-topics/struct-components/hoc.mdx): A higher-order function component will consume the context and pass the data to the struct component which requires it. +- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher-order function component will consume the context and pass the data to the struct component which requires it. - Consume context directly in the struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs) ## Use cases diff --git a/website/versioned_docs/version-0.22/concepts/function-components/properties.mdx b/website/versioned_docs/version-0.22/concepts/function-components/properties.mdx index 02c3f469b56..7eb40cee47f 100644 --- a/website/versioned_docs/version-0.22/concepts/function-components/properties.mdx +++ b/website/versioned_docs/version-0.22/concepts/function-components/properties.mdx @@ -343,9 +343,9 @@ These include, but are not limited to: **Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should _generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed) so you may have to manually force a render. Like all things, it has its place. Use it with caution. -3. Using `Vec` type instead of `IArray`.
- **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either - a reference-counted slice (`Rc`) or a `&'static [T]`, thus very cheap to clone.
+3. Using `Vec` type instead of `IArray`.
+ **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either + a reference-counted slice (`Rc<[T]>`) or a `&'static [T]`, thus very cheap to clone.
**Note**: `IArray` can be imported from [implicit-clone](https://crates.io/crates/implicit-clone) See that crate to learn more. 4. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue diff --git a/website/versioned_docs/version-0.22/concepts/html/events.mdx b/website/versioned_docs/version-0.22/concepts/html/events.mdx index da4dcd34b94..383ac99594c 100644 --- a/website/versioned_docs/version-0.22/concepts/html/events.mdx +++ b/website/versioned_docs/version-0.22/concepts/html/events.mdx @@ -40,7 +40,7 @@ listens for `click` events. See the end of this page for a [full list of availab Events dispatched by Yew follow the virtual DOM hierarchy when bubbling up to listeners. Currently, only the bubbling phase is supported for listeners. Note that the virtual DOM hierarchy is most often, but not always, identical to the actual -DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals.mdx) and other +DOM hierarchy. The distinction is important when working with [portals](../../advanced-topics/portals) and other more advanced techniques. The intuition for well-implemented components should be that events bubble from children to parents. In this way the hierarchy in your coded `html!` is the one observed by event handlers.