nodebuilder.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. //! Helpers for building virtual DOM VNodes.
  2. use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128};
  3. use crate::{
  4. events::VirtualEvent,
  5. innerlude::{Properties, VComponent, FC},
  6. nodes::{Attribute, Listener, NodeKey, VNode},
  7. prelude::VElement,
  8. virtual_dom::NodeCtx,
  9. };
  10. /// A virtual DOM element builder.
  11. ///
  12. /// Typically constructed with element-specific constructors, eg the `div`
  13. /// function for building `<div>` elements or the `button` function for building
  14. /// `<button>` elements.
  15. #[derive(Debug)]
  16. pub struct ElementBuilder<'a, 'b, Listeners, Attributes, Children>
  17. where
  18. Listeners: 'a + AsRef<[Listener<'a>]>,
  19. Attributes: 'a + AsRef<[Attribute<'a>]>,
  20. Children: 'a + AsRef<[VNode<'a>]>,
  21. {
  22. ctx: &'b NodeCtx<'a>,
  23. key: NodeKey<'a>,
  24. tag_name: &'a str,
  25. listeners: Listeners,
  26. attributes: Attributes,
  27. children: Children,
  28. namespace: Option<&'a str>,
  29. }
  30. impl<'a, 'b>
  31. ElementBuilder<
  32. 'a,
  33. 'b,
  34. bumpalo::collections::Vec<'a, Listener<'a>>,
  35. bumpalo::collections::Vec<'a, Attribute<'a>>,
  36. bumpalo::collections::Vec<'a, VNode<'a>>,
  37. >
  38. {
  39. /// Create a new `ElementBuilder` for an element with the given tag name.
  40. ///
  41. /// In general, only use this constructor if the tag is dynamic (i.e. you
  42. /// might build a `<div>` or you might build a `<span>` and you don't know
  43. /// until runtime). Prefer using the tag-specific constructors instead:
  44. /// `div(bump)` or `span(bump)`, etc.
  45. ///
  46. /// # Example
  47. ///
  48. /// ```
  49. /// use dioxus::{builder::*, bumpalo::Bump};
  50. ///
  51. /// let b = Bump::new();
  52. ///
  53. /// let tag_name = if flip_coin() {
  54. /// "div"
  55. /// } else {
  56. /// "span"
  57. /// };
  58. ///
  59. /// let my_element_builder = ElementBuilder::new(&b, tag_name);
  60. /// # fn flip_coin() -> bool { true }
  61. /// ```
  62. pub fn new(ctx: &'b NodeCtx<'a>, tag_name: &'static str) -> Self {
  63. let bump = ctx.bump();
  64. ElementBuilder {
  65. ctx,
  66. key: NodeKey::NONE,
  67. tag_name,
  68. listeners: bumpalo::collections::Vec::new_in(bump),
  69. attributes: bumpalo::collections::Vec::new_in(bump),
  70. children: bumpalo::collections::Vec::new_in(bump),
  71. namespace: None,
  72. }
  73. }
  74. }
  75. impl<'a, 'b, Listeners, Attributes, Children>
  76. ElementBuilder<'a, 'b, Listeners, Attributes, Children>
  77. where
  78. Listeners: 'a + AsRef<[Listener<'a>]>,
  79. Attributes: 'a + AsRef<[Attribute<'a>]>,
  80. Children: 'a + AsRef<[VNode<'a>]>,
  81. {
  82. /// Set the listeners for this element.
  83. ///
  84. /// You can use this method to customize the backing storage for listeners,
  85. /// for example to use a fixed-size array instead of the default
  86. /// dynamically-sized `bumpalo::collections::Vec`.
  87. ///
  88. /// Any listeners already added to the builder will be overridden.
  89. ///
  90. /// # Example
  91. ///
  92. /// ```no_run
  93. /// use dioxus::{builder::*, bumpalo::Bump};
  94. ///
  95. /// let b = Bump::new();
  96. ///
  97. /// // Create a `<div>` with a fixed-size array of two listeners.
  98. /// let my_div = div(&b)
  99. /// .listeners([
  100. /// on(&b, "click", |root, vdom, event| {
  101. /// // ...
  102. /// }),
  103. /// on(&b, "dblclick", |root, vdom, event| {
  104. /// // ...
  105. /// }),
  106. /// ])
  107. /// .finish();
  108. /// ```
  109. #[inline]
  110. pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, 'b, L, Attributes, Children>
  111. where
  112. L: 'a + AsRef<[Listener<'a>]>,
  113. {
  114. ElementBuilder {
  115. ctx: self.ctx,
  116. key: self.key,
  117. tag_name: self.tag_name,
  118. listeners,
  119. attributes: self.attributes,
  120. children: self.children,
  121. namespace: self.namespace,
  122. }
  123. }
  124. /// Set the attributes for this element.
  125. ///
  126. /// You can use this method to customize the backing storage for attributes,
  127. /// for example to use a fixed-size array instead of the default
  128. /// dynamically-sized `bumpalo::collections::Vec`.
  129. ///
  130. /// Any attributes already added to the builder will be overridden.
  131. ///
  132. /// # Example
  133. ///
  134. /// ```no_run
  135. /// use dioxus::{builder::*, bumpalo::Bump, Attribute};
  136. ///
  137. /// let b = Bump::new();
  138. ///
  139. /// // Create a `<div>` with a fixed-size array of two attributes.
  140. /// let my_div = div(&b)
  141. /// .attributes([
  142. /// attr("id", "my-div"),
  143. /// attr("class", "notification"),
  144. /// ])
  145. /// .finish();
  146. /// ```
  147. #[inline]
  148. pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, 'b, Listeners, A, Children>
  149. where
  150. A: 'a + AsRef<[Attribute<'a>]>,
  151. {
  152. ElementBuilder {
  153. ctx: self.ctx,
  154. key: self.key,
  155. tag_name: self.tag_name,
  156. listeners: self.listeners,
  157. attributes,
  158. children: self.children,
  159. namespace: self.namespace,
  160. }
  161. }
  162. /// Set the children for this element.
  163. ///
  164. /// You can use this method to customize the backing storage for children,
  165. /// for example to use a fixed-size array instead of the default
  166. /// dynamically-sized `bumpalo::collections::Vec`.
  167. ///
  168. /// Any children already added to the builder will be overridden.
  169. ///
  170. /// # Example
  171. ///
  172. /// ```no_run
  173. /// use dioxus::{builder::*, bumpalo::Bump};
  174. ///
  175. /// let b = Bump::new();
  176. ///
  177. /// // Create a `<div>` with a fixed-size array of two `<span>` children.
  178. /// let my_div = div(&b)
  179. /// .children([
  180. /// span(&b).finish(),
  181. /// span(&b).finish(),
  182. /// ])
  183. /// .finish();
  184. /// ```
  185. #[inline]
  186. pub fn children<C>(self, children: C) -> ElementBuilder<'a, 'b, Listeners, Attributes, C>
  187. where
  188. C: 'a + AsRef<[VNode<'a>]>,
  189. {
  190. ElementBuilder {
  191. ctx: self.ctx,
  192. key: self.key,
  193. tag_name: self.tag_name,
  194. listeners: self.listeners,
  195. attributes: self.attributes,
  196. children,
  197. namespace: self.namespace,
  198. }
  199. }
  200. /// Set the namespace for this element.
  201. ///
  202. /// # Example
  203. ///
  204. /// ```no_run
  205. /// use dioxus::{builder::*, bumpalo::Bump};
  206. ///
  207. /// let b = Bump::new();
  208. ///
  209. /// // Create a `<td>` tag with an xhtml namespace
  210. /// let my_td = td(&b)
  211. /// .namespace(Some("http://www.w3.org/1999/xhtml"))
  212. /// .finish();
  213. /// ```
  214. #[inline]
  215. pub fn namespace(self, namespace: Option<&'a str>) -> Self {
  216. ElementBuilder {
  217. ctx: self.ctx,
  218. key: self.key,
  219. tag_name: self.tag_name,
  220. listeners: self.listeners,
  221. attributes: self.attributes,
  222. children: self.children,
  223. namespace,
  224. }
  225. }
  226. /// Set this element's key.
  227. ///
  228. /// When diffing sets of siblings, if an old sibling and new sibling share a
  229. /// key, then they will always reuse the same physical DOM VNode. This is
  230. /// important when using CSS animations, web components, third party JS, or
  231. /// anything else that makes the diffing implementation observable.
  232. ///
  233. /// Do not use keys if such a scenario does not apply. Keyed diffing is
  234. /// generally more expensive than not, since it is putting greater
  235. /// constraints on the diffing algorithm.
  236. ///
  237. /// # Invariants You Must Uphold
  238. ///
  239. /// The key may not be `u32::MAX`, which is a reserved key value.
  240. ///
  241. /// Keys must be unique among siblings.
  242. ///
  243. /// All sibling VNodes must be keyed, or they must all not be keyed. You may
  244. /// not mix keyed and unkeyed siblings.
  245. ///
  246. /// # Example
  247. ///
  248. /// ```no_run
  249. /// use dioxus::{builder::*, bumpalo::Bump};
  250. ///
  251. /// let b = Bump::new();
  252. ///
  253. /// let my_li = li(&b)
  254. /// .key(1337)
  255. /// .finish();
  256. /// ```
  257. #[inline]
  258. pub fn key(mut self, key: &'a str) -> Self {
  259. self.key = NodeKey(Some(key));
  260. self
  261. }
  262. /// Create the virtual DOM VNode described by this builder.
  263. ///
  264. /// # Example
  265. ///
  266. /// ```no_run
  267. /// use dioxus::{builder::*, bumpalo::Bump, VNode};
  268. ///
  269. /// let b = Bump::new();
  270. ///
  271. /// // Start with a builder...
  272. /// let builder: ElementBuilder<_, _, _> = div(&b);
  273. ///
  274. /// // ...and finish it to create a virtual DOM VNode!
  275. /// let my_div: VNode = builder.finish();
  276. /// ```
  277. #[inline]
  278. pub fn finish(self) -> VNode<'a> {
  279. let bump = self.ctx.bump();
  280. let children: &'a Children = bump.alloc(self.children);
  281. let children: &'a [VNode<'a>] = children.as_ref();
  282. let listeners: &'a Listeners = bump.alloc(self.listeners);
  283. let listeners: &'a [Listener<'a>] = listeners.as_ref();
  284. let attributes: &'a Attributes = bump.alloc(self.attributes);
  285. let attributes: &'a [Attribute<'a>] = attributes.as_ref();
  286. VNode::element(
  287. bump,
  288. self.key,
  289. self.tag_name,
  290. listeners,
  291. attributes,
  292. children,
  293. self.namespace,
  294. )
  295. }
  296. }
  297. impl<'a, 'b, Attributes, Children>
  298. ElementBuilder<'a, 'b, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
  299. where
  300. Attributes: 'a + AsRef<[Attribute<'a>]>,
  301. Children: 'a + AsRef<[VNode<'a>]>,
  302. {
  303. /// Add a new event listener to this element.
  304. ///
  305. /// The `event` string specifies which event will be listened for. The
  306. /// `callback` function is the function that will be invoked if the
  307. /// specified event occurs.
  308. ///
  309. /// # Example
  310. ///
  311. /// ```no_run
  312. /// use dioxus::{builder::*, bumpalo::Bump};
  313. ///
  314. /// let b = Bump::new();
  315. ///
  316. /// // A button that does something when clicked!
  317. /// let my_button = button(&b)
  318. /// .on("click", |event| {
  319. /// // ...
  320. /// })
  321. /// .finish();
  322. /// ```
  323. pub fn on(self, event: &'static str, callback: impl Fn(VirtualEvent) + 'a) -> Self {
  324. let bump = &self.ctx.bump();
  325. let listener = Listener {
  326. event,
  327. callback: bump.alloc(callback),
  328. id: *self.ctx.listener_id.borrow(),
  329. scope: self.ctx.scope_ref.arena_idx,
  330. };
  331. self.add_listener(listener)
  332. }
  333. pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
  334. self.listeners.push(listener);
  335. // bump the context id forward
  336. *self.ctx.listener_id.borrow_mut() += 1;
  337. // Add this listener to the context list
  338. // This casts the listener to a self-referential pointer
  339. // This is okay because the bump arena is stable
  340. self.listeners.last().map(|g| {
  341. let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
  342. self.ctx
  343. .scope_ref
  344. .listeners
  345. .borrow_mut()
  346. .push(r.callback as *const _);
  347. });
  348. self
  349. }
  350. }
  351. impl<'a, 'b, Listeners, Children>
  352. ElementBuilder<'a, 'b, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
  353. where
  354. Listeners: 'a + AsRef<[Listener<'a>]>,
  355. Children: 'a + AsRef<[VNode<'a>]>,
  356. {
  357. /// Add a new attribute to this element.
  358. ///
  359. /// # Example
  360. ///
  361. /// ```no_run
  362. /// use dioxus::{builder::*, bumpalo::Bump};
  363. ///
  364. /// let b = Bump::new();
  365. ///
  366. /// // Create the `<div id="my-div"/>` element.
  367. /// let my_div = div(&b).attr("id", "my-div").finish();
  368. /// ```
  369. pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
  370. let value = match args.as_str() {
  371. Some(static_str) => static_str,
  372. None => {
  373. use bumpalo::core_alloc::fmt::Write;
  374. let mut s = bumpalo::collections::String::new_in(self.ctx.bump());
  375. s.write_fmt(args).unwrap();
  376. s.into_bump_str()
  377. }
  378. };
  379. self.attributes.push(Attribute { name, value });
  380. self
  381. }
  382. /// Conditionally add a "boolean-style" attribute to this element.
  383. ///
  384. /// If the `should_add` parameter is true, then adds an attribute with the
  385. /// given `name` and an empty string value. If the `should_add` parameter is
  386. /// false, then the attribute is not added.
  387. ///
  388. /// This method is useful for attributes whose semantics are defined by
  389. /// whether or not the attribute is present or not, and whose value is
  390. /// ignored. Example attributes like this include:
  391. ///
  392. /// * `checked`
  393. /// * `hidden`
  394. /// * `selected`
  395. ///
  396. /// # Example
  397. ///
  398. /// ```no_run
  399. /// use dioxus::{builder::*, bumpalo::Bump};
  400. /// use js_sys::Math;
  401. ///
  402. /// let b = Bump::new();
  403. ///
  404. /// // Create the `<div>` that is randomly hidden 50% of the time.
  405. /// let my_div = div(&b)
  406. /// .bool_attr("hidden", Math::random() >= 0.5)
  407. /// .finish();
  408. /// ```
  409. pub fn bool_attr(mut self, name: &'static str, should_add: bool) -> Self {
  410. if should_add {
  411. self.attributes.push(Attribute { name, value: "" });
  412. }
  413. self
  414. }
  415. }
  416. impl<'a, 'b, Listeners, Attributes>
  417. ElementBuilder<'a, 'b, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
  418. where
  419. Listeners: 'a + AsRef<[Listener<'a>]>,
  420. Attributes: 'a + AsRef<[Attribute<'a>]>,
  421. {
  422. /// Add a new child to this element.
  423. ///
  424. /// # Example
  425. ///
  426. /// ```no_run
  427. /// use dioxus::{builder::*, bumpalo::Bump};
  428. /// use js_sys::Math;
  429. ///
  430. /// let b = Bump::new();
  431. ///
  432. /// // Create `<p><span></span></p>`.
  433. /// let my_div = p(&b)
  434. /// .child(span(&b).finish())
  435. /// .finish();
  436. /// ```
  437. #[inline]
  438. pub fn child(mut self, child: VNode<'a>) -> Self {
  439. self.children.push(child);
  440. self
  441. }
  442. /// Add multiple children to this element from an iterator.
  443. ///
  444. /// # Example
  445. ///
  446. /// ```no_run
  447. /// use dioxus::{builder::*, bumpalo::Bump};
  448. ///
  449. /// let b = Bump::new();
  450. ///
  451. /// let my_div = p(&b)
  452. /// .iter_child((0..10).map(|f| span(&b).finish())
  453. /// .finish();
  454. /// ```
  455. pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
  456. for item in nodes {
  457. let child = item.into_vnode(&self.ctx);
  458. self.children.push(child);
  459. }
  460. self
  461. }
  462. }
  463. impl<'a> IntoIterator for VNode<'a> {
  464. type Item = VNode<'a>;
  465. type IntoIter = std::iter::Once<Self::Item>;
  466. fn into_iter(self) -> Self::IntoIter {
  467. std::iter::once(self)
  468. }
  469. }
  470. impl<'a> IntoVNode<'a> for VNode<'a> {
  471. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
  472. self
  473. }
  474. }
  475. impl<'a> IntoVNode<'a> for &VNode<'a> {
  476. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
  477. // cloning is cheap since vnodes are just references into bump arenas
  478. self.clone()
  479. }
  480. }
  481. pub trait IntoVNode<'a> {
  482. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
  483. }
  484. pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
  485. where
  486. G: IntoVNode<'a>,
  487. {
  488. }
  489. impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
  490. F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
  491. {
  492. }
  493. // Cover the cases where nodes are pre-rendered.
  494. // Likely used by enums.
  495. // ----
  496. // let nodes = ctx.render(rsx!{ ... };
  497. // rsx! { {nodes } }
  498. // impl<'a> IntoVNode<'a> for VNode {
  499. // fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
  500. // self.root
  501. // }
  502. // }
  503. // Wrap the the node-builder closure in a concrete type.
  504. // ---
  505. // This is a bit of a hack to implement the IntoVNode trait for closure types.
  506. pub struct LazyNodes<'a, G>
  507. where
  508. G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
  509. {
  510. inner: G,
  511. _p: std::marker::PhantomData<&'a ()>,
  512. }
  513. impl<'a, G> LazyNodes<'a, G>
  514. where
  515. G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
  516. {
  517. pub fn new(f: G) -> Self {
  518. Self {
  519. inner: f,
  520. _p: std::default::Default::default(),
  521. }
  522. }
  523. }
  524. // Cover the cases where nodes are used by macro.
  525. // Likely used directly.
  526. // ---
  527. // let nodes = rsx!{ ... };
  528. // rsx! { {nodes } }
  529. impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
  530. where
  531. G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
  532. {
  533. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
  534. (self.inner)(ctx)
  535. }
  536. }
  537. // Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
  538. impl<'a, G> IntoIterator for LazyNodes<'a, G>
  539. where
  540. G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
  541. {
  542. type Item = Self;
  543. type IntoIter = std::iter::Once<Self::Item>;
  544. fn into_iter(self) -> Self::IntoIter {
  545. std::iter::once(self)
  546. }
  547. }
  548. impl<'a> IntoVNode<'a> for () {
  549. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
  550. VNode::Suspended
  551. }
  552. }
  553. impl<'a> IntoVNode<'a> for Option<()> {
  554. fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
  555. VNode::Suspended
  556. }
  557. }
  558. /// Construct a text VNode.
  559. ///
  560. /// This is `dioxus`'s virtual DOM equivalent of `document.createTextVNode`.
  561. ///
  562. /// # Example
  563. ///
  564. /// ```no_run
  565. /// use dioxus::builder::*;
  566. ///
  567. /// let my_text = text("hello, dioxus!");
  568. /// ```
  569. #[inline]
  570. pub fn text<'a>(contents: &'a str) -> VNode<'a> {
  571. VNode::text(contents)
  572. }
  573. pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
  574. let f: &'a str = contents.into_bump_str();
  575. VNode::text(f)
  576. }
  577. pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a> {
  578. // This is a cute little optimization
  579. //
  580. // We use format_args! on everything. However, not all textnodes have dynamic content, and thus are completely static.
  581. // we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
  582. //
  583. // In the most general case, this prevents the need for any string allocations for simple code IE:
  584. // div {"abc"}
  585. //
  586. match args.as_str() {
  587. Some(static_str) => VNode::text(static_str),
  588. None => {
  589. use bumpalo::core_alloc::fmt::Write;
  590. let mut s = bumpalo::collections::String::new_in(bump);
  591. s.write_fmt(args).unwrap();
  592. VNode::text(s.into_bump_str())
  593. }
  594. }
  595. }
  596. /// Construct an attribute for an element.
  597. ///
  598. /// # Example
  599. ///
  600. /// This example creates the `id="my-id"` for some element like `<div
  601. /// id="my-id"/>`.
  602. ///
  603. /// ```no_run
  604. /// use dioxus::builder::*;
  605. ///
  606. /// let my_id_attr = attr("id", "my-id");
  607. /// ```
  608. pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
  609. Attribute { name, value }
  610. }
  611. pub fn virtual_child<'a, T: Properties>(
  612. ctx: &NodeCtx<'a>,
  613. f: FC<T>,
  614. props: T,
  615. key: Option<&'a str>, // key: NodeKey<'a>,
  616. ) -> VNode<'a> {
  617. // currently concerned about if props have a custom drop implementation
  618. // might override it with the props macro
  619. todo!()
  620. // VNode::Component(
  621. // ctx.bump()
  622. // .alloc(crate::nodes::VComponent::new(f, props, key)),
  623. // )
  624. }