Explorar o código

feat: implement type magic to allow strings, format args, and other types directly in rsx (#550)

* feat: implement type magic

* chore: undo example

* fix: let tests pass

* chore: add generic to allow any nesting of iterators

* Chore: remove comments

* chore: update rsx usage

* chore: use cleaner version of generic IntoVnode

* chore: don't derive default for lfietimed thing

* chore: remove latent comment

* fix: accept a third parameter
Jon Kelley %!s(int64=2) %!d(string=hai) anos
pai
achega
67dc6e6017

+ 2 - 2
examples/rsx_usage.rs

@@ -203,8 +203,8 @@ fn app(cx: Scope) -> Element {
             self::lowercase_helper {}
             self::lowercase_helper {}
 
 
             // helper functions
             // helper functions
-            // Single values must be wrapped in braces or `Some` to satisfy `IntoIterator`
-            [helper(&cx, "hello world!")]
+            // Anything that implements IntoVnode can be dropped directly into Rsx
+            helper(&cx, "hello world!")
         }
         }
     })
     })
 }
 }

+ 19 - 0
examples/simple_list.rs

@@ -0,0 +1,19 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx!(
+        // Use Map directly to lazily pull elements
+        (0..10).map(|f| rsx! { "{f}" }),
+        // Collect into an intermediate collection if necessary
+        ["a", "b", "c"]
+            .into_iter()
+            .map(|f| rsx! { "{f}" })
+            .collect::<Vec<_>>(),
+        // Use optionals
+        Some(rsx! { "Some" }),
+    ))
+}

+ 1 - 1
packages/autofmt/src/lib.rs

@@ -16,7 +16,7 @@ mod util;
 ///
 ///
 /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
 /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
 /// multiple edits are applied in a single file without tracking text shifts.
 /// multiple edits are applied in a single file without tracking text shifts.
-#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)]
+#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct FormattedBlock {
 pub struct FormattedBlock {
     /// The new contents of the block
     /// The new contents of the block
     pub formatted: String,
     pub formatted: String,

+ 7 - 9
packages/core/src/lazynodes.rs

@@ -288,16 +288,14 @@ mod tests {
             }
             }
 
 
             let caller = {
             let caller = {
-                let it = (0..10)
-                    .map(|i| {
-                        let val = cx.props.inner.clone();
-                        LazyNodes::new(move |f| {
-                            eprintln!("hell closure");
-                            let inner = DropInner { id: i };
-                            f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
-                        })
+                let it = (0..10).map(|i| {
+                    let val = cx.props.inner.clone();
+                    LazyNodes::new(move |f| {
+                        eprintln!("hell closure");
+                        let inner = DropInner { id: i };
+                        f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
                     })
                     })
-                    .collect::<Vec<_>>();
+                });
 
 
                 LazyNodes::new(|f| {
                 LazyNodes::new(|f| {
                     eprintln!("main closure");
                     eprintln!("main closure");

+ 49 - 86
packages/core/src/nodes.rs

@@ -2,7 +2,6 @@
 //!
 //!
 //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
 //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
 //! cheap and *very* fast to construct - building a full tree should be quick.
 //! cheap and *very* fast to construct - building a full tree should be quick.
-
 use crate::{
 use crate::{
     innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
     innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
     lazynodes::LazyNodes,
     lazynodes::LazyNodes,
@@ -375,7 +374,6 @@ pub struct Listener<'bump> {
 
 
 pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
 pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
 type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
 type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
-
 type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
 type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
 
 
 /// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
 /// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
@@ -706,43 +704,11 @@ impl<'a> NodeFactory<'a> {
     }
     }
 
 
     /// Create a new [`VNode::Fragment`] from any iterator
     /// Create a new [`VNode::Fragment`] from any iterator
-    pub fn fragment_from_iter<'b, 'c>(
+    pub fn fragment_from_iter<'c, I, J>(
         self,
         self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
+        node_iter: impl IntoVNode<'a, I, J> + 'c,
     ) -> VNode<'a> {
     ) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
-
-        for node in node_iter {
-            nodes.push(node.into_vnode(self));
-        }
-
-        if nodes.is_empty() {
-            VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
-        } else {
-            let children = nodes.into_bump_slice();
-
-            if cfg!(debug_assertions)
-                && children.len() > 1
-                && children.last().unwrap().key().is_none()
-            {
-                // todo: make the backtrace prettier or remove it altogether
-                log::error!(
-                    r#"
-                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.
-                See docs.rs/dioxus for more information.
-                -------------
-                {:?}
-                "#,
-                    backtrace::Backtrace::new()
-                );
-            }
-
-            VNode::Fragment(self.bump.alloc(VFragment {
-                children,
-                key: None,
-            }))
-        }
+        node_iter.into_vnode(self)
     }
     }
 
 
     /// Create a new [`VNode`] from any iterator of children
     /// Create a new [`VNode`] from any iterator of children
@@ -800,20 +766,11 @@ impl Debug for NodeFactory<'_> {
 ///
 ///
 /// As such, all node creation must go through the factory, which is only available in the component context.
 /// As such, all node creation must go through the factory, which is only available in the component context.
 /// These strict requirements make it possible to manage lifetimes and state.
 /// These strict requirements make it possible to manage lifetimes and state.
-pub trait IntoVNode<'a> {
+pub trait IntoVNode<'a, I = (), J = ()> {
     /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
     /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
 }
 }
 
 
-// For the case where a rendered VNode is passed into the rsx! macro through curly braces
-impl<'a> IntoIterator for VNode<'a> {
-    type Item = VNode<'a>;
-    type IntoIter = std::iter::Once<Self::Item>;
-    fn into_iter(self) -> Self::IntoIter {
-        std::iter::once(self)
-    }
-}
-
 // TODO: do we even need this? It almost seems better not to
 // TODO: do we even need this? It almost seems better not to
 // // For the case where a rendered VNode is passed into the rsx! macro through curly braces
 // // For the case where a rendered VNode is passed into the rsx! macro through curly braces
 impl<'a> IntoVNode<'a> for VNode<'a> {
 impl<'a> IntoVNode<'a> for VNode<'a> {
@@ -825,37 +782,7 @@ impl<'a> IntoVNode<'a> for VNode<'a> {
 // Conveniently, we also support "null" (nothing) passed in
 // Conveniently, we also support "null" (nothing) passed in
 impl IntoVNode<'_> for () {
 impl IntoVNode<'_> for () {
     fn into_vnode(self, cx: NodeFactory) -> VNode {
     fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.fragment_from_iter(None as Option<VNode>)
-    }
-}
-
-// Conveniently, we also support "None"
-impl IntoVNode<'_> for Option<()> {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.fragment_from_iter(None as Option<VNode>)
-    }
-}
-
-impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        self.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
-    }
-}
-
-impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        match self {
-            Some(lazy) => lazy.call(cx),
-            None => VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })),
-        }
-    }
-}
-
-impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> {
-    type Item = LazyNodes<'a, 'b>;
-    type IntoIter = std::iter::Once<Self::Item>;
-    fn into_iter(self) -> Self::IntoIter {
-        std::iter::once(self)
+        VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
     }
     }
 }
 }
 
 
@@ -883,17 +810,53 @@ impl IntoVNode<'_> for Arguments<'_> {
     }
     }
 }
 }
 
 
-impl<'a> IntoVNode<'a> for &Option<VNode<'a>> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        self.as_ref()
-            .map(|f| f.into_vnode(cx))
-            .unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
-    }
-}
-
 impl<'a> IntoVNode<'a> for &VNode<'a> {
 impl<'a> IntoVNode<'a> for &VNode<'a> {
     fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
     fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
         // borrowed nodes are strange
         // borrowed nodes are strange
         self.decouple()
         self.decouple()
     }
     }
 }
 }
+
+// Note that we're using the E as a generic but this is never crafted anyways.
+pub struct FromNodeIterator;
+impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
+where
+    T: IntoIterator<Item = I>,
+    I: IntoVNode<'a, E>,
+{
+    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
+        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
+
+        for node in self {
+            nodes.push(node.into_vnode(cx));
+        }
+
+        if nodes.is_empty() {
+            VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
+        } else {
+            let children = nodes.into_bump_slice();
+
+            if cfg!(debug_assertions)
+                && children.len() > 1
+                && children.last().unwrap().key().is_none()
+            {
+                // todo: make the backtrace prettier or remove it altogether
+                log::error!(
+                    r#"
+                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.
+                See docs.rs/dioxus for more information.
+                -------------
+                {:?}
+                "#,
+                    backtrace::Backtrace::new()
+                );
+            }
+
+            VNode::Fragment(cx.bump.alloc(VFragment {
+                children,
+                key: None,
+            }))
+        }
+    }
+}

+ 1 - 1
packages/dioxus/tests/borrowedstate.rs

@@ -8,7 +8,7 @@ fn test_borrowed_state() {
 }
 }
 
 
 fn Parent(cx: Scope) -> Element {
 fn Parent(cx: Scope) -> Element {
-    let value = cx.use_hook(|| String::new());
+    let value = cx.use_hook(String::new);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {

+ 1 - 1
packages/dioxus/tests/create_dom.rs

@@ -157,7 +157,7 @@ fn create_components() {
     fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
     fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
         cx.render(rsx! {
         cx.render(rsx! {
             h1 {}
             h1 {}
-            div { {&cx.props.children} }
+            div { &cx.props.children }
             p {}
             p {}
         })
         })
     }
     }