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
This commit is contained in:
1 parent
38e8745db9
commit
67dc6e6017
7 files changed
+80
-100
No files matched your search
@@ -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`
|
// Anything that implements IntoVnode can be dropped directly into Rsx
|
||||||
[helper(&cx, "hello world!")]
|
helper(&cx, "hello world!")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" }),
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -288,16 +288,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let caller = {
|
let caller = {
|
||||||
let it = (0..10)
|
let it = (0..10).map(|i| {
|
||||||
.map(|i| {
|
let val = cx.props.inner.clone();
|
||||||
let val = cx.props.inner.clone();
|
LazyNodes::new(move |f| {
|
||||||
LazyNodes::new(move |f| {
|
eprintln!("hell closure");
|
||||||
eprintln!("hell closure");
|
let inner = DropInner { id: i };
|
||||||
let inner = DropInner { id: i };
|
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
|
||||||
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
@@ -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);
|
node_iter.into_vnode(self)
|
||||||
|
|
||||||
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,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>)
|
VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user