diff --git a/examples/futures/src/markdown.rs b/examples/futures/src/markdown.rs index 74bb30b7bbd..4bbbc0bedac 100644 --- a/examples/futures/src/markdown.rs +++ b/examples/futures/src/markdown.rs @@ -54,12 +54,10 @@ pub fn render_markdown(src: &str) -> Html { top = pre; } else if let Tag::Table(aligns) = tag { if let Some(top_children) = top.children_mut() { - for r in top_children.children_mut().iter_mut() { + for r in top_children.iter_mut() { if let VNode::VTag(ref mut vtag) = r { if let Some(vtag_children) = vtag.children_mut() { - for (i, c) in - vtag_children.children_mut().iter_mut().enumerate() - { + for (i, c) in vtag_children.iter_mut().enumerate() { if let VNode::VTag(ref mut vtag) = c { match aligns[i] { Alignment::None => {} @@ -75,7 +73,7 @@ pub fn render_markdown(src: &str) -> Html { } } else if let Tag::TableHead = tag { if let Some(top_children) = top.children_mut() { - for c in top_children.children_mut().iter_mut() { + for c in top_children.iter_mut() { if let VNode::VTag(ref mut vtag) = c { // TODO // vtag.tag = "th".into(); diff --git a/packages/yew/src/dom_bundle/blist.rs b/packages/yew/src/dom_bundle/blist.rs index 1d41e5dd0a2..0f55824fe00 100644 --- a/packages/yew/src/dom_bundle/blist.rs +++ b/packages/yew/src/dom_bundle/blist.rs @@ -444,6 +444,7 @@ impl Reconcilable for VList { self.add_child(VText::new("").into()); } + let fully_keyed = self.fully_keyed(); let lefts = self.children; let rights = &mut blist.rev_children; test_log!("lefts: {:?}", lefts); @@ -452,12 +453,12 @@ impl Reconcilable for VList { if let Some(additional) = lefts.len().checked_sub(rights.len()) { rights.reserve_exact(additional); } - let first = if self.fully_keyed && blist.fully_keyed { + let first = if fully_keyed && blist.fully_keyed { BList::apply_keyed(root, parent_scope, parent, next_sibling, lefts, rights) } else { BList::apply_unkeyed(root, parent_scope, parent, next_sibling, lefts, rights) }; - blist.fully_keyed = self.fully_keyed; + blist.fully_keyed = fully_keyed; blist.key = self.key; test_log!("result: {:?}", rights); first @@ -478,9 +479,11 @@ mod feat_hydration { fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { let node_ref = NodeRef::default(); - let mut children = Vec::with_capacity(self.children.len()); + let fully_keyed = self.fully_keyed(); + let vchildren = self.children; + let mut children = Vec::with_capacity(vchildren.len()); - for (index, child) in self.children.into_iter().enumerate() { + for (index, child) in vchildren.into_iter().enumerate() { let (child_node_ref, child) = child.hydrate(root, parent_scope, parent, fragment); if index == 0 { @@ -496,7 +499,7 @@ mod feat_hydration { node_ref, BList { rev_children: children, - fully_keyed: self.fully_keyed, + fully_keyed, key: self.key, }, ) diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 4717246e72c..468b032d6d3 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -3,18 +3,31 @@ use std::ops::{Deref, DerefMut}; use super::{Key, VNode}; +#[derive(Clone, Copy, Debug, PartialEq)] +enum FullyKeyedState { + KnownFullyKeyed, + KnownMissingKeys, + Unknown, +} + /// This struct represents a fragment of the Virtual DOM tree. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct VList { /// The list of child [VNode]s pub(crate) children: Vec, /// All [VNode]s in the VList have keys - pub(crate) fully_keyed: bool, + fully_keyed: FullyKeyedState, pub key: Option, } +impl PartialEq for VList { + fn eq(&self, other: &Self) -> bool { + self.children == other.children && self.key == other.key + } +} + impl Default for VList { fn default() -> Self { Self::new() @@ -29,42 +42,10 @@ impl Deref for VList { } } -/// Mutable children of a [VList]. -/// -/// This struct has a `DerefMut` implementations into [`Vec`](std::vec::Vec). -/// Prefer to use immutable access, since this re-checks if all nodes have keys when dropped. -pub struct ChildrenMut<'a> { - children: &'a mut Vec, - fully_keyed: &'a mut bool, -} - -impl<'a> Deref for ChildrenMut<'a> { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - self.children - } -} - -impl<'a> DerefMut for ChildrenMut<'a> { +impl DerefMut for VList { fn deref_mut(&mut self) -> &mut Self::Target { - *self.fully_keyed = false; - self.children - } -} - -impl<'a> Drop for ChildrenMut<'a> { - fn drop(&mut self) { - if !*self.fully_keyed { - // Caller might have changed the keys of the VList or add unkeyed children. - *self.fully_keyed = self.children.iter().all(|ch| ch.has_key()); - } - } -} - -impl<'a> std::fmt::Debug for ChildrenMut<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ChildrenMut").field(&self.children).finish() + self.fully_keyed = FullyKeyedState::Unknown; + &mut self.children } } @@ -74,23 +55,29 @@ impl VList { Self { children: Vec::new(), key: None, - fully_keyed: true, + fully_keyed: FullyKeyedState::KnownFullyKeyed, } } /// Creates a new [VList] instance with children. pub fn with_children(children: Vec, key: Option) -> Self { - VList { - fully_keyed: children.iter().all(|ch| ch.has_key()), + let mut vlist = VList { + fully_keyed: FullyKeyedState::Unknown, children, key, - } + }; + vlist.fully_keyed = if vlist.fully_keyed() { + FullyKeyedState::KnownFullyKeyed + } else { + FullyKeyedState::KnownMissingKeys + }; + vlist } /// Add [VNode] child. pub fn add_child(&mut self, child: VNode) { - if self.fully_keyed && !child.has_key() { - self.fully_keyed = false; + if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() { + self.fully_keyed = FullyKeyedState::KnownMissingKeys; } self.children.push(child); } @@ -105,11 +92,23 @@ impl VList { } } - /// Get a mutable list of children in this vlist. - pub fn children_mut(&mut self) -> ChildrenMut<'_> { - ChildrenMut { - children: &mut self.children, - fully_keyed: &mut self.fully_keyed, + /// Recheck, if the all the children have keys. + /// + /// You can run this, after modifying the child list through the [DerefMut] implementation of + /// [VList], to precompute an internally kept flag, which speeds up reconciliation later. + pub fn recheck_fully_keyed(&mut self) { + self.fully_keyed = if self.fully_keyed() { + FullyKeyedState::KnownFullyKeyed + } else { + FullyKeyedState::KnownMissingKeys + }; + } + + pub(crate) fn fully_keyed(&self) -> bool { + match self.fully_keyed { + FullyKeyedState::KnownFullyKeyed => true, + FullyKeyedState::KnownMissingKeys => false, + FullyKeyedState::Unknown => self.children.iter().all(|c| c.has_key()), } } } @@ -122,25 +121,36 @@ mod test { #[test] fn mutably_change_children() { let mut vlist = VList::new(); - assert!(vlist.fully_keyed, "should start fully keyed"); + assert_eq!( + vlist.fully_keyed, + FullyKeyedState::KnownFullyKeyed, + "should start fully keyed" + ); // add a child that is keyed - let mut children = vlist.children_mut(); - children.push(VNode::VTag({ + vlist.add_child(VNode::VTag({ let mut tag = VTag::new("a"); tag.key = Some(42u32.into()); - Box::new(tag) + tag.into() })); - drop(children); - assert!(vlist.fully_keyed, "should still be fully keyed"); + assert_eq!( + vlist.fully_keyed, + FullyKeyedState::KnownFullyKeyed, + "should still be fully keyed" + ); assert_eq!(vlist.len(), 1, "should contain 1 child"); // now add a child that is not keyed - let mut children = vlist.children_mut(); - children.push(VNode::VText(VText::new("lorem ipsum"))); - drop(children); - assert!( - !vlist.fully_keyed, + vlist.add_child(VNode::VText(VText::new("lorem ipsum"))); + assert_eq!( + vlist.fully_keyed, + FullyKeyedState::KnownMissingKeys, "should not be fully keyed, text tags have no key" ); + let _: &mut [VNode] = &mut vlist; // Use deref mut + assert_eq!( + vlist.fully_keyed, + FullyKeyedState::Unknown, + "key state should be unknown, since it was potentially modified through children" + ); } }