chore: adjust semantics of placeholders and fragments
This commit is contained in:
1 parent
2c3a046426
commit
1c516aba6a
3 files changed
+89
-69
No files matched your search
+36
-19
@@ -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
@@ -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,
|
})),
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user