mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Improve nested html! expansion by unwrapping VNodes (#820)
* Improve nested html! expansion by omitting VNodes * Add unittest and fix child node expansion * Fix existing unittests and check backward compat * Fix rebase conflicts * Parse expression * Fix clippy Co-authored-by: Justin Starry <justin.starry@icloud.com>
This commit is contained in:
parent
b801d4fbf9
commit
a900fbee49
@ -23,7 +23,7 @@ lazy_static = "1.3.0"
|
|||||||
proc-macro-hack = "0.5"
|
proc-macro-hack = "0.5"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
syn = { version = "1.0", features = ["full", "extra-traits", "visit-mut"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
yew = { path = "../.." }
|
yew = { path = "../.." }
|
||||||
|
|||||||
@ -26,7 +26,7 @@ impl PeekValue<()> for HtmlBlock {
|
|||||||
|
|
||||||
impl Parse for HtmlBlock {
|
impl Parse for HtmlBlock {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
let content;
|
let content: syn::parse::ParseBuffer<'_>;
|
||||||
let brace = braced!(content in input);
|
let brace = braced!(content in input);
|
||||||
let content = if HtmlIterable::peek(content.cursor()).is_some() {
|
let content = if HtmlIterable::peek(content.cursor()).is_some() {
|
||||||
BlockContent::Iterable(content.parse()?)
|
BlockContent::Iterable(content.parse()?)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use syn::parse;
|
|||||||
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Expr, Ident, Path, PathArguments, PathSegment, Token, Type, TypePath};
|
use syn::{Expr, Ident, Index, Path, PathArguments, PathSegment, Token, Type, TypePath};
|
||||||
|
|
||||||
pub struct HtmlComponent {
|
pub struct HtmlComponent {
|
||||||
ty: Type,
|
ty: Type,
|
||||||
@ -123,10 +123,17 @@ impl ToTokens for HtmlComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let set_children = if !children.is_empty() {
|
let set_children = if !children.is_empty() {
|
||||||
|
let i = (0..children.len())
|
||||||
|
.map(|x| Index::from(x))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
quote! {
|
quote! {
|
||||||
.children(::yew::html::ChildrenRenderer::new(
|
.children(::yew::html::ChildrenRenderer::new({
|
||||||
vec![#(#children.into(),)*]
|
let mut v = Vec::new();
|
||||||
))
|
let comps = (#(#children,)*);
|
||||||
|
#(::yew::utils::NodeSeq::from(comps.#i).into_iter()
|
||||||
|
.for_each(|x| v.push(x.into()));)*
|
||||||
|
v
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
|
|||||||
@ -7,6 +7,10 @@ use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Expr, Token};
|
use syn::{Expr, Token};
|
||||||
|
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use syn::visit_mut::{self, VisitMut};
|
||||||
|
use syn::Macro;
|
||||||
|
|
||||||
pub struct HtmlIterable(Expr);
|
pub struct HtmlIterable(Expr);
|
||||||
|
|
||||||
impl PeekValue<()> for HtmlIterable {
|
impl PeekValue<()> for HtmlIterable {
|
||||||
@ -16,12 +20,28 @@ impl PeekValue<()> for HtmlIterable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HtmlInnerModifier;
|
||||||
|
impl VisitMut for HtmlInnerModifier {
|
||||||
|
fn visit_macro_mut(&mut self, node: &mut Macro) {
|
||||||
|
if node.path.is_ident("html") {
|
||||||
|
let ident = &mut node.path.segments.last_mut().unwrap().ident;
|
||||||
|
*ident = Ident::new("html_nested", Span::call_site());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate to the default impl to visit any nested functions.
|
||||||
|
visit_mut::visit_macro_mut(self, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Parse for HtmlIterable {
|
impl Parse for HtmlIterable {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
let for_token = input.parse::<Token![for]>()?;
|
let for_token = input.parse::<Token![for]>()?;
|
||||||
|
|
||||||
match input.parse() {
|
match input.parse() {
|
||||||
Ok(expr) => Ok(HtmlIterable(expr)),
|
Ok(mut expr) => {
|
||||||
|
HtmlInnerModifier.visit_expr_mut(&mut expr);
|
||||||
|
Ok(HtmlIterable(expr))
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.to_string().starts_with("unexpected end of input") {
|
if err.to_string().starts_with("unexpected end of input") {
|
||||||
Err(syn::Error::new_spanned(
|
Err(syn::Error::new_spanned(
|
||||||
|
|||||||
@ -4,8 +4,26 @@ use quote::{quote, quote_spanned, ToTokens};
|
|||||||
use syn::buffer::Cursor;
|
use syn::buffer::Cursor;
|
||||||
use syn::parse::{Parse, ParseStream, Result};
|
use syn::parse::{Parse, ParseStream, Result};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
use syn::Expr;
|
||||||
use syn::Lit;
|
use syn::Lit;
|
||||||
|
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use syn::visit_mut::{self, VisitMut};
|
||||||
|
use syn::Macro;
|
||||||
|
|
||||||
|
struct HtmlInnerModifier;
|
||||||
|
impl VisitMut for HtmlInnerModifier {
|
||||||
|
fn visit_macro_mut(&mut self, node: &mut Macro) {
|
||||||
|
if node.path.is_ident("html") {
|
||||||
|
let ident = &mut node.path.segments.last_mut().unwrap().ident;
|
||||||
|
*ident = Ident::new("html_nested", Span::call_site());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate to the default impl to visit any nested functions.
|
||||||
|
visit_mut::visit_macro_mut(self, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct HtmlNode(Node);
|
pub struct HtmlNode(Node);
|
||||||
|
|
||||||
impl Parse for HtmlNode {
|
impl Parse for HtmlNode {
|
||||||
@ -18,7 +36,9 @@ impl Parse for HtmlNode {
|
|||||||
}
|
}
|
||||||
Node::Literal(lit)
|
Node::Literal(lit)
|
||||||
} else {
|
} else {
|
||||||
Node::Raw(input.parse()?)
|
let mut expr: Expr = input.parse()?;
|
||||||
|
HtmlInnerModifier.visit_expr_mut(&mut expr);
|
||||||
|
Node::Expression(expr)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(HtmlNode(node))
|
Ok(HtmlNode(node))
|
||||||
@ -46,12 +66,8 @@ impl ToTokens for HtmlNode {
|
|||||||
impl ToTokens for Node {
|
impl ToTokens for Node {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let node_token = match &self {
|
let node_token = match &self {
|
||||||
Node::Literal(lit) => quote! {
|
Node::Literal(lit) => quote! {#lit},
|
||||||
::yew::virtual_dom::VNode::from(#lit)
|
Node::Expression(expr) => quote_spanned! {expr.span()=> {#expr} },
|
||||||
},
|
|
||||||
Node::Raw(stream) => quote_spanned! {stream.span()=>
|
|
||||||
::yew::virtual_dom::VNode::from({#stream})
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.extend(node_token);
|
tokens.extend(node_token);
|
||||||
@ -60,5 +76,5 @@ impl ToTokens for Node {
|
|||||||
|
|
||||||
enum Node {
|
enum Node {
|
||||||
Literal(Lit),
|
Literal(Lit),
|
||||||
Raw(TokenStream),
|
Expression(Expr),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,19 @@ impl HtmlTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct HtmlRootNested(HtmlTreeNested);
|
||||||
|
impl Parse for HtmlRootNested {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(HtmlRootNested(HtmlTreeNested::parse(input)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for HtmlRootNested {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
self.0.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct HtmlTreeNested(HtmlTree);
|
pub struct HtmlTreeNested(HtmlTree);
|
||||||
impl Parse for HtmlTreeNested {
|
impl Parse for HtmlTreeNested {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
|||||||
@ -63,7 +63,7 @@ mod derive_props;
|
|||||||
mod html_tree;
|
mod html_tree;
|
||||||
|
|
||||||
use derive_props::DerivePropsInput;
|
use derive_props::DerivePropsInput;
|
||||||
use html_tree::HtmlRoot;
|
use html_tree::{HtmlRoot, HtmlRootNested};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro_hack::proc_macro_hack;
|
use proc_macro_hack::proc_macro_hack;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
@ -94,6 +94,12 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
|
|||||||
TokenStream::from(input.into_token_stream())
|
TokenStream::from(input.into_token_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_hack]
|
||||||
|
pub fn html_nested(input: TokenStream) -> TokenStream {
|
||||||
|
let root = parse_macro_input!(input as HtmlRootNested);
|
||||||
|
TokenStream::from(quote! {#root})
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro_hack]
|
#[proc_macro_hack]
|
||||||
pub fn html(input: TokenStream) -> TokenStream {
|
pub fn html(input: TokenStream) -> TokenStream {
|
||||||
let root = parse_macro_input!(input as HtmlRoot);
|
let root = parse_macro_input!(input as HtmlRoot);
|
||||||
|
|||||||
@ -72,9 +72,14 @@ use proc_macro_hack::proc_macro_hack;
|
|||||||
#[proc_macro_hack(support_nested)]
|
#[proc_macro_hack(support_nested)]
|
||||||
pub use yew_macro::html;
|
pub use yew_macro::html;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[proc_macro_hack(support_nested)]
|
||||||
|
pub use yew_macro::html_nested;
|
||||||
|
|
||||||
/// This module contains macros which implements html! macro and JSX-like templates
|
/// This module contains macros which implements html! macro and JSX-like templates
|
||||||
pub mod macros {
|
pub mod macros {
|
||||||
pub use crate::html;
|
pub use crate::html;
|
||||||
|
pub use crate::html_nested;
|
||||||
pub use yew_macro::Properties;
|
pub use yew_macro::Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +163,7 @@ pub mod prelude {
|
|||||||
Renderable, ShouldRender,
|
Renderable, ShouldRender,
|
||||||
};
|
};
|
||||||
pub use crate::macros::*;
|
pub use crate::macros::*;
|
||||||
|
pub use crate::utils::NodeSeq;
|
||||||
pub use crate::virtual_dom::Classes;
|
pub use crate::virtual_dom::Classes;
|
||||||
|
|
||||||
/// Prelude module for creating worker.
|
/// Prelude module for creating worker.
|
||||||
|
|||||||
29
src/utils.rs
29
src/utils.rs
@ -3,6 +3,8 @@
|
|||||||
use failure::{err_msg, Error};
|
use failure::{err_msg, Error};
|
||||||
use stdweb::web::document;
|
use stdweb::web::document;
|
||||||
|
|
||||||
|
use crate::virtual_dom::VNode;
|
||||||
|
|
||||||
/// Returns `host` for the current document. Useful to connect to a server that server the app.
|
/// Returns `host` for the current document. Useful to connect to a server that server the app.
|
||||||
pub fn host() -> Result<String, Error> {
|
pub fn host() -> Result<String, Error> {
|
||||||
document()
|
document()
|
||||||
@ -10,3 +12,30 @@ pub fn host() -> Result<String, Error> {
|
|||||||
.ok_or_else(|| err_msg("can't get location"))
|
.ok_or_else(|| err_msg("can't get location"))
|
||||||
.and_then(|l| l.host().map_err(Error::from))
|
.and_then(|l| l.host().map_err(Error::from))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specialty type necessary for helping flattening components returned from nested html macros.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NodeSeq<T>(Vec<T>)
|
||||||
|
where
|
||||||
|
T: Into<VNode>;
|
||||||
|
|
||||||
|
impl<T: Into<VNode>> From<T> for NodeSeq<T> {
|
||||||
|
fn from(val: T) -> Self {
|
||||||
|
NodeSeq(vec![val])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<VNode>> From<Vec<T>> for NodeSeq<T> {
|
||||||
|
fn from(val: Vec<T>) -> Self {
|
||||||
|
NodeSeq(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<VNode>> IntoIterator for NodeSeq<T> {
|
||||||
|
type Item = T;
|
||||||
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -188,13 +188,13 @@ error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<
|
|||||||
|
|
|
|
||||||
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<&str>` is not satisfied
|
||||||
--> $DIR/html-component-fail.rs:78:5
|
--> $DIR/html-component-fail.rs:78:5
|
||||||
|
|
|
|
||||||
78 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
78 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
||||||
|
|
|
|
||||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vnode::VNode`
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `&str`
|
||||||
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
||||||
|
|||||||
@ -2,6 +2,79 @@
|
|||||||
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew::html::ChildrenRenderer;
|
use yew::html::ChildrenRenderer;
|
||||||
|
use yew::virtual_dom::{VChild, VComp, VNode};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Properties)]
|
||||||
|
pub struct ParentProperties {
|
||||||
|
#[props(required)]
|
||||||
|
pub children: ChildrenRenderer<ParentVariant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Parent {
|
||||||
|
props: ParentProperties,
|
||||||
|
link: ComponentLink<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Parent {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ParentProperties;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
|
return Parent { props, link };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ParentVariants {
|
||||||
|
Child(<Child as Component>::Properties),
|
||||||
|
ChildA(<ChildA as Component>::Properties),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChildProperties> for ParentVariants {
|
||||||
|
fn from(props: ChildProperties) -> Self {
|
||||||
|
ParentVariants::Child(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChildAProperties> for ParentVariants {
|
||||||
|
fn from(props: ChildAProperties) -> Self {
|
||||||
|
ParentVariants::ChildA(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ParentVariant {
|
||||||
|
props: ParentVariants,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CHILD> From<VChild<CHILD>> for ParentVariant
|
||||||
|
where
|
||||||
|
CHILD: Component,
|
||||||
|
CHILD::Properties: Into<ParentVariants>,
|
||||||
|
{
|
||||||
|
fn from(comp: VChild<CHILD>) -> Self {
|
||||||
|
return ParentVariant {
|
||||||
|
props: comp.props.into(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<VNode> for ParentVariant {
|
||||||
|
fn into(self) -> VNode {
|
||||||
|
match self.props {
|
||||||
|
ParentVariants::Child(props) => VComp::new::<Child>(props, NodeRef::default()).into(),
|
||||||
|
ParentVariants::ChildA(props) => VComp::new::<ChildA>(props, NodeRef::default()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Properties, Default, PartialEq)]
|
#[derive(Clone, Properties, Default, PartialEq)]
|
||||||
pub struct ChildProperties {
|
pub struct ChildProperties {
|
||||||
@ -30,6 +103,33 @@ impl Component for Child {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Properties, Default, PartialEq)]
|
||||||
|
pub struct ChildAProperties {
|
||||||
|
pub string: String,
|
||||||
|
#[props(required)]
|
||||||
|
pub int: i32,
|
||||||
|
pub vec: Vec<i32>,
|
||||||
|
pub optional_callback: Option<Callback<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChildA;
|
||||||
|
impl Component for ChildA {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ChildAProperties;
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
ChildA
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Properties, Default)]
|
#[derive(Clone, Properties, Default)]
|
||||||
pub struct ContainerProperties {
|
pub struct ContainerProperties {
|
||||||
#[props(required)]
|
#[props(required)]
|
||||||
@ -178,6 +278,22 @@ fn compile_pass() {
|
|||||||
<ChildContainer int=1><Child int = 2 /><Child int = 2 /></ChildContainer>
|
<ChildContainer int=1><Child int = 2 /><Child int = 2 /></ChildContainer>
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<Parent>
|
||||||
|
<ChildA int=1 />
|
||||||
|
{
|
||||||
|
html! {
|
||||||
|
<Child int=1 />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{(0..2).map(|_| {
|
||||||
|
return html! {
|
||||||
|
<Child int=1 />
|
||||||
|
}
|
||||||
|
}).collect::<Vec<VChild<Child>>>()}
|
||||||
|
</Parent>
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user