chore: adjust semantics of placeholders and fragments

This commit is contained in:
Jonathan Kelley committed 2021-12-18 15:49:30 -05:00
1 parent 2c3a046426
commit 1c516aba6a
3 files changed
+89 -69

No files matched your search

+36 -19
View File
@@ -501,9 +501,16 @@ impl<'bump> DiffState<'bump> {
self.diff_component_nodes(old_node, new_node, *old, *new) self.diff_component_nodes(old_node, new_node, *old, *new)
} }
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
(Placeholder(old), Placeholder(new)) => new.dom_id.set(old.dom_id.get()), (Placeholder(old), Placeholder(new)) => self.diff_placeholder_nodes(old, new),
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node), (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
// The normal pathway still works, but generates slightly weird instructions
// This pathway just ensures we get create and replace
(Placeholder(_), Fragment(new)) => {
self.stack
.create_children(new.children, MountType::Replace { old: old_node });
}
// Anything else is just a basic replace and create // Anything else is just a basic replace and create
( (
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
@@ -634,7 +641,9 @@ impl<'bump> DiffState<'bump> {
} }
// todo: this is for the "settext" optimization // todo: this is for the "settext" optimization
// it works, but i'm not sure if it's the direction we want to take // it works, but i'm not sure if it's the direction we want to take right away
// I haven't benchmarked the performance imporvemenet yet. Perhaps
// we can make it a config?
// match (old.children.len(), new.children.len()) { // match (old.children.len(), new.children.len()) {
// (0, 0) => {} // (0, 0) => {}
@@ -754,6 +763,11 @@ impl<'bump> DiffState<'bump> {
self.diff_children(old.children, new.children); self.diff_children(old.children, new.children);
} }
// probably will get inlined
fn diff_placeholder_nodes(&mut self, old: &'bump VPlaceholder, new: &'bump VPlaceholder) {
new.dom_id.set(old.dom_id.get())
}
// ============================================= // =============================================
// Utilities for creating new diff instructions // Utilities for creating new diff instructions
// ============================================= // =============================================
@@ -784,23 +798,26 @@ impl<'bump> DiffState<'bump> {
(_, []) => { (_, []) => {
self.remove_nodes(old, true); self.remove_nodes(old, true);
} }
([VNode::Placeholder(old_anchor)], [VNode::Placeholder(new_anchor)]) => {
old_anchor.dom_id.set(new_anchor.dom_id.get()); // todo: placeholders explicitly replace fragments
} //
([VNode::Placeholder(_)], _) => { // ([VNode::Placeholder(old_anchor)], [VNode::Placeholder(new_anchor)]) => {
self.stack // old_anchor.dom_id.set(new_anchor.dom_id.get());
.create_children(new, MountType::Replace { old: &old[0] }); // }
} // ([VNode::Placeholder(_)], _) => {
(_, [VNode::Placeholder(_)]) => { // self.stack
let new: &'bump VNode<'bump> = &new[0]; // .create_children(new, MountType::Replace { old: &old[0] });
if let Some(first_old) = old.get(0) { // }
self.remove_nodes(&old[1..], true); // (_, [VNode::Placeholder(_)]) => {
self.stack // let new: &'bump VNode<'bump> = &new[0];
.create_node(new, MountType::Replace { old: first_old }); // if let Some(first_old) = old.get(0) {
} else { // self.remove_nodes(&old[1..], true);
self.stack.create_node(new, MountType::Append {}); // self.stack
} // .create_node(new, MountType::Replace { old: first_old });
} // } else {
// self.stack.create_node(new, MountType::Append {});
// }
// }
_ => { _ => {
let new_is_keyed = new[0].key().is_some(); let new_is_keyed = new[0].key().is_some();
let old_is_keyed = old[0].key().is_some(); let old_is_keyed = old[0].key().is_some();
+46 -43
View File
@@ -177,9 +177,11 @@ impl Debug for VNode<'_> {
.debug_struct("VNode::VElement") .debug_struct("VNode::VElement")
.field("name", &el.tag_name) .field("name", &el.tag_name)
.field("key", &el.key) .field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
.finish(), .finish(),
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
VNode::Placeholder(_) => write!(s, "VNode::VAnchor"), VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"),
VNode::Fragment(frag) => { VNode::Fragment(frag) => {
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
} }
@@ -225,6 +227,10 @@ pub struct VText<'src> {
/// A list of VNodes with no single root. /// A list of VNodes with no single root.
pub struct VFragment<'src> { pub struct VFragment<'src> {
pub key: Option<&'src str>, pub key: Option<&'src str>,
/// Fragments can never have zero children. Enforced by NodeFactory.
///
/// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic.
pub children: &'src [VNode<'src>], pub children: &'src [VNode<'src>],
} }
@@ -602,15 +608,15 @@ impl<'a> NodeFactory<'a> {
} }
if nodes.is_empty() { if nodes.is_empty() {
nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder { VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(), dom_id: empty_cell(),
}))); }))
} else {
VNode::Fragment(VFragment {
children: nodes.into_bump_slice(),
key: None,
})
} }
VNode::Fragment(VFragment {
children: nodes.into_bump_slice(),
key: None,
})
} }
pub fn fragment_from_iter<'b, 'c>( pub fn fragment_from_iter<'b, 'c>(
@@ -624,32 +630,34 @@ impl<'a> NodeFactory<'a> {
} }
if nodes.is_empty() { if nodes.is_empty() {
nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder { VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(), dom_id: empty_cell(),
}))); }))
} } else {
let children = nodes.into_bump_slice();
let children = nodes.into_bump_slice(); if cfg!(debug_assertions)
&& children.len() > 1
if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none() && children.last().unwrap().key().is_none()
{ {
// todo: make the backtrace prettier or remove it altogether // todo: make the backtrace prettier or remove it altogether
log::error!( log::error!(
r#" r#"
Warning: Each child in an array or iterator should have a unique "key" prop. Warning: Each child in an array or iterator should have a unique "key" prop.
Not providing a key will lead to poor performance with lists. Not providing a key will lead to poor performance with lists.
See docs.rs/dioxus for more information. See docs.rs/dioxus for more information.
------------- -------------
{:?} {:?}
"#, "#,
backtrace::Backtrace::new() backtrace::Backtrace::new()
); );
} }
VNode::Fragment(VFragment { VNode::Fragment(VFragment {
children, children,
key: None, key: None,
}) })
}
} }
// this isn't quite feasible yet // this isn't quite feasible yet
@@ -658,28 +666,24 @@ impl<'a> NodeFactory<'a> {
self, self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>, node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
) -> Element<'a> { ) -> Element<'a> {
let bump = self.bump; let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
let mut nodes = bumpalo::collections::Vec::new_in(bump);
for node in node_iter { for node in node_iter {
nodes.push(node.into_vnode(self)); nodes.push(node.into_vnode(self));
} }
if nodes.is_empty() { if nodes.is_empty() {
nodes.push(VNode::Placeholder(bump.alloc(VPlaceholder { Some(VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(), dom_id: empty_cell(),
}))); })))
} else {
let children = nodes.into_bump_slice();
Some(VNode::Fragment(VFragment {
children,
key: None,
}))
} }
let children = nodes.into_bump_slice();
// TODO
// We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
Some(VNode::Fragment(VFragment {
children,
key: None,
}))
} }
} }
@@ -749,10 +753,9 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
match self { match self {
Some(lazy) => lazy.call(cx), Some(lazy) => lazy.call(cx),
None => VNode::Fragment(VFragment { None => VNode::Placeholder(cx.bump.alloc(VPlaceholder {
children: &[], dom_id: empty_cell(),
key: None, })),
}),
} }
} }
} }
+7 -7
View File
@@ -184,12 +184,13 @@ fn empty_fragments_create_anchors_with_many_children() {
create.edits, create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { CreateElement {
tag: "div",
root: 2, root: 2,
tag: "div"
}, },
CreateTextNode { CreateTextNode {
text: "hello: 0", text: "hello: 0",
@@ -197,8 +198,8 @@ fn empty_fragments_create_anchors_with_many_children() {
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { CreateElement {
tag: "div",
root: 4, root: 4,
tag: "div"
}, },
CreateTextNode { CreateTextNode {
text: "hello: 1", text: "hello: 1",
@@ -206,15 +207,15 @@ fn empty_fragments_create_anchors_with_many_children() {
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { CreateElement {
tag: "div",
root: 6, root: 6,
tag: "div"
}, },
CreateTextNode { CreateTextNode {
text: "hello: 2", text: "hello: 2",
root: 7 root: 7
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
ReplaceWith { m: 3, root: 1 } ReplaceWith { root: 1, m: 3 }
] ]
); );
} }
@@ -257,13 +258,12 @@ fn many_items_become_fragment() {
] ]
); );
// note: the ID gets reused
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
Remove { root: 3 }, CreatePlaceholder { root: 5 },
CreatePlaceholder { root: 4 },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: 1, m: 1 },
Remove { root: 3 },
] ]
); );
} }