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:
Jon Kelley 2022-09-12 22:49:04 -07:00 committed by GitHub
parent 38e8745db9
commit 67dc6e6017
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 100 deletions

View file

@ -203,8 +203,8 @@ fn app(cx: Scope) -> Element {
self::lowercase_helper {}
// 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
examples/simple_list.rs Normal file
View file

@ -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" }),
))
}

View file

@ -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
/// 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 {
/// The new contents of the block
pub formatted: String,

View file

@ -288,16 +288,14 @@ mod tests {
}
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| {
eprintln!("main closure");

View file

@ -2,7 +2,6 @@
//!
//! 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.
use crate::{
innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
lazynodes::LazyNodes,
@ -375,7 +374,6 @@ pub struct Listener<'bump> {
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + '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.
@ -706,43 +704,11 @@ impl<'a> NodeFactory<'a> {
}
/// Create a new [`VNode::Fragment`] from any iterator
pub fn fragment_from_iter<'b, 'c>(
pub fn fragment_from_iter<'c, I, J>(
self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
node_iter: impl IntoVNode<'a, I, J> + 'c,
) -> 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
@ -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.
/// 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
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
// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
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
impl IntoVNode<'_> for () {
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> {
fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
// borrowed nodes are strange
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,
}))
}
}
}

View file

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

View file

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