component.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. use std::{
  2. any::TypeId,
  3. ops::{Deref, DerefMut},
  4. };
  5. use crate::{
  6. any_props::AnyProps,
  7. innerlude::{
  8. ElementRef, MountId, ScopeOrder, SuspenseBoundaryProps, SuspenseBoundaryPropsWithOwner,
  9. VComponent, WriteMutations,
  10. },
  11. nodes::VNode,
  12. scopes::ScopeId,
  13. virtual_dom::VirtualDom,
  14. RenderReturn,
  15. };
  16. impl VirtualDom {
  17. pub(crate) fn run_and_diff_scope<M: WriteMutations>(
  18. &mut self,
  19. to: Option<&mut M>,
  20. scope_id: ScopeId,
  21. ) {
  22. let scope = &mut self.scopes[scope_id.0];
  23. if SuspenseBoundaryProps::downcast_mut_from_props(&mut *scope.props).is_some() {
  24. SuspenseBoundaryProps::diff(scope_id, self, to)
  25. } else {
  26. let new_nodes = self.run_scope(scope_id);
  27. self.diff_scope(to, scope_id, new_nodes);
  28. }
  29. }
  30. #[tracing::instrument(skip(self, to), level = "trace", name = "VirtualDom::diff_scope")]
  31. fn diff_scope<M: WriteMutations>(
  32. &mut self,
  33. to: Option<&mut M>,
  34. scope: ScopeId,
  35. new_nodes: RenderReturn,
  36. ) {
  37. // We don't diff the nodes if the scope is suspended or has an error
  38. let Ok(new_real_nodes) = &new_nodes.node else {
  39. return;
  40. };
  41. self.runtime.push_scope(scope);
  42. let scope_state = &mut self.scopes[scope.0];
  43. // Load the old and new rendered nodes
  44. let old = scope_state.last_rendered_node.take().unwrap();
  45. // If there are suspended scopes, we need to check if the scope is suspended before we diff it
  46. // If it is suspended, we need to diff it but write the mutations nothing
  47. // Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders
  48. let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));
  49. old.diff_node(new_real_nodes, self, render_to.as_deref_mut());
  50. self.scopes[scope.0].last_rendered_node = Some(new_nodes);
  51. if render_to.is_some() {
  52. self.runtime.get_state(scope).unwrap().mount(&self.runtime);
  53. }
  54. self.runtime.pop_scope();
  55. }
  56. /// Create a new [`ScopeState`] for a component that has been created with [`VirtualDom::create_scope`]
  57. ///
  58. /// Returns the number of nodes created on the stack
  59. #[tracing::instrument(skip(self, to), level = "trace", name = "VirtualDom::create_scope")]
  60. pub(crate) fn create_scope<M: WriteMutations>(
  61. &mut self,
  62. to: Option<&mut M>,
  63. scope: ScopeId,
  64. new_nodes: RenderReturn,
  65. parent: Option<ElementRef>,
  66. ) -> usize {
  67. self.runtime.push_scope(scope);
  68. // If there are suspended scopes, we need to check if the scope is suspended before we diff it
  69. // If it is suspended, we need to diff it but write the mutations nothing
  70. // Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders
  71. let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));
  72. // Create the node
  73. let nodes = new_nodes.create(self, parent, render_to.as_deref_mut());
  74. // Then set the new node as the last rendered node
  75. self.scopes[scope.0].last_rendered_node = Some(new_nodes);
  76. if render_to.is_some() {
  77. self.runtime.get_state(scope).unwrap().mount(&self.runtime);
  78. }
  79. self.runtime.pop_scope();
  80. nodes
  81. }
  82. pub(crate) fn remove_component_node<M: WriteMutations>(
  83. &mut self,
  84. to: Option<&mut M>,
  85. destroy_component_state: bool,
  86. scope_id: ScopeId,
  87. replace_with: Option<usize>,
  88. ) {
  89. // If this is a suspense boundary, remove the suspended nodes as well
  90. if let Some(mut suspense) =
  91. SuspenseBoundaryProps::downcast_mut_from_props(&mut *self.scopes[scope_id.0].props)
  92. .cloned()
  93. {
  94. suspense.remove_suspended_nodes::<M>(self, destroy_component_state);
  95. }
  96. // Remove the component from the dom
  97. if let Some(node) = self.scopes[scope_id.0].last_rendered_node.as_ref() {
  98. node.clone_mounted()
  99. .remove_node_inner(self, to, destroy_component_state, replace_with)
  100. };
  101. if destroy_component_state {
  102. // Now drop all the resources
  103. self.drop_scope(scope_id);
  104. }
  105. }
  106. }
  107. impl VNode {
  108. pub(crate) fn diff_vcomponent(
  109. &self,
  110. mount: MountId,
  111. idx: usize,
  112. new: &VComponent,
  113. old: &VComponent,
  114. scope_id: ScopeId,
  115. parent: Option<ElementRef>,
  116. dom: &mut VirtualDom,
  117. to: Option<&mut impl WriteMutations>,
  118. ) {
  119. // Replace components that have different render fns
  120. if old.render_fn != new.render_fn {
  121. return self.replace_vcomponent(mount, idx, new, parent, dom, to);
  122. }
  123. // copy out the box for both
  124. let old_scope = &mut dom.scopes[scope_id.0];
  125. let old_props: &mut dyn AnyProps = old_scope.props.deref_mut();
  126. let new_props: &dyn AnyProps = new.props.deref();
  127. // If the props are static, then we try to memoize by setting the new with the old
  128. // The target ScopeState still has the reference to the old props, so there's no need to update anything
  129. // This also implicitly drops the new props since they're not used
  130. if old_props.memoize(new_props.props()) {
  131. tracing::trace!("Memoized props for component {:#?}", scope_id,);
  132. return;
  133. }
  134. // Now diff the scope
  135. dom.run_and_diff_scope(to, scope_id);
  136. let height = dom.runtime.get_state(scope_id).unwrap().height;
  137. dom.dirty_scopes.remove(&ScopeOrder::new(height, scope_id));
  138. }
  139. fn replace_vcomponent(
  140. &self,
  141. mount: MountId,
  142. idx: usize,
  143. new: &VComponent,
  144. parent: Option<ElementRef>,
  145. dom: &mut VirtualDom,
  146. mut to: Option<&mut impl WriteMutations>,
  147. ) {
  148. let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
  149. // Remove the scope id from the mount
  150. dom.mounts[mount.0].mounted_dynamic_nodes[idx] = ScopeId::PLACEHOLDER.0;
  151. let m = self.create_component_node(mount, idx, new, parent, dom, to.as_deref_mut());
  152. // Instead of *just* removing it, we can use the replace mutation
  153. dom.remove_component_node(to, true, scope, Some(m));
  154. }
  155. /// Create a new component (if it doesn't already exist) node and then mount the [`ScopeState`] for a component
  156. ///
  157. /// Returns the number of nodes created on the stack
  158. pub(super) fn create_component_node(
  159. &self,
  160. mount: MountId,
  161. idx: usize,
  162. component: &VComponent,
  163. parent: Option<ElementRef>,
  164. dom: &mut VirtualDom,
  165. to: Option<&mut impl WriteMutations>,
  166. ) -> usize {
  167. // If this is a suspense boundary, run our suspense creation logic instead of running the component
  168. if component.props.props().type_id() == TypeId::of::<SuspenseBoundaryPropsWithOwner>() {
  169. return SuspenseBoundaryProps::create(mount, idx, component, parent, dom, to);
  170. }
  171. let mut scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
  172. // If the scopeid is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
  173. if scope_id.is_placeholder() {
  174. scope_id = dom
  175. .new_scope(component.props.duplicate(), component.name)
  176. .state()
  177. .id;
  178. // Store the scope id for the next render
  179. dom.mounts[mount.0].mounted_dynamic_nodes[idx] = scope_id.0;
  180. // If this is a new scope, we also need to run it once to get the initial state
  181. let new = dom.run_scope(scope_id);
  182. // Then set the new node as the last rendered node
  183. dom.scopes[scope_id.0].last_rendered_node = Some(new);
  184. }
  185. let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
  186. let new_node = dom.scopes[scope.0]
  187. .last_rendered_node
  188. .as_ref()
  189. .expect("Component to be mounted")
  190. .clone();
  191. dom.create_scope(to, scope, new_node, parent)
  192. }
  193. }