wip: enable more diffing

This commit is contained in:
Jonathan Kelley committed 2021-07-05 01:11:49 -04:00
1 parent b5e5ef171a
commit e8f29a8f8a
26 files changed
+1326 -367

No files matched your search

+371 -202
View File
@@ -135,197 +135,357 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
When re-entering, we reuse the EditList in DiffState
*/
match old_node {
VNode::Element(old) => match new_node {
// New node is an element, old node was en element, need to investiage more deeply
VNode::Element(new) => {
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.create(new_node);
self.dom.replace_with();
return;
}
new.dom_id.set(old.dom_id.get());
self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children);
}
// New node is a text element, need to replace the element with a simple text node
VNode::Text(_) => {
self.create(new_node);
self.dom.replace_with();
}
// New node is a component
// Make the component and replace our element on the stack with it
VNode::Component(_) => {
self.create(new_node);
self.dom.replace_with();
}
// New node is actually a sequence of nodes.
// We need to replace this one node with a sequence of nodes
// Not yet implement because it's kinda hairy
VNode::Fragment(new) => {
match new.children.len() {
0 => {
// remove
}
1 => {
// replace
self.create(&new.children[0]);
self.dom.replace_with();
}
_ => {
todo!()
// remove and mount many
// self.remove_self_and_next_siblings(old)
//
// let iter = ChildIterator::new(new_node, self.components).rev();
// for child in iter {}
// for child in new.children.iter().skip(1).rev() {
// self.dom.remove();
// }
}
}
}
// New Node is actually suspended. Todo
VNode::Suspended { real } => todo!(),
},
// Old element was text
VNode::Text(old) => match new_node {
VNode::Text(new) => {
if old.text != new.text {
log::debug!("Text has changed {}, {}", old.text, new.text);
self.dom.set_text(new.text);
}
new.dom_id.set(old.dom_id.get());
}
VNode::Element(_) | VNode::Component(_) => {
self.create(new_node);
self.dom.replace_with();
}
// TODO on handling these types
VNode::Fragment(frag) => {
if frag.children.len() == 0 {
// do nothing
} else {
self.create(&frag.children[0]);
self.dom.replace_with();
for child in frag.children.iter().skip(1) {
self.create(child);
self.dom.append_child();
}
}
}
VNode::Suspended { real } => todo!(),
},
// Old element was a component
VNode::Component(old) => {
match new_node {
// It's something entirely different
VNode::Element(_) | VNode::Text(_) => {
self.create(new_node);
self.dom.replace_with();
}
// It's also a component
VNode::Component(new) => {
if old.user_fc == new.user_fc {
// Make sure we're dealing with the same component (by function pointer)
// Make sure the new component vnode is referencing the right scope id
let scope_id = old.ass_scope.get();
new.ass_scope.set(scope_id);
new.mounted_root.set(old.mounted_root.get());
// make sure the component's caller function is up to date
self.components
.with_scope(scope_id.unwrap(), |scope| {
scope.caller = Rc::downgrade(&new.caller)
})
.unwrap();
// React doesn't automatically memoize, but we do.
// The cost is low enough to make it worth checking
// Rust produces fairly performant comparison methods, sometimes SIMD accelerated
let should_render = match old.comparator {
Some(comparator) => comparator(new),
None => true,
};
if should_render {
// // self.dom.commit_traversal();
self.components
.with_scope(scope_id.unwrap(), |f| {
f.run_scope().unwrap();
})
.unwrap();
// diff_machine.change_list.load_known_root(root_id);
// run the scope
//
} else {
// Component has memoized itself and doesn't need to be re-rendered.
// We still need to make sure the child's props are up-to-date.
// Don't commit traversal
}
} else {
// It's an entirely different component
// A new component has shown up! We need to destroy the old node
// Wipe the old one and plant the new one
// self.dom.commit_traversal();
// self.dom.replace_node_with(old.dom_id, new.dom_id);
// self.create(new_node);
// self.dom.replace_with();
self.create(new_node);
// self.create_and_repalce(new_node, old.mounted_root.get());
// Now we need to remove the old scope and all of its descendents
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
}
VNode::Fragment(_) => todo!(),
VNode::Suspended { real } => todo!(),
log::debug!("diffing...");
match (old_node, new_node) {
// Handle the "sane" cases first.
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
(VNode::Text(old), VNode::Text(new)) => {
if old.text != new.text {
self.dom.push_root(old.dom_id.get());
log::debug!("Text has changed {}, {}", old.text, new.text);
self.dom.set_text(new.text);
}
new.dom_id.set(old.dom_id.get());
}
VNode::Fragment(old) => {
(VNode::Element(old), VNode::Element(new)) => {
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
match new_node {
VNode::Fragment(new) => todo!(),
// In Dioxus, this is less likely to occur unless through a fragment
if new.tag_name != old.tag_name || new.namespace != old.namespace {
log::debug!("things have changed....?");
self.dom.push_root(old.dom_id.get());
self.create(new_node);
self.dom.replace_with();
return;
}
log::debug!("Important Things haven't changed!");
new.dom_id.set(old.dom_id.get());
// going from fragment to element means we're going from many (or potentially none) to one
VNode::Element(new) => {}
VNode::Text(_) => todo!(),
VNode::Suspended { real } => todo!(),
VNode::Component(_) => todo!(),
self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children);
}
(VNode::Component(old), VNode::Component(new)) => {
log::warn!("diffing components? {:#?}", new.user_fc);
if old.user_fc == new.user_fc {
// Make sure we're dealing with the same component (by function pointer)
// Make sure the new component vnode is referencing the right scope id
let scope_id = old.ass_scope.get();
new.ass_scope.set(scope_id);
// new.mounted_root.set(old.mounted_root.get());
// make sure the component's caller function is up to date
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
// .with_scope(scope_id.unwrap(), |scope| {
scope.caller = Rc::downgrade(&new.caller);
scope.update_children(new.children);
// })
// .unwrap();
// React doesn't automatically memoize, but we do.
// The cost is low enough to make it worth checking
// Rust produces fairly performant comparison methods, sometimes SIMD accelerated
// let should_render = match old.comparator {
// Some(comparator) => comparator(new),
// None => true,
// };
// if should_render {
// // self.dom.commit_traversal();
// let f = self.components.try_get_mut(scope_id.unwrap()).unwrap();
// self.components
// .with_scope(scope_id.unwrap(), |f| {
log::debug!("running scope during diff {:#?}", scope_id);
scope.run_scope().unwrap();
self.diff_node(scope.old_frame(), scope.next_frame());
log::debug!("scope completed {:#?}", scope_id);
self.seen_nodes.insert(scope_id.unwrap());
// })
// .unwrap();
// diff_machine.change_list.load_known_root(root_id);
// run the scope
//
// } else {
// log::error!("Memoized componented");
// // Component has memoized itself and doesn't need to be re-rendered.
// // We still need to make sure the child's props are up-to-date.
// // Don't commit traversal
// }
} else {
// It's an entirely different component
// A new component has shown up! We need to destroy the old node
// Wipe the old one and plant the new one
// self.dom.commit_traversal();
// self.dom.replace_node_with(old.dom_id, new.dom_id);
// self.create(new_node);
log::warn!("creating and replacing...");
self.create(new_node);
// self.dom.replace_with();
// self.create_and_repalce(new_node, old.mounted_root.get());
// Now we need to remove the old scope and all of its descendents
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
}
// a suspended node will perform a mem-copy of the previous elements until it is ready
// this means that event listeners will need to be disabled and removed
// it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
VNode::Suspended { real: old_real } => {
(VNode::Fragment(old), VNode::Fragment(new)) => {
log::debug!("diffing as fragment");
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
self.diff_node(old.children.get(0).unwrap(), new.children.get(0).unwrap());
return;
}
// Diff using the approach where we're looking for added or removed nodes.
if old.children.len() != new.children.len() {}
// Diff where we think the elements are the same
if old.children.len() == new.children.len() {}
self.diff_children(old.children, new.children);
// todo!()
}
// Okay - these are the "insane" cases where the structure is entirely different.
// The factory and rsx! APIs don't really produce structures like this, so we don't take any too complicated
// code paths.
// in the case where the old node was a fragment but the new nodes are text,
(VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => {
// find the first real element int the old node
let mut iter = RealChildIterator::new(old_node, self.components);
if let Some(first) = iter.next() {
// replace the very first node with the creation of the element or text
} else {
// there are no real elements in the old fragment...
// We need to load up the next real
}
if let VNode::Component(old) = old_node {
// schedule this component's destructor to be run
todo!()
}
}
// In the case where real nodes are being replaced by potentially
(VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => {
//
match new_node {
VNode::Suspended { real: new_real } => {
//
}
VNode::Element(_) => todo!(),
VNode::Text(_) => todo!(),
VNode::Fragment(_) => todo!(),
VNode::Component(_) => todo!(),
}
}
(VNode::Text(_), VNode::Element(_)) => {
self.create(new_node);
self.dom.replace_with();
}
(VNode::Element(_), VNode::Text(_)) => {
self.create(new_node);
self.dom.replace_with();
}
_ => {
//
} /*
*/
//
// VNode::Element(old) => match new_node {
// // New node is an element, old node was en element, need to investiage more deeply
// VNode::Element(new) => {
// // If the element type is completely different, the element needs to be re-rendered completely
// // This is an optimization React makes due to how users structure their code
// if new.tag_name != old.tag_name || new.namespace != old.namespace {
// self.create(new_node);
// self.dom.replace_with();
// return;
// }
// new.dom_id.set(old.dom_id.get());
// self.diff_listeners(old.listeners, new.listeners);
// self.diff_attr(old.attributes, new.attributes, new.namespace);
// self.diff_children(old.children, new.children);
// }
// // New node is a text element, need to replace the element with a simple text node
// VNode::Text(_) => {
// self.create(new_node);
// self.dom.replace_with();
// }
// // New node is a component
// // Make the component and replace our element on the stack with it
// VNode::Component(_) => {
// self.create(new_node);
// self.dom.replace_with();
// }
// // New node is actually a sequence of nodes.
// // We need to replace this one node with a sequence of nodes
// // Not yet implement because it's kinda hairy
// VNode::Fragment(new) => {
// match new.children.len() {
// 0 => {
// // remove
// }
// 1 => {
// // replace
// self.create(&new.children[0]);
// self.dom.replace_with();
// }
// _ => {
// todo!()
// // remove and mount many
// // self.remove_self_and_next_siblings(old)
// //
// // let iter = ChildIterator::new(new_node, self.components).rev();
// // for child in iter {}
// // for child in new.children.iter().skip(1).rev() {
// // self.dom.remove();
// // }
// }
// }
// }
// // New Node is actually suspended. Todo
// VNode::Suspended { real } => todo!(),
// },
// // Old element was text
// VNode::Text(old) => match new_node {
// VNode::Text(new) => {
// if old.text != new.text {
// log::debug!("Text has changed {}, {}", old.text, new.text);
// self.dom.set_text(new.text);
// }
// new.dom_id.set(old.dom_id.get());
// }
// VNode::Element(_) | VNode::Component(_) => {
// self.create(new_node);
// self.dom.replace_with();
// }
// // TODO on handling these types
// VNode::Fragment(frag) => {
// if frag.children.len() == 0 {
// // do nothing
// } else {
// self.create(&frag.children[0]);
// self.dom.replace_with();
// for child in frag.children.iter().skip(1) {
// self.create(child);
// self.dom.append_child();
// }
// }
// }
// VNode::Suspended { real } => todo!(),
// },
// // Old element was a component
// VNode::Component(old) => {
// match new_node {
// // It's something entirely different
// VNode::Element(_) | VNode::Text(_) => {
// self.create(new_node);
// self.dom.replace_with();
// }
// // It's also a component
// VNode::Component(new) => {
// if old.user_fc == new.user_fc {
// // Make sure we're dealing with the same component (by function pointer)
// // Make sure the new component vnode is referencing the right scope id
// let scope_id = old.ass_scope.get();
// new.ass_scope.set(scope_id);
// new.mounted_root.set(old.mounted_root.get());
// // make sure the component's caller function is up to date
// self.components
// .with_scope(scope_id.unwrap(), |scope| {
// scope.caller = Rc::downgrade(&new.caller)
// })
// .unwrap();
// // React doesn't automatically memoize, but we do.
// // The cost is low enough to make it worth checking
// // Rust produces fairly performant comparison methods, sometimes SIMD accelerated
// let should_render = match old.comparator {
// Some(comparator) => comparator(new),
// None => true,
// };
// if should_render {
// // // self.dom.commit_traversal();
// self.components
// .with_scope(scope_id.unwrap(), |f| {
// f.run_scope().unwrap();
// })
// .unwrap();
// // diff_machine.change_list.load_known_root(root_id);
// // run the scope
// //
// } else {
// // Component has memoized itself and doesn't need to be re-rendered.
// // We still need to make sure the child's props are up-to-date.
// // Don't commit traversal
// }
// } else {
// // It's an entirely different component
// // A new component has shown up! We need to destroy the old node
// // Wipe the old one and plant the new one
// // self.dom.commit_traversal();
// // self.dom.replace_node_with(old.dom_id, new.dom_id);
// // self.create(new_node);
// // self.dom.replace_with();
// self.create(new_node);
// // self.create_and_repalce(new_node, old.mounted_root.get());
// // Now we need to remove the old scope and all of its descendents
// let old_scope = old.ass_scope.get().unwrap();
// self.destroy_scopes(old_scope);
// }
// }
// VNode::Fragment(_) => todo!(),
// VNode::Suspended { real } => todo!(),
// }
// }
// VNode::Fragment(old) => {
// //
// match new_node {
// VNode::Fragment(new) => todo!(),
// // going from fragment to element means we're going from many (or potentially none) to one
// VNode::Element(new) => {}
// VNode::Text(_) => todo!(),
// VNode::Suspended { real } => todo!(),
// VNode::Component(_) => todo!(),
// }
// }
// // a suspended node will perform a mem-copy of the previous elements until it is ready
// // this means that event listeners will need to be disabled and removed
// // it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
// VNode::Suspended { real: old_real } => {
// //
// match new_node {
// VNode::Suspended { real: new_real } => {
// //
// }
// VNode::Element(_) => todo!(),
// VNode::Text(_) => todo!(),
// VNode::Fragment(_) => todo!(),
// VNode::Component(_) => todo!(),
// }
// }
}
}
@@ -340,6 +500,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// [... node]
fn create(&mut self, node: &'bump VNode<'bump>) {
// debug_assert!(self.dom.traversal_is_committed());
log::warn!("Creating node!");
match node {
VNode::Text(text) => {
let real_id = self.dom.create_text_node(text.text);
@@ -402,7 +563,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
}
VNode::Component(component) => {
let real_id = self.dom.create_placeholder();
// let real_id = self.dom.create_placeholder();
// let root_id = next_id();
// self.dom.save_known_root(root_id);
@@ -455,6 +616,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
new_component.run_scope().unwrap();
// And then run the diff algorithm
let _real_id = self.dom.create_placeholder();
self.diff_node(new_component.old_frame(), new_component.next_frame());
// Finally, insert this node as a seen node.
@@ -675,11 +837,14 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
);
if new_is_keyed && old_is_keyed {
todo!("Not yet implemented a migration away from temporaries");
log::warn!("using the wrong approach");
self.diff_non_keyed_children(old, new);
// todo!("Not yet implemented a migration away from temporaries");
// let t = self.dom.next_temporary();
// self.diff_keyed_children(old, new);
// self.dom.set_next_temporary(t);
} else {
log::debug!("diffing non keyed children");
self.diff_non_keyed_children(old, new);
}
}
@@ -1120,24 +1285,24 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
// self.dom.go_to_sibling(i);
// [... parent this_child]
let did = old_child.get_mounted_id(self.components).unwrap();
if did.0 == 0 {
log::debug!("Root is bad: {:#?}", old_child);
}
self.dom.push_root(did);
// let did = old_child.get_mounted_id(self.components).unwrap();
// if did.0 == 0 {
// log::debug!("Root is bad: {:#?}", old_child);
// }
// self.dom.push_root(did);
self.diff_node(old_child, new_child);
let old_id = old_child.get_mounted_id(self.components).unwrap();
let new_id = new_child.get_mounted_id(self.components).unwrap();
// let old_id = old_child.get_mounted_id(self.components).unwrap();
// let new_id = new_child.get_mounted_id(self.components).unwrap();
log::debug!(
"pushed root. {:?}, {:?}",
old_child.get_mounted_id(self.components).unwrap(),
new_child.get_mounted_id(self.components).unwrap()
);
if old_id != new_id {
log::debug!("Mismatch: {:?}", new_child);
}
// log::debug!(
// "pushed root. {:?}, {:?}",
// old_child.get_mounted_id(self.components).unwrap(),
// new_child.get_mounted_id(self.components).unwrap()
// );
// if old_id != new_id {
// log::debug!("Mismatch: {:?}", new_child);
// }
}
// match old.len().cmp(&new.len()) {
@@ -1256,7 +1421,11 @@ enum KeyedPrefixResult {
MoreWorkToDo(usize),
}
struct ChildIterator<'a> {
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
///
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore".
struct RealChildIterator<'a> {
scopes: &'a ScopeArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
@@ -1264,7 +1433,7 @@ struct ChildIterator<'a> {
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> ChildIterator<'a> {
impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a ScopeArena) -> Self {
Self {
scopes,
@@ -1279,7 +1448,7 @@ impl<'a> ChildIterator<'a> {
// }
// }
impl<'a> Iterator for ChildIterator<'a> {
impl<'a> Iterator for RealChildIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
@@ -1402,7 +1571,7 @@ mod tests {
let mut renderer = DebugDom::new();
dom.rebuild(&mut renderer).unwrap();
let starter = dom.base_scope().root();
let ite = ChildIterator::new(starter, &dom.components);
let ite = RealChildIterator::new(starter, &dom.components);
for child in ite {
match child {
VNode::Element(el) => println!("Found: Element {}", el.tag_name),
+50 -2
View File
@@ -10,14 +10,62 @@ use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
#[derive(Debug)]
pub struct EventTrigger {
///
pub component_id: ScopeIdx,
///
pub real_node_id: RealDomNode,
///
pub event: VirtualEvent,
///
pub priority: EventPriority,
}
/// Priority of Event Triggers.
///
/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
///
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
#[derive(Debug)]
pub enum EventPriority {
/// "Immediate" work will interrupt whatever work is currently being done and force its way through. This type of work
/// is typically reserved for small changes to single elements.
///
/// The primary user of the "Immediate" priority is the `Signal` API which performs surgical mutations to the DOM.
Immediate,
/// "High Priority" work will not interrupt other high priority work, but will interrupt long medium and low priority work.
///
///
/// This is typically reserved for things like user interaction.
High,
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
/// than "High Priority" events and will take presedence over low priority events.
///
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
Medium,
/// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
/// advanced to the front of the work queue until completed.
///
/// The primary user of Low Priority work is the asynchronous work system (suspense).
Low,
}
impl EventTrigger {
pub fn new(event: VirtualEvent, scope: ScopeIdx, mounted_dom_id: RealDomNode) -> Self {
pub fn new(
event: VirtualEvent,
scope: ScopeIdx,
mounted_dom_id: RealDomNode,
priority: EventPriority,
) -> Self {
Self {
priority,
component_id: scope,
real_node_id: mounted_dom_id,
event,
@@ -78,7 +126,7 @@ pub mod on {
( $( $eventdata:ident($wrapper:ident): [ $( $name:ident )* ]; )* ) => {
$(
#[derive(Debug)]
pub struct $wrapper(Rc<dyn $eventdata>);
pub struct $wrapper(pub Rc<dyn $eventdata>);
impl Deref for $wrapper {
type Target = Rc<dyn $eventdata>;
fn deref(&self) -> &Self::Target {
+24 -6
View File
@@ -9,6 +9,8 @@ use std::{
u128,
};
use bumpalo::Bump;
use crate::{
events::VirtualEvent,
innerlude::{Properties, VComponent, FC},
@@ -654,8 +656,9 @@ pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a
// we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
//
// In the most general case, this prevents the need for any string allocations for simple code IE:
// div {"abc"}
//
// ```
// div {"abc"}
// ```
match args.as_str() {
Some(static_str) => VNode::text(static_str),
None => {
@@ -735,13 +738,14 @@ impl<'a, 'b> ChildrenList<'a, 'b> {
}
}
// NodeFactory is used to build VNodes in the component's memory space.
// This struct adds metadata to the final VNode about listeners, attributes, and children
/// This struct provides an ergonomic API to quickly build VNodes.
///
/// NodeFactory is used to build VNodes in the component's memory space.
/// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeFactory<'a> {
pub scope_ref: &'a Scope,
pub listener_id: Cell<usize>,
// pub listener_id: RefCell<usize>,
}
impl<'a> NodeFactory<'a> {
@@ -756,7 +760,7 @@ impl<'a> NodeFactory<'a> {
}
/// Create an element builder
pub fn element<'b, Listeners, Attributes, Children>(
pub fn element<'b>(
&'b self,
tag_name: &'static str,
) -> ElementBuilder<
@@ -787,6 +791,20 @@ impl<'a> NodeFactory<'a> {
key: NodeKey::new_opt(key),
}))
}
pub fn fragment_from_iter(
&self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
) -> VNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
for node in node_iter.into_iter() {
nodes.push(node.into_vnode(&self));
}
VNode::Fragment(
self.bump()
.alloc(VFragment::new(None, nodes.into_bump_slice())),
)
}
}
use std::fmt::Debug;
+5 -1
View File
@@ -29,7 +29,11 @@ pub enum VNode<'src> {
/// A text node (node type `TEXT_NODE`).
Text(VText<'src>),
/// A fragment is a "virtual position" in the DOM
/// A fragment is a list of elements that might have a dynamic order.
/// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently.
///
/// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes.
///
/// Fragments may have children and keys
Fragment(&'src VFragment<'src>),
+10 -2
View File
@@ -487,6 +487,14 @@ impl Scope {
self.caller = broken_caller;
}
pub fn update_children<'creator_node>(
&mut self,
child_nodes: &'creator_node [VNode<'creator_node>],
) {
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
self.child_nodes = child_nodes;
}
/// Create a new context and run the component with references from the Virtual Dom
/// This function downcasts the function pointer based on the stored props_type
///
@@ -597,9 +605,9 @@ impl Scope {
///
/// }
///
/// fn example(cx: Context, props: &Props -> VNode {
/// fn example(cx: Context<Props>) -> VNode {
/// html! {
/// <div> "Hello, {cx.cx.name}" </div>
/// <div> "Hello, {cx.name}" </div>
/// }
/// }
/// ```