Feat: massive changes to definition of components
This change switches back to the original `ctx<props>` syntax for commponents. This lets lifetime elision to remove the need to match exactly which lifetime (props or ctx) gets carried to the output. As such, `Props` is currently required to be static. It *is* possible to loosen this restriction, and will be done in the future, though only through adding metadata about the props through the Props derive macro. Implementing the IS_STATIC trait is unsafe, so the derive macro will do it through some heuristics. For now, this unlocks sharing vnodes from parents to children, enabling pass-thru components, fragments, portals, etc.
This commit is contained in:
1 parent
c1fd848f89
commit
508c560320
82 files changed
+692
-558
No files matched your search
@@ -14,9 +14,9 @@ proc-macro = true
|
||||
[dependencies]
|
||||
once_cell = "1.7.2"
|
||||
proc-macro-hack = "0.5.19"
|
||||
proc-macro2 = "1.0.6"
|
||||
proc-macro2 = { version = "1.0.6" }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
|
||||
# testing
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -5,9 +5,7 @@ use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Signature,
|
||||
};
|
||||
use syn::{
|
||||
Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
|
||||
};
|
||||
use syn::{Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility};
|
||||
|
||||
/// A parsed version of the user's input
|
||||
pub struct FunctionComponent {
|
||||
@@ -135,7 +133,7 @@ impl ToTokens for FunctionComponent {
|
||||
}
|
||||
|
||||
impl<'a> FC for #function_name<'a> {
|
||||
fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree {
|
||||
fn render(ctx: Context<'_>, props: &#function_name<'a>) -> VNode {
|
||||
let #function_name {
|
||||
..
|
||||
} = props;
|
||||
@@ -244,7 +242,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
|
||||
match output {
|
||||
ReturnType::Default => Err(syn::Error::new_spanned(
|
||||
output,
|
||||
"function components must return a `DomTree`",
|
||||
"function components must return a `VNode`",
|
||||
)),
|
||||
ReturnType::Type(_, ty) => Ok(ty),
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ pub fn rsx_template(s: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
|
||||
/// We aim to keep functional parity with html templates.
|
||||
#[proc_macro]
|
||||
pub fn html_template(s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsxtemplate::RsxTemplate>(s) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
// #[proc_macro_attribute]
|
||||
// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
@@ -51,7 +61,7 @@ pub fn rsx_template(s: TokenStream) -> TokenStream {
|
||||
/// ```ignore
|
||||
///
|
||||
/// #[fc]
|
||||
/// fn Example(ctx: Context, name: &str) -> DomTree {
|
||||
/// fn Example(ctx: Context, name: &str) -> VNode {
|
||||
/// ctx.render(rsx! { h1 {"hello {name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -22,7 +22,19 @@ impl Parse for RsxTemplate {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
if s.peek(LitStr) {
|
||||
use std::str::FromStr;
|
||||
|
||||
let lit = s.parse::<LitStr>()?;
|
||||
let g = lit.span();
|
||||
let mut value = lit.value();
|
||||
if value.ends_with('\n') {
|
||||
value.pop();
|
||||
if value.ends_with('\r') {
|
||||
value.pop();
|
||||
}
|
||||
}
|
||||
let lit = LitStr::new(&value, lit.span());
|
||||
|
||||
// panic!("{:#?}", lit);
|
||||
match lit.parse::<crate::rsxt::RsxRender>() {
|
||||
Ok(r) => Ok(Self { inner: r }),
|
||||
Err(e) => Err(e),
|
||||
|
||||
@@ -28,7 +28,7 @@ We have big goals for Dioxus. The final implementation must:
|
||||
## Optimizations
|
||||
|
||||
- Support a pluggable allocation strategy that makes VNode creation **very** fast
|
||||
- Support lazy DomTrees (ie DomTrees that are not actually created when the html! macro is used)
|
||||
- Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
|
||||
- Support advanced diffing strategies (patience, Meyers, etc)
|
||||
|
||||
## Design Quirks
|
||||
|
||||
@@ -2,7 +2,7 @@ fn main() {}
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(dioxus_core::prelude::LazyNodes::new(move |ctx| {
|
||||
let bump = ctx.bump();
|
||||
dioxus::builder::ElementBuilder::new(ctx, "h1")
|
||||
@@ -15,3 +15,16 @@ static Example: FC<()> = |ctx, props| {
|
||||
.finish()
|
||||
}))
|
||||
};
|
||||
|
||||
struct Props {
|
||||
text: String,
|
||||
}
|
||||
static Example2: FC<Props> = |ctx| {
|
||||
ctx.render(dioxus_core::prelude::LazyNodes::new(move |__ctx| {
|
||||
let bump = __ctx.bump();
|
||||
dioxus::builder::ElementBuilder::new(__ctx, "h1")
|
||||
.children([{ dioxus::builder::text3(bump, format_args!("{}", ctx.text)) }])
|
||||
// .children([{ dioxus::builder::text3(bump, format_args!("hello")) }])
|
||||
.finish()
|
||||
}))
|
||||
};
|
||||
@@ -21,12 +21,12 @@ struct ListItem {
|
||||
age: u32,
|
||||
}
|
||||
|
||||
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
|
||||
fn app(ctx: Context<Props>) -> VNode {
|
||||
let (val, set_val) = use_state(&ctx, || 0);
|
||||
|
||||
ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
|
||||
let mut root = builder::ElementBuilder::new(c, "div");
|
||||
for child in &props.items {
|
||||
for child in &ctx.items {
|
||||
// notice that the child directly borrows from our vec
|
||||
// this makes lists very fast (simply views reusing lifetimes)
|
||||
// <ChildItem item=child hanldler=setter />
|
||||
@@ -55,11 +55,11 @@ struct ChildProps {
|
||||
item_handler: Callback<i32>,
|
||||
}
|
||||
|
||||
fn ChildItem<'a>(ctx: Context<'a>, props: &ChildProps) -> DomTree {
|
||||
fn ChildItem<'a>(ctx: Context<ChildProps>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
onclick: move |evt| (props.item_handler)(10)
|
||||
h1 { "abcd123" }
|
||||
// onclick: move |evt| (ctx.item_handler)(10)
|
||||
h1 { "abcd123 {ctx.item.name}" }
|
||||
h2 { "abcd123" }
|
||||
div {
|
||||
"abcd123"
|
||||
|
||||
@@ -16,7 +16,7 @@ fn main() {
|
||||
};
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let nodes = ctx.children();
|
||||
|
||||
//
|
||||
@@ -26,43 +26,3 @@ static Example: FC<()> = |ctx, props| {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct MyContext<'a, T> {
|
||||
props: &'a T,
|
||||
inner: &'a Scope,
|
||||
}
|
||||
impl<'a, T> MyContext<'a, T> {
|
||||
fn children(&self) -> Vec<VNode<'a>> {
|
||||
todo!()
|
||||
}
|
||||
pub fn render2<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
&self,
|
||||
lazy_nodes: LazyNodes<'a, F>,
|
||||
) -> VNode<'a> {
|
||||
self.inner.render2(lazy_nodes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for MyContext<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.props
|
||||
}
|
||||
}
|
||||
|
||||
struct MyProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn example(scope: MyContext<MyProps>) -> VNode {
|
||||
let childs = scope.children();
|
||||
|
||||
scope.inner.render2(rsx! {
|
||||
div {
|
||||
"{scope.title}"
|
||||
{childs}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -10,19 +10,20 @@ struct Props {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
static Example: FC<Props> = |ctx, props| {
|
||||
static Example: FC<Props> = |cpt| {
|
||||
todo!()
|
||||
|
||||
// let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
|
||||
|
||||
// ctx.render(LazyNodes::new(move |bump| {
|
||||
// builder::ElementBuilder::new(bump, "button")
|
||||
// .on("click", move |_| {
|
||||
// println!("Value is {}", props.name);
|
||||
// println!("Value is {}", ctx.name);
|
||||
// println!("Value is {}", value.as_str());
|
||||
// println!("Value is {}", *value);
|
||||
// })
|
||||
// .on("click", move |_| {
|
||||
// println!("Value is {}", props.name);
|
||||
// println!("Value is {}", ctx.name);
|
||||
// })
|
||||
// .finish()
|
||||
// }))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dioxus_core::component::fc_to_builder;
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
static BLAH: FC<()> = |ctx, _props| {
|
||||
static BLAH: FC<()> = |ctx| {
|
||||
let g = "asd".to_string();
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
@@ -17,7 +17,7 @@ pub struct ExampleProps {
|
||||
some_field: String,
|
||||
}
|
||||
|
||||
static SomeComponent: FC<ExampleProps> = |ctx, _props| {
|
||||
static SomeComponent: FC<ExampleProps> = |ctx| {
|
||||
let blah = rsx! {
|
||||
div {}
|
||||
};
|
||||
|
||||
@@ -10,14 +10,21 @@ fn main() {
|
||||
.map(|f| f / 3);
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (name, set_name) = use_state(&ctx, || "...?");
|
||||
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "Hello, {name}" </h1>
|
||||
<button onclick={move |_| set_name("jack")}> "jack!" </button>
|
||||
<button onclick={move |_| set_name("jill")}> "jill!" </button>
|
||||
</div>
|
||||
})
|
||||
ctx.render(rsx!(
|
||||
div {
|
||||
h1 { "Hello, {name}" }
|
||||
// look ma - we can rsx! and html! together
|
||||
{["jack", "jill"].iter().map(|f| html!(<button onclick={move |_| set_name(f)}> "{f}" </button>))}
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a, P>(
|
||||
ctx: &'a Context<'src, P>,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> VNode<'src> {
|
||||
ctx.render(lazy_nodes)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ mod baller {
|
||||
use super::*;
|
||||
pub struct BallerProps {}
|
||||
|
||||
pub fn Baller(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn Baller(ctx: Context<()>) -> VNode {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ pub struct TallerProps {
|
||||
a: &'static str,
|
||||
}
|
||||
|
||||
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
|
||||
pub fn Taller(ctx: Context<TallerProps>) -> VNode {
|
||||
let b = true;
|
||||
todo!()
|
||||
}
|
||||
@@ -90,7 +90,7 @@ fn main() {}
|
||||
// div(bump)
|
||||
// .attr("class", "edit")
|
||||
// .child(text("Hello"))
|
||||
// .child(text(ctx.props.text.as_ref()))
|
||||
// .child(text(ctx.ctx.text.as_ref()))
|
||||
// .finish()
|
||||
// })
|
||||
// }
|
||||
@@ -123,7 +123,7 @@ fn main() {}
|
||||
// _ => html! { <div> </div> },
|
||||
// };
|
||||
|
||||
// if ctx.props.blah {
|
||||
// if ctx.ctx.blah {
|
||||
// html! { <div> </div> }
|
||||
// } else {
|
||||
// tex
|
||||
|
||||
@@ -7,23 +7,19 @@ use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
static Header: FC<()> = |ctx, props| {
|
||||
let inner = use_ref(&ctx, || 0);
|
||||
static Header: FC<()> = |ctx| {
|
||||
let inner = use_ref(ctx, || 0);
|
||||
|
||||
let handler1 = move || println!("Value is {}", inner.borrow());
|
||||
|
||||
ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| {
|
||||
builder::ElementBuilder::new(nodectx, "div")
|
||||
.child(VNode::Component(VComponent::new(
|
||||
Bottom,
|
||||
nodectx.bump().alloc(()),
|
||||
None,
|
||||
)))
|
||||
.child(VNode::Component(VComponent::new(Bottom, (), None)))
|
||||
.finish()
|
||||
}))
|
||||
};
|
||||
|
||||
static Bottom: FC<()> = |ctx, props| {
|
||||
static Bottom: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "bruh 1" </h1>
|
||||
@@ -32,7 +28,7 @@ static Bottom: FC<()> = |ctx, props| {
|
||||
})
|
||||
};
|
||||
|
||||
fn Top(ctx: Context, a: &str, b: &i32, c: &impl Fn()) -> DomTree {
|
||||
fn Top(ctx: Context<()>) -> VNode {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "bruh 1" </h1>
|
||||
@@ -124,44 +120,44 @@ fn try_test() {
|
||||
// test_poc()
|
||||
}
|
||||
|
||||
fn test_poc(ctx: Context) {
|
||||
let b = Bump::new();
|
||||
// fn test_poc(ctx: Context) {
|
||||
// let b = Bump::new();
|
||||
|
||||
let h = Args {
|
||||
a: CuOpt::Some("ASD"),
|
||||
b: CuOpt::Some(123),
|
||||
c: CuOpt::Some(|| {}),
|
||||
// c: CuOpt::Some(b.alloc(|| {})),
|
||||
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
};
|
||||
// let h = Args {
|
||||
// a: CuOpt::Some("ASD"),
|
||||
// b: CuOpt::Some(123),
|
||||
// c: CuOpt::Some(|| {}),
|
||||
// // c: CuOpt::Some(b.alloc(|| {})),
|
||||
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// };
|
||||
|
||||
let h2 = Args {
|
||||
a: CuOpt::Some("ASD"),
|
||||
b: CuOpt::Some(123),
|
||||
c: CuOpt::Some(|| {}),
|
||||
// c: CuOpt::Some(b.alloc(|| {})),
|
||||
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
};
|
||||
// let h2 = Args {
|
||||
// a: CuOpt::Some("ASD"),
|
||||
// b: CuOpt::Some(123),
|
||||
// c: CuOpt::Some(|| {}),
|
||||
// // c: CuOpt::Some(b.alloc(|| {})),
|
||||
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
|
||||
// };
|
||||
|
||||
// dbg!((&h.a).memo((&&h2.a)));
|
||||
// dbg!((&h.b).memo((&&h2.b)));
|
||||
// dbg!((&h.c).memo((&&h2.c)));
|
||||
//
|
||||
// ctx: Context
|
||||
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
|
||||
}
|
||||
// // dbg!((&h.a).memo((&&h2.a)));
|
||||
// // dbg!((&h.b).memo((&&h2.b)));
|
||||
// // dbg!((&h.c).memo((&&h2.c)));
|
||||
// //
|
||||
// // ctx: Context
|
||||
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
|
||||
// }
|
||||
|
||||
fn test_realzies() {
|
||||
let h = Args {
|
||||
a: CuOpt::Some("ASD"),
|
||||
b: CuOpt::Some(123),
|
||||
c: CuOpt::Some(|| {}),
|
||||
};
|
||||
// fn test_realzies() {
|
||||
// let h = Args {
|
||||
// a: CuOpt::Some("ASD"),
|
||||
// b: CuOpt::Some(123),
|
||||
// c: CuOpt::Some(|| {}),
|
||||
// };
|
||||
|
||||
let g = |ctx: Context| {
|
||||
//
|
||||
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
|
||||
};
|
||||
}
|
||||
// let g = |ctx: Context| {
|
||||
// //
|
||||
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
|
||||
// };
|
||||
// }
|
||||
@@ -16,22 +16,22 @@ fn test() {
|
||||
// let g: FC<ButtonProps> = CustomButton;
|
||||
}
|
||||
|
||||
trait Render {
|
||||
fn render(ctx: Context, props: &Self) -> DomTree;
|
||||
trait Render: Sized {
|
||||
fn render(ctx: Context<Self>) -> VNode;
|
||||
}
|
||||
// include as much as you might accept
|
||||
struct Button<'a> {
|
||||
onhover: Option<&'a dyn Fn()>,
|
||||
struct Button {
|
||||
onhover: Option<Box<dyn Fn()>>,
|
||||
}
|
||||
|
||||
impl Render for Button<'_> {
|
||||
fn render(ctx: Context, _props: &Self) -> DomTree {
|
||||
impl Render for Button {
|
||||
fn render(ctx: Context<Self>) -> VNode {
|
||||
let _onfocus = move |_evt: ()| log::debug!("Focused");
|
||||
|
||||
// todo!()
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
// ..props.attrs,
|
||||
// ..ctx.attrs,
|
||||
class: "abc123",
|
||||
// style: { a: 2, b: 3, c: 4 },
|
||||
onclick: move |_evt| {
|
||||
@@ -47,7 +47,7 @@ impl Render for Button<'_> {
|
||||
}
|
||||
|
||||
// #[fc]
|
||||
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {}
|
||||
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> VNode {}
|
||||
|
||||
// h1 {
|
||||
// tag: "type", abc: 123, class: "big small wide short",
|
||||
|
||||
@@ -14,10 +14,28 @@ struct SomeProps {
|
||||
name: String,
|
||||
}
|
||||
|
||||
static Example: FC<SomeProps> = |ctx, _props| {
|
||||
static Example: FC<SomeProps> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "hello world!" </h1>
|
||||
</div>
|
||||
})
|
||||
};
|
||||
|
||||
// #[test]
|
||||
#[derive(PartialEq, Clone)]
|
||||
struct MyStruct {
|
||||
a: String,
|
||||
}
|
||||
|
||||
fn check_before_to_owned() {
|
||||
let new_str = MyStruct {
|
||||
a: "asd".to_string(),
|
||||
};
|
||||
|
||||
let out = town(&new_str);
|
||||
}
|
||||
|
||||
fn town<T: ToOwned + PartialEq>(t: &T) -> T::Owned {
|
||||
t.to_owned()
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
|
||||
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||
//! that ensures compile-time required and optional fields on props.
|
||||
//! that ensures compile-time required and optional fields on ctx.
|
||||
|
||||
use crate::innerlude::FC;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ impl DebugRenderer {
|
||||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ impl DebugRenderer {
|
||||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new_with_props<T: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
|
||||
root_props: T,
|
||||
) -> Self {
|
||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
||||
@@ -78,7 +78,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn ensure_creation() -> Result<(), ()> {
|
||||
// static Example: FC<()> = |ctx, props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// //
|
||||
// ctx.render(html! { <div> "hello world" </div> })
|
||||
// };
|
||||
|
||||
@@ -65,22 +65,22 @@ pub struct DiffMachine<'a> {
|
||||
}
|
||||
pub enum LifeCycleEvent<'a> {
|
||||
Mount {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
PropsChanged {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
SameProps {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
Replace {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
old_scope: Weak<VCompAssociatedScope>,
|
||||
new_scope: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
|
||||
+11
-11
@@ -48,8 +48,8 @@ mod use_state_def {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
ctx.use_hook(
|
||||
@@ -169,8 +169,8 @@ mod new_use_state_def {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: F,
|
||||
) -> &'a UseState<T> {
|
||||
ctx.use_hook(
|
||||
@@ -246,8 +246,8 @@ mod use_ref_def {
|
||||
/// To change it, use modify.
|
||||
/// Modifications to this value do not cause updates to the component
|
||||
/// Attach to inner context reference, so context can be consumed
|
||||
pub fn use_ref<'a, T: 'static>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_ref<'a, T: 'static, P>(
|
||||
ctx: Context<'a, P>,
|
||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||
) -> &'a RefCell<T> {
|
||||
ctx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
|
||||
@@ -270,11 +270,11 @@ mod use_reducer_def {
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: impl FnOnce() -> State,
|
||||
_reducer: impl Fn(&mut State, Action),
|
||||
) -> (&'a State, &'a impl Fn(Action)) {
|
||||
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
|
||||
ctx.use_hook(
|
||||
move || UseReducer {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
@@ -320,7 +320,7 @@ mod use_reducer_def {
|
||||
}
|
||||
|
||||
// #[allow(unused)]
|
||||
// static Example: FC<()> = |ctx, props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// let (count, reduce) = use_reducer(
|
||||
// &ctx,
|
||||
// || 0,
|
||||
@@ -347,7 +347,7 @@ mod use_reducer_def {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_is_initialized(ctx: Context) -> bool {
|
||||
pub fn use_is_initialized<P>(ctx: Context<P>) -> bool {
|
||||
let val = use_ref(ctx, || false);
|
||||
match *val.borrow() {
|
||||
true => true,
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
//! #[derive(Properties)]
|
||||
//! struct Props { name: String }
|
||||
//!
|
||||
//! fn Example(ctx: Context, props: &Props) -> DomTree {
|
||||
//! html! { <div> "Hello {props.name}!" </div> }
|
||||
//! fn Example(ctx: Context, props: &Props) -> VNode {
|
||||
//! html! { <div> "Hello {ctx.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
|
||||
@@ -31,8 +31,8 @@
|
||||
//! #[derive(Properties)]
|
||||
//! struct Props { name: String }
|
||||
//!
|
||||
//! static Example: FC<Props> = |ctx, props| {
|
||||
//! html! { <div> "Hello {props.name}!" </div> }
|
||||
//! static Example: FC<Props> = |ctx| {
|
||||
//! html! { <div> "Hello {ctx.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
@@ -101,7 +101,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::patch::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||
|
||||
// Re-export the FC macro
|
||||
pub use crate as dioxus;
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128};
|
||||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{DomTree, Properties, VComponent, FC},
|
||||
innerlude::{Properties, VComponent, FC},
|
||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
||||
prelude::VElement,
|
||||
virtual_dom::NodeCtx,
|
||||
@@ -477,7 +477,7 @@ where
|
||||
/// .iter_child((0..10).map(|f| span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoDomTree<'a>>) -> Self {
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||
for item in nodes {
|
||||
let child = item.into_vnode(&self.ctx);
|
||||
self.children.push(child);
|
||||
@@ -486,13 +486,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DomTree {
|
||||
type Item = DomTree;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
// impl IntoIterator for VNode {
|
||||
// type Item = VNode;
|
||||
// type IntoIter = std::iter::Once<Self::Item>;
|
||||
// fn into_iter(self) -> Self::IntoIter {
|
||||
// std::iter::once(self)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<'a> IntoIterator for VNode<'a> {
|
||||
type Item = VNode<'a>;
|
||||
@@ -501,23 +501,30 @@ impl<'a> IntoIterator for VNode<'a> {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDomTree<'a> for VNode<'a> {
|
||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoDomTree<'a> {
|
||||
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
// cloning is cheap since vnodes are just references into bump arenas
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoVNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
|
||||
}
|
||||
|
||||
pub trait DomTreeBuilder<'a, G>: IntoIterator<Item = G>
|
||||
pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
|
||||
where
|
||||
G: IntoDomTree<'a>,
|
||||
G: IntoVNode<'a>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
||||
impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
||||
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
|
||||
{
|
||||
}
|
||||
@@ -527,15 +534,15 @@ impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
||||
// ----
|
||||
// let nodes = ctx.render(rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a> IntoDomTree<'a> for DomTree {
|
||||
fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
self.root
|
||||
}
|
||||
}
|
||||
// impl<'a> IntoVNode<'a> for VNode {
|
||||
// fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
// self.root
|
||||
// }
|
||||
// }
|
||||
|
||||
// Wrap the the node-builder closure in a concrete type.
|
||||
// ---
|
||||
// This is a bit of a hack to implement the IntoDomTree trait for closure types.
|
||||
// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
||||
pub struct LazyNodes<'a, G>
|
||||
where
|
||||
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
|
||||
@@ -561,7 +568,7 @@ where
|
||||
// ---
|
||||
// let nodes = rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a, G> IntoDomTree<'a> for LazyNodes<'a, G>
|
||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||
where
|
||||
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
|
||||
{
|
||||
@@ -581,13 +588,13 @@ where
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDomTree<'a> for () {
|
||||
impl<'a> IntoVNode<'a> for () {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDomTree<'a> for Option<()> {
|
||||
impl<'a> IntoVNode<'a> for Option<()> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
@@ -650,15 +657,16 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
|
||||
Attribute { name, value }
|
||||
}
|
||||
|
||||
pub fn virtual_child<'a, T: Properties + 'a>(
|
||||
pub fn virtual_child<'a, T: Properties>(
|
||||
ctx: &NodeCtx<'a>,
|
||||
f: FC<T>,
|
||||
p: T,
|
||||
props: T,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
) -> VNode<'a> {
|
||||
// currently concerned about if props have a custom drop implementation
|
||||
// might override it with the props macro
|
||||
let bump = &ctx.bump();
|
||||
let propsd: &'a mut _ = bump.alloc(p);
|
||||
VNode::Component(crate::nodes::VComponent::new(f, propsd, key))
|
||||
VNode::Component(
|
||||
ctx.bump()
|
||||
.alloc(crate::nodes::VComponent::new(f, props, key)),
|
||||
)
|
||||
}
|
||||
+61
-43
@@ -5,17 +5,10 @@
|
||||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{Context, Properties, ScopeIdx, FC},
|
||||
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// A domtree represents the result of "Viewing" the context
|
||||
/// It's a placeholder over vnodes, to make working with lifetimes easier
|
||||
pub struct DomTree {
|
||||
// this should *never* be publicly accessible to external
|
||||
pub root: VNode<'static>,
|
||||
}
|
||||
use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// Tools for the base unit of the virtual dom - the VNode
|
||||
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
||||
@@ -35,7 +28,19 @@ pub enum VNode<'src> {
|
||||
Suspended,
|
||||
|
||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
||||
Component(VComponent<'src>),
|
||||
Component(&'src VComponent<'src>),
|
||||
}
|
||||
|
||||
// it's okay to clone because vnodes are just references to places into the bump
|
||||
impl<'a> Clone for VNode<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
VNode::Element(el) => VNode::Element(el),
|
||||
VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
|
||||
VNode::Suspended => VNode::Suspended,
|
||||
VNode::Component(c) => VNode::Component(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
@@ -196,6 +201,12 @@ pub struct VText<'bump> {
|
||||
pub text: &'bump str,
|
||||
}
|
||||
|
||||
impl<'b> Clone for VText<'b> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { text: self.text }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VText<'a> {
|
||||
// / Create an new `VText` instance with the specified text.
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
@@ -218,13 +229,14 @@ pub struct VComponent<'src> {
|
||||
pub stable_addr: Rc<StableScopeAddres>,
|
||||
pub ass_scope: Rc<VCompAssociatedScope>,
|
||||
|
||||
pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||
pub caller: Rc<dyn Fn(Context) -> DomTree + 'src>,
|
||||
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||
pub caller: Rc<dyn Fn(&Scope) -> VNode + 'src>,
|
||||
|
||||
pub children: &'src [VNode<'src>],
|
||||
|
||||
// a pointer into the bump arena (given by the 'src lifetime)
|
||||
raw_props: *const (),
|
||||
raw_props: Box<dyn Any>,
|
||||
// raw_props: *const (),
|
||||
|
||||
// a pointer to the raw fn typ
|
||||
pub user_fc: *const (),
|
||||
@@ -237,28 +249,30 @@ impl<'a> VComponent<'a> {
|
||||
// - perform comparisons when diffing (memoization)
|
||||
// TODO: lift the requirement that props need to be static
|
||||
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
|
||||
pub fn new<P: Properties + 'a>(component: FC<P>, props: &'a P, key: Option<&'a str>) -> Self {
|
||||
pub fn new<P: Properties>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
||||
let caller_ref = component as *const ();
|
||||
|
||||
let raw_props = props as *const P as *const ();
|
||||
// let raw_props = props as *const P as *const ();
|
||||
|
||||
let props_comparator = move |other: &VComponent| {
|
||||
// Safety:
|
||||
// We are guaranteed that the props will be of the same type because
|
||||
// there is no way to create a VComponent other than this `new` method.
|
||||
//
|
||||
// Therefore, if the render functions are identical (by address), then so will be
|
||||
// props type paramter (because it is the same render function). Therefore, we can be
|
||||
// sure
|
||||
if caller_ref == other.user_fc {
|
||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
real_other == props
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
// let props_comparator = move |other: &VComponent| {
|
||||
// // Safety:
|
||||
// // We are guaranteed that the props will be of the same type because
|
||||
// // there is no way to create a VComponent other than this `new` method.
|
||||
// //
|
||||
// // Therefore, if the render functions are identical (by address), then so will be
|
||||
// // props type paramter (because it is the same render function). Therefore, we can be
|
||||
// // sure
|
||||
// if caller_ref == other.user_fc {
|
||||
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
||||
// // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
// &props == g
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// };
|
||||
|
||||
let caller = Rc::new(create_closure(component, raw_props));
|
||||
let caller: Rc<dyn Fn(&Scope) -> VNode + 'a> = Rc::new(move |scp| todo!());
|
||||
// let caller = Rc::new(create_closure(component, raw_props));
|
||||
|
||||
let key = match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
@@ -269,23 +283,27 @@ impl<'a> VComponent<'a> {
|
||||
key,
|
||||
ass_scope: Rc::new(RefCell::new(None)),
|
||||
user_fc: caller_ref,
|
||||
raw_props: props as *const P as *const _,
|
||||
raw_props: Box::new(props),
|
||||
_p: PhantomData,
|
||||
children: &[],
|
||||
caller,
|
||||
comparator: Rc::new(props_comparator),
|
||||
// comparator: Rc::new(props_comparator),
|
||||
stable_addr: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_closure<'a, P: Properties + 'a>(
|
||||
component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> impl for<'r> Fn(Context<'r>) -> DomTree + 'a {
|
||||
move |ctx: Context| -> DomTree {
|
||||
// cast back into the right lifetime
|
||||
let safe_props: &'a P = unsafe { &*(raw_props as *const P) };
|
||||
component(ctx, safe_props)
|
||||
}
|
||||
}
|
||||
// fn create_closure<P: Properties>(
|
||||
// component: FC<P>,
|
||||
// raw_props: *const (),
|
||||
// ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> {
|
||||
// move |ctx| -> VNode {
|
||||
// // cast back into the right lifetime
|
||||
// let safe_props: &P = unsafe { &*(raw_props as *const P) };
|
||||
// component(Context {
|
||||
// props: safe_props,
|
||||
// scope: ctx,
|
||||
// })
|
||||
// // component(ctx, safe_props)
|
||||
// }
|
||||
// }
|
||||
@@ -28,6 +28,7 @@ use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
@@ -53,7 +54,7 @@ pub struct VirtualDom {
|
||||
#[doc(hidden)]
|
||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
||||
|
||||
/// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
|
||||
/// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic.
|
||||
///
|
||||
/// Whenver props need to be updated, an Error will be thrown if the new props do not
|
||||
/// match the props used to create the VirtualDom.
|
||||
@@ -77,11 +78,11 @@ impl VirtualDom {
|
||||
/// ```ignore
|
||||
/// // Directly from a closure
|
||||
///
|
||||
/// let dom = VirtualDom::new(|ctx, _| ctx.render(rsx!{ div {"hello world"} }));
|
||||
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
|
||||
///
|
||||
/// // or pass in...
|
||||
///
|
||||
/// let root = |ctx, _| {
|
||||
/// let root = |ctx| {
|
||||
/// ctx.render(rsx!{
|
||||
/// div {"hello world"}
|
||||
/// })
|
||||
@@ -90,17 +91,17 @@ impl VirtualDom {
|
||||
///
|
||||
/// // or directly from a fn
|
||||
///
|
||||
/// fn Example(ctx: Context, props: &()) -> DomTree {
|
||||
/// fn Example(ctx: Context<()>) -> VNode {
|
||||
/// ctx.render(rsx!{ div{"hello world"} })
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// ```
|
||||
pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl Fn(Context<()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
/// Start a new VirtualDom instance with a dependent props.
|
||||
/// Start a new VirtualDom instance with a dependent ctx.
|
||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||
///
|
||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||
@@ -109,11 +110,11 @@ impl VirtualDom {
|
||||
/// ```ignore
|
||||
/// // Directly from a closure
|
||||
///
|
||||
/// let dom = VirtualDom::new(|ctx, props| ctx.render(rsx!{ div {"hello world"} }));
|
||||
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
|
||||
///
|
||||
/// // or pass in...
|
||||
///
|
||||
/// let root = |ctx, props| {
|
||||
/// let root = |ctx| {
|
||||
/// ctx.render(rsx!{
|
||||
/// div {"hello world"}
|
||||
/// })
|
||||
@@ -122,21 +123,27 @@ impl VirtualDom {
|
||||
///
|
||||
/// // or directly from a fn
|
||||
///
|
||||
/// fn Example(ctx: Context, props: &SomeProps) -> DomTree {
|
||||
/// fn Example(ctx: Context, props: &SomeProps) -> VNode {
|
||||
/// ctx.render(rsx!{ div{"hello world"} })
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// ```
|
||||
pub fn new_with_props<P: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static,
|
||||
root: impl Fn(Context<P>) -> VNode + 'static,
|
||||
root_props: P,
|
||||
) -> Self {
|
||||
let components = ScopeArena::new(Arena::new());
|
||||
|
||||
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
|
||||
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
|
||||
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
|
||||
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
|
||||
todo!()
|
||||
// root(Context {
|
||||
// props: &root_props,
|
||||
// scope: ctx,
|
||||
// })
|
||||
});
|
||||
|
||||
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
|
||||
let caller_ref = Rc::downgrade(&_root_caller);
|
||||
@@ -396,7 +403,7 @@ impl VirtualDom {
|
||||
stable_scope_addr,
|
||||
..
|
||||
} => {
|
||||
// In this case, the parent made a new DomTree that resulted in the same props for us
|
||||
// In this case, the parent made a new VNode that resulted in the same props for us
|
||||
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
||||
log::debug!("Received the same props");
|
||||
|
||||
@@ -617,7 +624,8 @@ impl Scope {
|
||||
)
|
||||
}(&self);
|
||||
|
||||
self.frames.cur_frame_mut().head_node = new_head.root;
|
||||
todo!();
|
||||
// self.frames.cur_frame_mut().head_node = new_head;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -676,41 +684,63 @@ impl Scope {
|
||||
///
|
||||
/// fn example(ctx: Context, props: &Props -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.name}" </div>
|
||||
/// <div> "Hello, {ctx.ctx.name}" </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
// todo: force lifetime of source into T as a valid lifetime too
|
||||
// it's definitely possible, just needs some more messing around
|
||||
pub type Context<'src> = &'src Scope;
|
||||
|
||||
impl Scope {
|
||||
pub struct Context<'src, T> {
|
||||
pub props: &'src T,
|
||||
pub scope: &'src Scope,
|
||||
}
|
||||
|
||||
impl<'src, T> Copy for Context<'src, T> {}
|
||||
impl<'src, T> Clone for Context<'src, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
props: self.props,
|
||||
scope: self.scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Context<'a, T> {
|
||||
type Target = &'a T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.props
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src, T> Context<'src, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> &[VNode] {
|
||||
pub fn children(&self) -> &'src [VNode<'src>] {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn schedule_update(&self) -> impl Fn() -> () {
|
||||
self.event_queue.schedule_update(&self)
|
||||
pub fn schedule_update(&self) -> impl Fn() -> () + 'static {
|
||||
self.scope.event_queue.schedule_update(&self.scope)
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
&'a self,
|
||||
_fut: impl Future<Output = LazyNodes<'a, F>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
// /// Create a suspended component from a future.
|
||||
// ///
|
||||
// /// When the future completes, the component will be renderered
|
||||
// pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
// &'a self,
|
||||
// _fut: impl Future<Output = LazyNodes<'a, F>>,
|
||||
// ) -> VNode<'src> {
|
||||
// todo!()
|
||||
// }
|
||||
}
|
||||
|
||||
// ================================================
|
||||
// Render Implementation for Components
|
||||
// ================================================
|
||||
//
|
||||
impl Scope {
|
||||
impl<'src, T> Context<'src, T> {
|
||||
// impl<'scope> Context<'scope> {
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
@@ -719,7 +749,7 @@ impl Scope {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context, props: &()) -> VNode {
|
||||
/// fn Component(ctx: Context<()>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div> "Hello World" </div>};
|
||||
///
|
||||
@@ -727,31 +757,22 @@ impl Scope {
|
||||
/// ctx.render(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn render<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
|
||||
&'scope self,
|
||||
lazy_nodes: LazyNodes<'scope, F>,
|
||||
) -> DomTree {
|
||||
pub fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
|
||||
self,
|
||||
// &'a/ self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> VNode<'src> {
|
||||
let ctx = NodeCtx {
|
||||
scope_ref: self,
|
||||
scope_ref: self.scope,
|
||||
listener_id: 0.into(),
|
||||
};
|
||||
|
||||
DomTree {
|
||||
root: unsafe {
|
||||
std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render2<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
|
||||
&'scope self,
|
||||
lazy_nodes: LazyNodes<'scope, F>,
|
||||
) -> VNode<'scope> {
|
||||
let ctx = NodeCtx {
|
||||
scope_ref: self,
|
||||
listener_id: 0.into(),
|
||||
};
|
||||
lazy_nodes.into_vnode(&ctx)
|
||||
todo!()
|
||||
// VNode {
|
||||
// root: unsafe {
|
||||
// std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
|
||||
// },
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,7 +783,7 @@ impl Scope {
|
||||
// We need to pin the hook so it doesn't move as we initialize the list of hooks
|
||||
type Hook = Pin<Box<dyn std::any::Any>>;
|
||||
|
||||
impl Scope {
|
||||
impl<'src, T> Context<'src, T> {
|
||||
// impl<'scope> Context<'scope> {
|
||||
/// Store a value between renders
|
||||
///
|
||||
@@ -775,28 +796,28 @@ impl Scope {
|
||||
/// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
|
||||
/// use_hook(
|
||||
/// || Rc::new(RefCell::new(initial_value())),
|
||||
/// |state, _| state.clone(),
|
||||
/// |state| state.clone(),
|
||||
/// |_| {},
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_hook<'scope, InternalHookState: 'static, Output: 'scope>(
|
||||
&'scope self,
|
||||
pub fn use_hook<InternalHookState: 'static, Output: 'src>(
|
||||
&self,
|
||||
|
||||
// The closure that builds the hook state
|
||||
initializer: impl FnOnce() -> InternalHookState,
|
||||
|
||||
// The closure that takes the hookstate and returns some value
|
||||
runner: impl FnOnce(&'scope mut InternalHookState) -> Output,
|
||||
runner: impl FnOnce(&'src mut InternalHookState) -> Output,
|
||||
|
||||
// The closure that cleans up whatever mess is left when the component gets torn down
|
||||
// TODO: add this to the "clean up" group for when the component is dropped
|
||||
_cleanup: impl FnOnce(InternalHookState),
|
||||
) -> Output {
|
||||
let idx = *self.hookidx.borrow();
|
||||
let idx = *self.scope.hookidx.borrow();
|
||||
|
||||
// Grab out the hook list
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
let mut hooks = self.scope.hooks.borrow_mut();
|
||||
|
||||
// If the idx is the same as the hook length, then we need to add the current hook
|
||||
if idx >= hooks.len() {
|
||||
@@ -804,7 +825,7 @@ impl Scope {
|
||||
hooks.push(Box::pin(new_state));
|
||||
}
|
||||
|
||||
*self.hookidx.borrow_mut() += 1;
|
||||
*self.scope.hookidx.borrow_mut() += 1;
|
||||
|
||||
let stable_ref = hooks
|
||||
.get_mut(idx)
|
||||
@@ -831,7 +852,7 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
// ================================================
|
||||
// Context API Implementation for Components
|
||||
// ================================================
|
||||
impl Scope {
|
||||
impl<'src, P> Context<'src, P> {
|
||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
///
|
||||
/// This is a hook, so it may not be called conditionally!
|
||||
@@ -845,7 +866,7 @@ impl Scope {
|
||||
///
|
||||
///
|
||||
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
|
||||
let mut ctxs = self.shared_contexts.borrow_mut();
|
||||
let mut ctxs = self.scope.shared_contexts.borrow_mut();
|
||||
let ty = TypeId::of::<T>();
|
||||
|
||||
let is_initialized = self.use_hook(
|
||||
@@ -873,12 +894,12 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// There are hooks going on here!
|
||||
pub fn use_context<'a, T: 'static>(&'a self) -> &'a Rc<T> {
|
||||
pub fn use_context<T: 'static>(&self) -> &'src Rc<T> {
|
||||
self.try_use_context().unwrap()
|
||||
}
|
||||
|
||||
/// Uses a context, storing the cached value around
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<&Rc<T>> {
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<&'src Rc<T>> {
|
||||
struct UseContextHook<C> {
|
||||
par: Option<Rc<C>>,
|
||||
we: Option<Weak<C>>,
|
||||
@@ -890,7 +911,7 @@ impl Scope {
|
||||
we: None as Option<Weak<T>>,
|
||||
},
|
||||
move |hook| {
|
||||
let mut scope = Some(self);
|
||||
let mut scope = Some(self.scope);
|
||||
|
||||
if let Some(we) = &hook.we {
|
||||
if let Some(re) = we.upgrade() {
|
||||
@@ -942,7 +963,7 @@ impl Scope {
|
||||
|
||||
// We actually allocate the properties for components in their parent's properties
|
||||
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
||||
pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
|
||||
pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
|
||||
@@ -978,7 +999,7 @@ impl PartialOrd for HeightMarker {
|
||||
}
|
||||
|
||||
// NodeCtx is used to build VNodes in the component's memory space.
|
||||
// This struct adds metadata to the final DomTree about listeners, attributes, and children
|
||||
// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
#[derive(Clone)]
|
||||
pub struct NodeCtx<'a> {
|
||||
pub scope_ref: &'a Scope,
|
||||
@@ -1100,7 +1121,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn simulate() {
|
||||
let dom = VirtualDom::new(|ctx, props| {
|
||||
let dom = VirtualDom::new(|ctx| {
|
||||
//
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
@@ -1122,7 +1143,7 @@ mod tests {
|
||||
todo!()
|
||||
}
|
||||
|
||||
let _ = check_send(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
|
||||
let _ = check_sync(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
|
||||
let _ = check_send(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
|
||||
let _ = check_sync(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
|
||||
}
|
||||
}
|
||||
@@ -3,43 +3,44 @@
|
||||
This crate provides all the batteries required to build Dioxus apps.
|
||||
|
||||
Included in this crate is:
|
||||
|
||||
- Dioxus core
|
||||
- Essential hooks (use_state, use_ref, use_reducer, etc)
|
||||
- rsx! and html! macros
|
||||
|
||||
You'll still need to pull in a renderer to render the Dioxus VDOM. Any one of:
|
||||
|
||||
- dioxus-web (to run in WASM)
|
||||
- dioxus-ssr (to run on the server or for static sites)
|
||||
- dioxus-webview (to run on the desktop)
|
||||
- dioxus-mobile (to run on iOS/Android)
|
||||
|
||||
|
||||
Make sure dioxus and its renderer share the same major version; the renderers themselves rely on dioxus.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
dioxus = "0.2"
|
||||
dioxus-web = "0.2"
|
||||
dioxus = "0.2"
|
||||
dioxus-web = "0.2"
|
||||
```
|
||||
|
||||
```rust
|
||||
use dioxus::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_web::start(|ctx, _| {
|
||||
dioxus_web::start(|ctx| {
|
||||
rsx!{in ctx, div { "Hello world" }}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Additionally, you'll want to look at other projects for more batteries
|
||||
|
||||
- essential-hooks (use_router, use_storage, use_cache, use_channel)
|
||||
- Recoil.rs or Reducer.rs for state management
|
||||
- 3D renderer (ThreeD), charts (Sciviz), game engine (Bevy)
|
||||
|
||||
|
||||
Extra resources:
|
||||
|
||||
- The guide is available at:
|
||||
- The crate docs are at:
|
||||
- Video tutorials are at:
|
||||
|
||||
@@ -37,7 +37,7 @@ impl SomeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub static ExampleReducer: FC<()> = |ctx, props| {
|
||||
pub static ExampleReducer: FC<()> = |ctx| {
|
||||
let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
|
||||
|
||||
let is_playing = state.is_playing();
|
||||
@@ -85,7 +85,7 @@ enum KindaState {
|
||||
Erred,
|
||||
}
|
||||
|
||||
static EnumReducer: FC<()> = |ctx, props| {
|
||||
static EnumReducer: FC<()> = |ctx| {
|
||||
let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
|
||||
|
||||
let contents = helper(&ctx);
|
||||
@@ -126,7 +126,7 @@ static EnumReducer: FC<()> = |ctx, props| {
|
||||
})
|
||||
};
|
||||
|
||||
fn helper(ctx: &Context) -> DomTree {
|
||||
fn helper(ctx: &Context) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dioxus_ssr::{
|
||||
prelude::*,
|
||||
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent},
|
||||
prelude::{builder::IntoVNode, dioxus::events::on::MouseEvent},
|
||||
TextRenderer,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,6 @@ fn main() {
|
||||
TextRenderer::new(App);
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
todo!()
|
||||
}
|
||||
@@ -112,8 +112,8 @@ impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
|
||||
|
||||
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
||||
Self {
|
||||
children: props.children,
|
||||
context: Rc::new(props.context),
|
||||
children: ctx.children,
|
||||
context: Rc::new(ctx.context),
|
||||
consumers: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
@@ -123,14 +123,14 @@ impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
|
||||
}
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> bool {
|
||||
let should_render = if self.children == props.children {
|
||||
let should_render = if self.children == ctx.children {
|
||||
false
|
||||
} else {
|
||||
self.children = props.children;
|
||||
self.children = ctx.children;
|
||||
true
|
||||
};
|
||||
|
||||
let new_context = Rc::new(props.context);
|
||||
let new_context = Rc::new(ctx.context);
|
||||
if self.context != new_context {
|
||||
self.context = new_context;
|
||||
self.notify_consumers();
|
||||
@@ -357,10 +357,10 @@ mod tests {
|
||||
log::info!("Render counter {:?}", counter);
|
||||
return html! {
|
||||
<>
|
||||
<div id=props.id.clone()>
|
||||
<div id=ctx.id.clone()>
|
||||
{ format!("total: {}", counter.borrow()) }
|
||||
</div>
|
||||
{ props.children.clone() }
|
||||
{ ctx.children.clone() }
|
||||
</>
|
||||
};
|
||||
}
|
||||
@@ -384,14 +384,14 @@ mod tests {
|
||||
let ctx = use_context::<Rc<MyContext>>().expect("context not passed down");
|
||||
log::info!("============");
|
||||
log::info!("ctx is {:#?}", ctx);
|
||||
log::info!("magic is {:#?}", props.magic);
|
||||
log::info!("magic is {:#?}", ctx.magic);
|
||||
log::info!("outlet counter is {:#?}", ctx);
|
||||
log::info!("============");
|
||||
|
||||
return html! {
|
||||
<>
|
||||
<div>{ format!("magic: {}\n", props.magic) }</div>
|
||||
<div id=props.id.clone()>
|
||||
<div>{ format!("magic: {}\n", ctx.magic) }</div>
|
||||
<div id=ctx.id.clone()>
|
||||
{ format!("current: {}, total: {}", ctx.0, counter.borrow()) }
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -277,8 +277,8 @@ mod tests {
|
||||
type TProps = FunctionProps;
|
||||
|
||||
fn run(props: &Self::TProps) -> Html {
|
||||
let effect_called = props.effect_called.clone();
|
||||
let destroy_called = props.destroy_called.clone();
|
||||
let effect_called = ctx.effect_called.clone();
|
||||
let destroy_called = ctx.destroy_called.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
effect_called();
|
||||
@@ -297,7 +297,7 @@ mod tests {
|
||||
if *show {
|
||||
let effect_called: Rc<dyn Fn()> = Rc::new(move || set_show(false));
|
||||
return html! {
|
||||
<UseEffectComponent destroy_called=props.destroy_called.clone() effect_called=effect_called />
|
||||
<UseEffectComponent destroy_called=ctx.destroy_called.clone() effect_called=effect_called />
|
||||
};
|
||||
} else {
|
||||
return html! {
|
||||
|
||||
@@ -48,7 +48,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||
pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
|
||||
use_hook(
|
||||
|| Rc::new(RefCell::new(initial_value())),
|
||||
|state, _| state.clone(),
|
||||
|state| state.clone(),
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ uses the same memoization on top of the use_context API.
|
||||
|
||||
Here's a fully-functional todo app using the use_map API:
|
||||
```rust
|
||||
static TodoList: FC<()> = |ctx, props| {
|
||||
static TodoList: FC<()> = |ctx| {
|
||||
let todos = use_map(ctx, || HashMap::new());
|
||||
let input = use_ref(|| None);
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Dioxus for iOS
|
||||
|
||||
This crate implements a renderer of the Dioxus DomTree to an iOS app
|
||||
This crate implements a renderer of the Dioxus VNode to an iOS app
|
||||
@@ -27,7 +27,7 @@ mod client {
|
||||
Ok(dioxus_web::WebsysRenderer::start(APP).await)
|
||||
}
|
||||
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
todo!()
|
||||
// let (selected_stream, set_stream) = use_state(&ctx, || SelectedStream::Football);
|
||||
|
||||
@@ -102,9 +102,9 @@ mod server {
|
||||
selected_stream: SelectedStream,
|
||||
}
|
||||
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx, props| {
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx| {
|
||||
//
|
||||
let g = match props.selected_stream {
|
||||
let g = match ctx.selected_stream {
|
||||
SelectedStream::Football => ctx.render(rsx! {
|
||||
li {
|
||||
"watch football!"
|
||||
|
||||
@@ -27,7 +27,7 @@ mod client {
|
||||
Ok(dioxus_web::WebsysRenderer::start(APP).await)
|
||||
}
|
||||
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
todo!()
|
||||
// let (selected_stream, set_stream) = use_state(&ctx, || SelectedStream::Football);
|
||||
|
||||
@@ -102,9 +102,9 @@ mod server {
|
||||
selected_stream: SelectedStream,
|
||||
}
|
||||
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx, props| {
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx| {
|
||||
//
|
||||
match props.selected_stream {
|
||||
match ctx.selected_stream {
|
||||
SelectedStream::Football => ctx.render(rsx! {
|
||||
li {
|
||||
"watch football!"
|
||||
|
||||
@@ -21,7 +21,7 @@ This atom of state is initialized with a value of `"Green"`. The atom that is re
|
||||
This is then later used in components like so:
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
// The recoil root must be initialized at the top of the application before any use_recoil hooks
|
||||
recoil::init_recoil_root(&ctx, |_| {});
|
||||
|
||||
@@ -36,7 +36,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
||||
Atoms are considered "Writable" objects since any consumer may also set the Atom's value with their own:
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let color = recoil::use_read(ctx, Light);
|
||||
let set_color = recoil::use_write(ctx, Light);
|
||||
rsx!{in ctx,
|
||||
@@ -53,7 +53,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
||||
"Reading" a value with use_read subscribes that component to any changes to that value while "Writing" a value does not. It's a good idea to use `write-only` whenever it makes sense to prevent unnecessary evaluations. Both `read` and `write` may be combined together to provide a `use_state` style hook.
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let (color, set_color) = recoil::use_read_write(ctx, Light);
|
||||
rsx!{in ctx,
|
||||
div {
|
||||
@@ -125,7 +125,7 @@ const CloudRatings: AtomFamily<&str, i32> = |api| {
|
||||
Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subscribers will only be updated if they too select the same family with the same key and that value is updated. Otherwise, these subscribers will never re-render on an "insert", "remove", or "update" of the collection. You could easily implement this yourself with Atoms, immutable datastructures, and selectors, but our implementation is more efficient and first-class.
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let (rating, set_rating) = recoil::use_read_write(ctx, CloudRatings.select("AWS"));
|
||||
rsx!{in ctx,
|
||||
div {
|
||||
|
||||
@@ -28,7 +28,7 @@ impl TitleController {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx| {
|
||||
let title = use_read(ctx, &TITLE);
|
||||
let subtitle = use_read(ctx, &SUBTITLE);
|
||||
let controller = use_recoil_api(ctx, TitleController::new);
|
||||
|
||||
@@ -19,7 +19,7 @@ fn update_title(api: &RecoilApi) {
|
||||
}
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let title = use_read(ctx, &TITLE);
|
||||
let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api));
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const TODOS: EcsModel<u32, TodoModel> = |builder| {};
|
||||
// const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(0).select(k);
|
||||
// const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
// let title = use_recoil_value(ctx, &C_SELECTOR);
|
||||
|
||||
@@ -13,7 +13,7 @@ struct Todo {
|
||||
content: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, move |cfg| {});
|
||||
|
||||
let todos = use_read_family(ctx, &TODOS);
|
||||
@@ -36,8 +36,8 @@ struct ChildProps {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
static Child: FC<ChildProps> = |ctx, props| {
|
||||
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id));
|
||||
static Child: FC<ChildProps> = |ctx| {
|
||||
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&ctx.id));
|
||||
|
||||
rsx! { in ctx,
|
||||
li {
|
||||
|
||||
@@ -12,7 +12,7 @@ struct Todo {
|
||||
contents: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
let todos = use_read(ctx, &TODOS);
|
||||
|
||||
@@ -28,8 +28,8 @@ struct ChildProps {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
static Child: FC<ChildProps> = |ctx, props| {
|
||||
let todo = use_read(ctx, &TODOS).get(&props.id).unwrap();
|
||||
static Child: FC<ChildProps> = |ctx| {
|
||||
let todo = use_read(ctx, &TODOS).get(&ctx.id).unwrap();
|
||||
// let (todo, set_todo) = use_read_write(ctx, &TODOS);
|
||||
|
||||
rsx! { in ctx,
|
||||
|
||||
@@ -3,7 +3,7 @@ use recoil::*;
|
||||
|
||||
const COUNT: Atom<i32> = |_| 0;
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
let (count, set_count) = use_read_write(ctx, &COUNT);
|
||||
|
||||
@@ -17,7 +17,7 @@ const D_SELECTOR: SelectorFamilyBorrowed<i32, i32> = |api, key| -> &i32 {
|
||||
a
|
||||
};
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
let title = use_read(ctx, &C_SELECTOR);
|
||||
|
||||
@@ -5,7 +5,7 @@ const A: Atom<i32> = |_| 0;
|
||||
const B: Atom<i32> = |_| 0;
|
||||
const C: Selector<i32> = |api| api.get(&A) + api.get(&B);
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
rsx! { in ctx,
|
||||
div {
|
||||
@@ -16,12 +16,12 @@ static App: FC<()> = |ctx, _| {
|
||||
}
|
||||
};
|
||||
|
||||
static Banner: FC<()> = |ctx, _| {
|
||||
static Banner: FC<()> = |ctx| {
|
||||
let count = use_read(ctx, &C);
|
||||
ctx.render(rsx! { h1 { "Count: {count}" } })
|
||||
};
|
||||
|
||||
static BtnA: FC<()> = |ctx, _| {
|
||||
static BtnA: FC<()> = |ctx| {
|
||||
let (a, set) = use_read_write(ctx, &A);
|
||||
rsx! { in ctx,
|
||||
div { "a"
|
||||
@@ -31,7 +31,7 @@ static BtnA: FC<()> = |ctx, _| {
|
||||
}
|
||||
};
|
||||
|
||||
static BtnB: FC<()> = |ctx, _| {
|
||||
static BtnB: FC<()> = |ctx| {
|
||||
let (b, set) = use_read_write(ctx, &B);
|
||||
rsx! { in ctx,
|
||||
div { "b"
|
||||
|
||||
@@ -93,8 +93,8 @@ const ContentCards: SelectorFamily<Uuid, ContentCard> = |api, key| api.on_get_as
|
||||
data
|
||||
})
|
||||
|
||||
static ContentCard: FC<()> = |ctx, props| {
|
||||
let body = async match use_recoil_value()(props.id).await {
|
||||
static ContentCard: FC<()> = |ctx| {
|
||||
let body = async match use_recoil_value()(ctx.id).await {
|
||||
Ok(content) => rsx!{in ctx, p {"{content}"} }
|
||||
Err(e) => rsx!{in ctx, p {"Failed to load"}}
|
||||
};
|
||||
|
||||
+28
-22
@@ -31,7 +31,7 @@ mod traits {
|
||||
|
||||
// Atoms, selectors, and their family variants are readable
|
||||
pub trait Readable<T: AtomValue>: Sized + Copy {
|
||||
fn use_read<'a>(self, ctx: Context<'a>) -> &'a T {
|
||||
fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
|
||||
hooks::use_read(ctx, self)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ mod traits {
|
||||
// If the atom is currently pending, that future will resolve to pending
|
||||
// If the atom is currently ready, the future will immediately resolve
|
||||
// if the atom switches from ready to pending, the component will re-run, returning a pending future
|
||||
fn use_read_async<'a>(self, ctx: Context<'a>) -> &'a T {
|
||||
fn use_read_async<'a, P>(self, ctx: Context<'a, P>) -> &'a T {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ mod traits {
|
||||
// Only atoms and atom families are writable
|
||||
// Selectors and selector families are not
|
||||
pub trait Writable<T: AtomValue>: Readable<T> + Sized {
|
||||
fn use_read_write<'a>(self, ctx: Context<'a>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
fn use_read_write<'a, P>(self, ctx: Context<'a, P>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn use_write<'a>(self, ctx: Context<'a>) -> &'a Rc<dyn Fn(T)> {
|
||||
fn use_write<'a, P>(self, ctx: Context<'a, P>) -> &'a Rc<dyn Fn(T)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ mod atoms {
|
||||
use super::*;
|
||||
use dioxus_core::prelude::Context;
|
||||
|
||||
fn _test(ctx: Context) {
|
||||
fn _test(ctx: Context<()>) {
|
||||
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
|
||||
|
||||
// ensure that atoms are both read and write
|
||||
@@ -159,7 +159,7 @@ mod atomfamily {
|
||||
use super::*;
|
||||
const Titles: AtomHashMap<u32, &str> = |map| {};
|
||||
|
||||
fn test(ctx: Context) {
|
||||
fn test(ctx: Context<()>) {
|
||||
let title = Titles.select(&10).use_read(ctx);
|
||||
let t2 = use_read(ctx, &Titles.select(&10));
|
||||
}
|
||||
@@ -370,7 +370,7 @@ mod hooks {
|
||||
use super::*;
|
||||
use dioxus_core::{hooks::use_ref, prelude::Context};
|
||||
|
||||
pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) {
|
||||
pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
|
||||
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
|
||||
}
|
||||
|
||||
@@ -381,12 +381,12 @@ mod hooks {
|
||||
///
|
||||
/// You can use this method to create controllers that perform much more complex actions than set/get
|
||||
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
|
||||
pub fn use_recoil_api<'a>(ctx: Context<'a>) -> &Rc<RecoilContext> {
|
||||
pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc<RecoilContext> {
|
||||
ctx.use_context::<RecoilContext>()
|
||||
}
|
||||
|
||||
pub fn use_write<'a, T: AtomValue>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_write<'a, T: AtomValue, P>(
|
||||
ctx: Context<'a, P>,
|
||||
// todo: this shouldn't need to be static
|
||||
writable: impl Writable<T>,
|
||||
) -> &'a Rc<dyn Fn(T)> {
|
||||
@@ -412,7 +412,10 @@ mod hooks {
|
||||
/// Read the atom and get the Rc directly to the Atom's slot
|
||||
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
|
||||
/// work with as
|
||||
pub fn use_read_raw<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &Rc<T> {
|
||||
pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
readable: impl Readable<T>,
|
||||
) -> &Rc<T> {
|
||||
struct ReadHook<T> {
|
||||
value: Rc<T>,
|
||||
consumer_id: u32,
|
||||
@@ -446,7 +449,10 @@ mod hooks {
|
||||
}
|
||||
|
||||
///
|
||||
pub fn use_read<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &'a T {
|
||||
pub fn use_read<'a, T: AtomValue, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
readable: impl Readable<T>,
|
||||
) -> &'a T {
|
||||
use_read_raw(ctx, readable).as_ref()
|
||||
}
|
||||
|
||||
@@ -465,8 +471,8 @@ mod hooks {
|
||||
/// // equivalent to:
|
||||
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
|
||||
/// ```
|
||||
pub fn use_read_write<'a, T: AtomValue + 'static>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
writable: impl Writable<T>,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
(use_read(ctx, writable), use_write(ctx, writable))
|
||||
@@ -492,8 +498,8 @@ mod hooks {
|
||||
///
|
||||
/// modify_atom(&|a| *a += 1)
|
||||
/// ```
|
||||
pub fn use_modify<'a, T: AtomValue + 'static + Clone>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_modify<'a, T: AtomValue + 'static + Clone, P>(
|
||||
ctx: Context<'a, P>,
|
||||
writable: impl Writable<T>,
|
||||
) -> impl Fn(&dyn Fn()) {
|
||||
|_| {}
|
||||
@@ -502,8 +508,8 @@ mod hooks {
|
||||
/// Use a family collection directly
|
||||
/// !! Any changes to the family will cause this subscriber to update
|
||||
/// Try not to put this at the very top-level of your app.
|
||||
pub fn use_read_family<'a, K, V>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_read_family<'a, K, V, P>(
|
||||
ctx: Context<'a, P>,
|
||||
t: &AtomHashMap<K, V>,
|
||||
) -> &'a im_rc::HashMap<K, V> {
|
||||
todo!()
|
||||
@@ -525,11 +531,11 @@ mod utils {
|
||||
/// This tiny util wraps your main component with the initializer for the recoil root.
|
||||
/// This is useful for small programs and the examples in this crate
|
||||
pub fn RecoilApp<T: 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree,
|
||||
) -> impl for<'a> Fn(Context<'a>, &'a T) -> DomTree {
|
||||
move |ctx, props| {
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode,
|
||||
) -> impl for<'a> Fn(Context<'a, T>) -> VNode {
|
||||
move |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
root(ctx, props)
|
||||
root(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Router hook for Dioxus apps
|
||||
|
||||
Dioxus-router provides a use_router hook that returns a different value depending on the route.
|
||||
The router is generic over any value, however it makes sense to return a different set of VNodes
|
||||
Dioxus-router provides a use_router hook that returns a different value depending on the route.
|
||||
The router is generic over any value, however it makes sense to return a different set of VNodes
|
||||
and feed them into the App's return VNodes.
|
||||
|
||||
Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head.
|
||||
@@ -11,20 +11,20 @@ Here's an example of how to use the router hook:
|
||||
```rust
|
||||
static App: FC<()> = |ctx| {
|
||||
|
||||
// Route returns the associated VNodes
|
||||
// Route returns the associated VNodes
|
||||
// This hook re-fires when the route changes
|
||||
let route = use_router(ctx, |cfg| {
|
||||
cfg.at("/").serve(|ctx| {
|
||||
html!{ <LandingPage /> }
|
||||
});
|
||||
|
||||
|
||||
cfg.at("/shoes/:id").serve(|ctx| {
|
||||
let id: Uuid = ctx.props.parse().unwrap();
|
||||
let id: Uuid = ctx.ctx.parse().unwrap();
|
||||
html!{ <ShoesPage id=id /> }
|
||||
});
|
||||
|
||||
cfg.at("/pants/:id").serve(|ctx| {
|
||||
let id: Uuid = ctx.props.parse().unwrap();
|
||||
let id: Uuid = ctx.ctx.parse().unwrap();
|
||||
html!{ <PantsPage id=id /> }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,8 +14,8 @@ struct ExampleProps {
|
||||
initial_name: String,
|
||||
}
|
||||
|
||||
static Example: FC<ExampleProps> = |ctx, props| {
|
||||
let dispaly_name = use_state_new(&ctx, move || props.initial_name.clone());
|
||||
static Example: FC<ExampleProps> = |ctx| {
|
||||
let dispaly_name = use_state_new(&ctx, move || ctx.initial_name.clone());
|
||||
|
||||
let buttons = ["Jack", "Jill", "Bob"].iter().map(|name| {
|
||||
rsx!{
|
||||
|
||||
@@ -61,7 +61,7 @@ impl<T> TextRenderer<T> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Immediately render a DomTree to string
|
||||
/// Immediately render a VNode to string
|
||||
pub fn to_text(root: VNode) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ fn html_render(
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let mut dom = VirtualDom::new(|ctx, props| {
|
||||
let mut dom = VirtualDom::new(|ctx| {
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::*;
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
h1 {"hello"}
|
||||
@@ -22,7 +22,7 @@ static App: FC<()> = |ctx, _| {
|
||||
})
|
||||
};
|
||||
|
||||
static C1: FC<()> = |ctx, props| {
|
||||
static C1: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
"numba 1"
|
||||
@@ -30,7 +30,7 @@ static C1: FC<()> = |ctx, props| {
|
||||
})
|
||||
};
|
||||
|
||||
static C2: FC<()> = |ctx, props| {
|
||||
static C2: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
"numba 2"
|
||||
|
||||
@@ -24,7 +24,7 @@ fn main() {
|
||||
#[derive(Debug)]
|
||||
struct CustomContext([&'static str; 3]);
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"]));
|
||||
|
||||
ctx.render(rsx! {
|
||||
@@ -52,9 +52,9 @@ struct ButtonProps {
|
||||
id: u8,
|
||||
}
|
||||
|
||||
fn CustomButton(ctx: Context, props: &ButtonProps) -> DomTree {
|
||||
fn CustomButton(ctx: Context, props: &ButtonProps) -> VNode {
|
||||
let names = ctx.use_context::<CustomContext>();
|
||||
let name = names.0[props.id as usize];
|
||||
let name = names.0[ctx.id as usize];
|
||||
|
||||
ctx.render(rsx!{
|
||||
button {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||
|
||||
fn main() {
|
||||
@@ -7,8 +9,8 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
|
||||
}
|
||||
|
||||
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
|
||||
fn CustomA(ctx: Context<()>) -> VNode {
|
||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string() as String);
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
@@ -23,46 +25,48 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
||||
onclick: move |_| set_val(val.to_ascii_lowercase())
|
||||
}
|
||||
components::CustomB {
|
||||
val: val.as_ref()
|
||||
val: val.clone()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mod components {
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Props, PartialEq)]
|
||||
pub struct PropsB<'src> {
|
||||
val: &'src str,
|
||||
pub struct PropsB {
|
||||
val: String,
|
||||
}
|
||||
|
||||
pub fn CustomB<'a>(ctx: Context<'a>, props: &'a PropsB<'a>) -> DomTree {
|
||||
let (first, last) = props.val.split_at(3);
|
||||
pub fn CustomB(ctx: Context<PropsB>) -> VNode {
|
||||
let (first, last) = ctx.val.split_at(3);
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "m-8"
|
||||
"CustomB {props.val}"
|
||||
"CustomB {ctx.val}"
|
||||
CustomC {
|
||||
val: first
|
||||
val: first.to_string()
|
||||
}
|
||||
CustomC {
|
||||
val: last
|
||||
val: last.to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Props, PartialEq)]
|
||||
struct PropsC<'src> {
|
||||
val: &'src str,
|
||||
struct PropsC {
|
||||
val: String,
|
||||
}
|
||||
|
||||
fn CustomC<'a>(ctx: Context<'a>, props: &'a PropsC<'a>) -> DomTree {
|
||||
fn CustomC(ctx: Context<PropsC>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "m-8"
|
||||
"CustomC {props.val}"
|
||||
"CustomC {ctx.val}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
main { class: "dark:bg-gray-800 bg-white relative h-screen"
|
||||
NavBar {}
|
||||
@@ -15,7 +15,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
||||
})
|
||||
}
|
||||
|
||||
fn NavBar(ctx: Context, props: &()) -> DomTree {
|
||||
fn NavBar(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
|
||||
div { class: "container mx-auto px-6 flex items-center justify-between"
|
||||
@@ -55,7 +55,7 @@ fn NavBar(ctx: Context, props: &()) -> DomTree {
|
||||
})
|
||||
}
|
||||
|
||||
fn Landing(ctx: Context, props: &()) -> DomTree {
|
||||
fn Landing(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
|
||||
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
|
||||
|
||||
@@ -8,7 +8,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let cansee = use_state_new(&ctx, || false);
|
||||
rsx! { in ctx,
|
||||
div {
|
||||
@@ -22,7 +22,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
||||
}
|
||||
}
|
||||
|
||||
fn Child(ctx: Context, props: &()) -> DomTree {
|
||||
fn Child(ctx: Context<()>) -> VNode {
|
||||
rsx! { in ctx,
|
||||
section { class: "py-6 bg-coolGray-100 text-coolGray-900"
|
||||
div { class: "container mx-auto flex flex-col items-center justify-center p-4 space-y-8 md:p-10 md:px-24 xl:px-48"
|
||||
|
||||
@@ -9,7 +9,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
span {
|
||||
|
||||
@@ -11,7 +11,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// this is a component
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (event, set_event) = use_state(&ctx, || None);
|
||||
|
||||
let handler = move |evt: MouseEvent| {
|
||||
@@ -48,10 +48,10 @@ struct ExampleProps {
|
||||
name: String
|
||||
}
|
||||
|
||||
static Example2: FC<ExampleProps> = |ctx, props| {
|
||||
static Example2: FC<ExampleProps> = |ctx| {
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
h1 {"hello {props.name}"}
|
||||
h1 {"hello {ctx.name}"}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (name, set_name) = use_state(&ctx, || "...?");
|
||||
|
||||
log::debug!("Running component....");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct TodoItem {
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
@@ -143,7 +143,7 @@ static App: FC<()> = |ctx, _| {
|
||||
))
|
||||
};
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
// let reducer = recoil::use_callback(&ctx, || ());
|
||||
// let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
// }
|
||||
|
||||
// fn Example(ctx: Context, props: ()) -> DomTree {
|
||||
// fn Example(ctx: Context, props: ()) -> VNode {
|
||||
// let user_data = use_sql_query(&ctx, USER_DATA_QUERY);
|
||||
|
||||
// ctx.render(rsx! {
|
||||
|
||||
@@ -21,8 +21,8 @@ struct ExampleProps {
|
||||
initial_name: &'static str,
|
||||
}
|
||||
|
||||
static Example: FC<ExampleProps> = |ctx, props| {
|
||||
let name = use_state_new(&ctx, move || props.initial_name);
|
||||
static Example: FC<ExampleProps> = |ctx| {
|
||||
let name = use_state_new(&ctx, move || ctx.initial_name);
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
@@ -53,12 +53,12 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> {
|
||||
handler: F
|
||||
}
|
||||
|
||||
fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> DomTree {
|
||||
fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
button {
|
||||
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
||||
onmouseover: {&props.handler}
|
||||
"{props.name}"
|
||||
onmouseover: {&ctx.handler}
|
||||
"{ctx.name}"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -74,10 +74,10 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
|
||||
struct PlaceholderProps {
|
||||
val: &'static str
|
||||
}
|
||||
fn Placeholder(ctx: Context, props: &PlaceholderProps) -> DomTree {
|
||||
fn Placeholder(ctx: Context, props: &PlaceholderProps) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
"child: {props.val}"
|
||||
"child: {ctx.val}"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::recoil;
|
||||
use crate::state::{FilterState, TODOS};
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let reducer = recoil::use_callback(&ctx, || ());
|
||||
let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ mod todolist;
|
||||
static APP_STYLE: &'static str = include_str!("./style.css");
|
||||
|
||||
fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
id: "app"
|
||||
|
||||
@@ -7,9 +7,9 @@ pub struct TodoEntryProps {
|
||||
id: uuid::Uuid,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
let todo = use_atom_family(&ctx, &TODOS, props.id);
|
||||
let todo = use_atom_family(&ctx, &TODOS, ctx.id);
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
};
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
pub fn TodoList(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
|
||||
let filter = use_atom(&ctx, &FILTER);
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct TodoItem {
|
||||
// =======================
|
||||
// Components
|
||||
// =======================
|
||||
pub fn App(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn App(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
id: "app"
|
||||
@@ -51,7 +51,7 @@ pub fn App(ctx: Context, props: &()) -> DomTree {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn TodoList(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (todos, set_todos) = use_state(&ctx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
|
||||
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||
@@ -100,17 +100,17 @@ pub struct TodoEntryProps {
|
||||
item: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
// #[inline_props]
|
||||
pub fn TodoEntry(
|
||||
ctx: Context,
|
||||
baller: &impl Fn() -> (),
|
||||
caller: &impl Fn() -> (),
|
||||
todo: &Rc<TodoItem>,
|
||||
) -> DomTree {
|
||||
// pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> DomTree {
|
||||
) -> VNode {
|
||||
// pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
// let todo = &props.item;
|
||||
// let todo = &ctx.item;
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
@@ -129,7 +129,7 @@ pub fn TodoEntry(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let toggles = [
|
||||
("All", "", FilterState::All),
|
||||
("Active", "active", FilterState::Active),
|
||||
|
||||
@@ -72,7 +72,7 @@ impl TodoManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn TodoList(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let draft = use_state_new(&ctx, || "".to_string());
|
||||
let todos = use_read(&ctx, &TODO_LIST);
|
||||
let filter = use_read(&ctx, &FILTER);
|
||||
@@ -116,9 +116,9 @@ pub struct TodoEntryProps {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
let todo = use_read(&ctx, &TODO_LIST).get(&props.id).unwrap();
|
||||
let todo = use_read(&ctx, &TODO_LIST).get(&ctx.id).unwrap();
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
@@ -137,7 +137,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let reducer = TodoManager(use_recoil_api(ctx));
|
||||
let items_left = use_read(ctx, &TODOS_LEFT);
|
||||
|
||||
@@ -178,7 +178,7 @@ pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn Footer(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn Footer(ctx: Context<()>) -> VNode {
|
||||
rsx! { in ctx,
|
||||
footer {
|
||||
class: "info"
|
||||
@@ -197,7 +197,7 @@ pub fn Footer(ctx: Context, _props: &()) -> DomTree {
|
||||
|
||||
const APP_STYLE: &'static str = include_str!("./todomvc/style.css");
|
||||
|
||||
fn App(ctx: Context, _props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
rsx! { in ctx,
|
||||
div { id: "app"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! basic example that renders a simple domtree to the page :)
|
||||
//! basic example that renders a simple VNode to the page :)
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
@@ -9,7 +9,7 @@ fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-col">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! --------------
|
||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||
|
||||
use dioxus::prelude::{Context, DomTree, Properties};
|
||||
use dioxus::prelude::{Context, Properties, VNode};
|
||||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
@@ -33,7 +33,7 @@ impl WebsysRenderer {
|
||||
///
|
||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||
/// Pairs well with the wasm_bindgen async handler
|
||||
pub async fn start(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) {
|
||||
pub async fn start(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) {
|
||||
Self::new(root).run().await.expect("Virtual DOM failed :(");
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl WebsysRenderer {
|
||||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ impl WebsysRenderer {
|
||||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new_with_props<T: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
|
||||
root_props: T,
|
||||
) -> Self {
|
||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
||||
@@ -178,7 +178,7 @@ mod tests {
|
||||
pretty_env_logger::init();
|
||||
log::info!("Hello!");
|
||||
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
todo!()
|
||||
// ctx.render(html! {
|
||||
// <div>
|
||||
|
||||
@@ -147,7 +147,7 @@ fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
fn Component(ctx: Context, props: ()) -> DomTree {
|
||||
fn Component(ctx: Context, props: ()) -> VNode {
|
||||
let user_data = use_sql_query(&ctx, USER_DATA_QUERY);
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
||||
@@ -36,22 +36,22 @@ fn blah() {
|
||||
let handler = dioxus_liveview::new_handler()
|
||||
.from_directory("abc123") // serve a given directory as the root
|
||||
.with_context(|| SomeContext {}) // build out a new context for all of the server-rendered components to share
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props: &ServerRendered| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx: &ServerRendered| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
// depend on the framework, build a different type of handler
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() {
|
||||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<svg class="octicon octicon-star v-align-text-bottom"
|
||||
@@ -36,7 +36,7 @@ static Example: FC<()> = |ctx, _props| {
|
||||
</div>
|
||||
})
|
||||
};
|
||||
// static Example: FC<()> = |ctx, _props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// ctx.render(rsx! {
|
||||
// div {
|
||||
// class: "flex items-center justify-center flex-col"
|
||||
|
||||
Reference in new issue
Block a user