Feat: revert FC changes (like the old style).

This commit reverts to the old style of props + FC. The old style is desirable
because people comfortable with react can automatically be comfortable with
dioxus. It's also nice in that the same props can be used to drive two different
components - something the trait version couldn't do. Now, our trait bound forces
implementations to have the #[derive(Props)] flag. This will need to implement the
Properties trait as well as PartialEq (using ptr::equal for closure fields).
This commit is contained in:
Jonathan Kelley 2021-03-09 00:58:20 -05:00
parent 55a5541dba
commit 7158bc3575
15 changed files with 1339 additions and 137 deletions

View file

@ -5,6 +5,7 @@ use syn::parse_macro_input;
mod fc;
mod htm;
mod ifmt;
mod props;
mod rsxt;
mod util;
@ -59,3 +60,12 @@ pub fn format_args_f(input: TokenStream) -> TokenStream {
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
#[proc_macro_derive(Props, attributes(builder))]
pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
match props::impl_my_derive(&input) {
Ok(output) => output.into(),
Err(error) => error.to_compile_error().into(),
}
}

File diff suppressed because it is too large Load diff

View file

@ -198,14 +198,19 @@ impl Parse for Component {
// parse the guts
let content: ParseBuffer;
syn::braced!(content in s);
let mut fields: Vec<Field> = Vec::new();
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
}
let mut body: Vec<Field> = Vec::new();
let mut attrs = Vec::new();
let mut children: Vec<Node> = Vec::new();
parse_element_content(content, &mut attrs, &mut children);
// 'parsing: loop {
// // [1] Break if empty
// if content.is_empty() {
// break 'parsing;
// }
// }
// let body = content.parse()?;

View file

@ -7,12 +7,15 @@
fn main() {}
use dioxus_core::prelude::*;
use std::fmt::Debug;
use dioxus_core::{component::Properties, prelude::*};
struct Props {
items: Vec<ListItem>,
}
#[derive(PartialEq)]
struct ListItem {
name: String,
age: u32,
@ -21,18 +24,18 @@ struct ListItem {
fn app(ctx: Context, props: &Props) -> DomTree {
let (f, setter) = use_state(&ctx, || 0);
ctx.render(move |b| {
let mut root = builder::ElementBuilder::new(b, "div");
ctx.render(move |c| {
let mut root = builder::ElementBuilder::new(c, "div");
for child in &props.items {
// notice that the child directly borrows from our vec
// this makes lists very fast (simply views reusing lifetimes)
// <ChildItem item=child hanldler=setter />
root = root.child(builder::virtual_child(
b.bump,
c,
ChildProps {
item: child,
item_handler: setter,
},
// <ChildItem item=child hanldler=setter />
child_item,
));
}
@ -41,7 +44,16 @@ fn app(ctx: Context, props: &Props) -> DomTree {
}
type StateSetter<T> = dyn Fn(T);
// struct StateSetter<T>(dyn Fn(T));
// impl<T> PartialEq for StateSetter<T> {
// fn eq(&self, other: &Self) -> bool {
// self as *const _ == other as *const _
// }
// }
// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
#[derive(Props)]
struct ChildProps<'a> {
// Pass down complex structs
item: &'a ListItem,
@ -50,6 +62,19 @@ struct ChildProps<'a> {
item_handler: &'a StateSetter<i32>,
}
impl PartialEq for ChildProps<'_> {
fn eq(&self, other: &Self) -> bool {
todo!()
}
}
impl<'a> Properties for ChildProps<'a> {
type Builder = ChildPropsBuilder<'a, ((), ())>;
fn builder() -> <Self as Properties>::Builder {
ChildProps::builder()
}
}
fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
todo!()
// ctx.render(rsx! {

View file

@ -1,5 +1,5 @@
use dioxus_core::prelude::*;
use dioxus_core::scope::FC;
use dioxus_core_macro::fc;
use std::marker::PhantomData;
@ -22,7 +22,7 @@ use std::marker::PhantomData;
// always try to fill in with Default
#[fc]
// #[fc]
fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree {
ctx.render(rsx! {
div {
@ -33,7 +33,7 @@ fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree {
})
}
#[fc]
// #[fc]
fn SomeComponent(ctx: Context, a: &str, b: &str) -> DomTree {
todo!()
}

View file

@ -1,32 +1,20 @@
use std::marker::PhantomData;
use dioxus_core_macro::Props;
fn main() {}
#[derive(Debug, Props)]
struct SomeProps {
a: i32,
trait Props<'parent> {}
struct SomeProps<'p> {
text: &'p str,
// automatically do the default (none) and automatically Into<T>
#[builder(default, setter(strip_option))]
b: Option<i32>,
}
impl<'p> Props<'p> for SomeProps<'p> {}
// have we committed to the trait style yet?
struct OutputNode<'a> {
_p: PhantomData<&'a ()>,
fn main() {
let g: SomeProps = SomeProps::builder().a(10).b(10).build();
let r = g.b.unwrap_or_else(|| 10);
}
// combine reference to self (borrowed from self) and referenfce to parent (borrowed from parent)
// borrow chain looks like 'p + 's -> 'p + 's -> 'p + 's
// always adding new lifetimes from self into the mix
// what does a "self" lifetime mean?
// a "god" gives us our data
// the god's lifetime is tied to Context, and the borrowed props object
// for the sake of simplicity, we just clobber lifetimes.
// user functions are just lies and we abuse lifetimes.
// everything is managed at runtime because that's how we make something ergonomc
// lifetime management in dioxus is just cheating around the rules
// our kind god manages lifetimes for us so we don't have to, thanks god
fn something<'s>(_props: &'s SomeProps<'s>) -> OutputNode<'s> {
todo!()
}
// type BC<'p, P: Props<'p>> = for<'a, 'b, 'c> fn(&'a P<'b>) -> OutputNode<'c>;
fn auto_into_some() {}

View file

@ -1,20 +1,19 @@
use dioxus_core::prelude::*;
use dioxus_core::{component::Properties, prelude::*};
fn main() -> Result<(), ()> {
let p1 = Props { name: "bob".into() };
let p1 = SomeProps { name: "bob".into() };
let mut vdom = VirtualDom::new_with_props(Example, p1);
// vdom.update_props(|p: &mut Props| {});
Ok(())
}
#[derive(Debug, PartialEq)]
struct Props {
#[derive(Debug, PartialEq, Props)]
struct SomeProps {
name: String,
}
static Example: FC<Props> = |ctx, _props| {
static Example: FC<SomeProps> = |ctx, _props| {
ctx.render(html! {
<div>
<h1> "hello world!" </h1>
@ -24,3 +23,11 @@ static Example: FC<Props> = |ctx, _props| {
</div>
})
};
// toodo: derive this
impl Properties for SomeProps {
type Builder = SomePropsBuilder<((),)>;
fn builder() -> Self::Builder {
SomeProps::builder()
}
}

View file

@ -1,69 +1,33 @@
//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
//! for components to be used within Nodes.
//!
//! 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.
use crate::innerlude::FC;
pub type ScopeIdx = generational_arena::Index;
/// The `Component` trait refers to any struct or funciton that can be used as a component
/// We automatically implement Component for FC<T>
// pub trait Component {
// type Props: Properties<'static>;
// fn builder(&'static self) -> Self::Props;
// }
// // Auto implement component for a FC
// // Calling the FC is the same as "rendering" it
// impl<P: Properties<'static>> Component for FC<P> {
// type Props = P;
// fn builder(&self) -> Self::Props {
// todo!()
// }
// }
/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
/// Components take a "properties" object
// pub trait Properties<'a>
// where
// Self: Debug,
// {
// fn call(&self, ptr: *const ()) {}
// }
// // Auto implement for no-prop components
// impl<'a> Properties<'a> for () {
// fn call(&self, ptr: *const ()) {}
// }
#[cfg(test)]
mod tests {
use crate::prelude::bumpalo::Bump;
use crate::prelude::*;
// fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
// todo!()
// }
// static TestComponent: FC<()> = |ctx, props| {
// //
// ctx.render(html! {
// <div>
// </div>
// })
// };
// static TestComponent2: FC<()> = |ctx, props| {
// //
// ctx.render(|ctx| VNode::text("blah"))
// };
// #[test]
// fn ensure_types_work() {
// let bump = Bump::new();
// // Happiness! The VNodes are now allocated onto the bump vdom
// let _ = test_static_fn(&bump, TestComponent);
// let _ = test_static_fn(&bump, TestComponent2);
// }
pub trait Properties: PartialEq {
type Builder;
fn builder() -> Self::Builder;
}
pub struct EmptyBuilder;
impl EmptyBuilder {
pub fn build() -> () {
()
}
}
impl Properties for () {
type Builder = EmptyBuilder;
fn builder() -> Self::Builder {
EmptyBuilder {}
}
}
pub fn fc_to_builder<T: Properties>(f: FC<T>) -> T::Builder {
T::builder()
}

View file

@ -99,7 +99,7 @@ impl<'a> Context<'a> {
/// ctx.render(lazy_tree)
/// }
///```
pub fn render(self, lazy_nodes: impl FnOnce(&NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
pub fn render(self, lazy_nodes: impl FnOnce(&'_ NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
let ctx = NodeCtx {
bump: self.bump,
scope: self.scope,

View file

@ -21,11 +21,12 @@ impl DebugRenderer {
pub fn log_dom(&self) {}
}
#[cfg(old)]
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use crate::scope::FC;
use crate::scope::Properties;
#[test]
fn ensure_creation() -> Result<(), ()> {

View file

@ -90,6 +90,7 @@ pub mod builder {
pub(crate) mod innerlude {
// pub(crate) use crate::component::Properties;
pub(crate) use crate::component::Properties;
pub(crate) use crate::context::Context;
pub(crate) use crate::error::{Error, Result};
use crate::nodes;
@ -106,7 +107,7 @@ pub(crate) mod innerlude {
// pub use nodes::iterables::IterableNodes;
/// This type alias is an internal way of abstracting over the static functions that represent components.
// pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
mod fc2 {}
// pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
@ -137,7 +138,7 @@ pub mod prelude {
// pub use nodes::iterables::IterableNodes;
/// This type alias is an internal way of abstracting over the static functions that represent components.
// pub use crate::innerlude::FC;
pub use crate::innerlude::FC;
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
@ -151,8 +152,8 @@ pub mod prelude {
pub use crate as dioxus;
pub use crate::nodebuilder as builder;
// pub use dioxus_core_macro::fc;
pub use dioxus_core_macro::format_args_f;
pub use dioxus_core_macro::{html, rsx};
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
pub use crate::component::ScopeIdx;
pub use crate::diff::DiffMachine;

View file

@ -5,7 +5,7 @@ use std::{borrow::BorrowMut, ops::Deref};
use crate::{
context::NodeCtx,
events::VirtualEvent,
innerlude::VComponent,
innerlude::{VComponent, FC},
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement,
};
@ -526,7 +526,11 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
// _f: crate::innerlude::FC<T>,
// _props: T
pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> {
pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>(
ctx: &'b NodeCtx<'a>,
p: T,
f: FC<T>,
) -> VNode<'a> {
todo!()
// VNode::Component()
}

View file

@ -296,10 +296,7 @@ mod vtext {
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
mod vcomponent {
use crate::{
innerlude::{Context, ScopeIdx},
scope::FC,
};
use crate::innerlude::{Context, Properties, ScopeIdx, FC};
use std::{
any::{Any, TypeId},
cell::RefCell,
@ -345,7 +342,7 @@ mod vcomponent {
// this lets us keep scope generic *and* downcast its props when we need to:
// - perform comparisons when diffing (memoization)
// -
pub fn new<P: FC + 'static>(comp: P) -> Self {
pub fn new<P: Properties + 'static>(caller: FC<P>, comp: P) -> Self {
todo!()
// let p = Rc::new(props);

View file

@ -11,10 +11,6 @@ use std::{
ops::Deref,
};
pub trait FC: PartialEq {
fn render(ctx: Context, props: &Self) -> DomTree;
}
pub trait Scoped {
fn run(&mut self);
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
@ -30,15 +26,19 @@ pub trait Scoped {
///
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope<P: FC> {
pub struct Scope<P: Properties> {
// Map to the parent
pub parent: Option<ScopeIdx>,
// our own index
pub myidx: ScopeIdx,
// the props
pub props: P,
// and the actual render function
pub caller: FC<P>,
// ==========================
// slightly unsafe stuff
// ==========================
@ -61,7 +61,8 @@ pub struct Scope<P: FC> {
// instead of having it as a trait method, we use a single function
// todo: do the unsafety magic stuff to erase the type of p
pub fn create_scoped<P: FC + 'static>(
pub fn create_scoped<P: Properties + 'static>(
caller: FC<P>,
props: P,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
@ -84,6 +85,7 @@ pub fn create_scoped<P: FC + 'static>(
let frames = ActiveFrame::from_frames(old_frame, new_frame);
Box::new(Scope {
caller,
myidx,
hook_arena,
hooks,
@ -94,7 +96,7 @@ pub fn create_scoped<P: FC + 'static>(
})
}
impl<P: FC + 'static> Scoped for Scope<P> {
impl<P: Properties + 'static> Scoped for Scope<P> {
/// Create a new context and run the component with references from the Virtual Dom
/// This function downcasts the function pointer based on the stored props_type
///
@ -121,8 +123,8 @@ impl<P: FC + 'static> Scoped for Scope<P> {
// Note that the actual modification of the vnode head element occurs during this call
// let _: DomTree = caller(ctx, props);
let _: DomTree = P::render(ctx, &self.props);
// let _: DomTree = (self.caller)(ctx, &self.props);
// let _: DomTree = P::render (ctx, &self.props);
let _: DomTree = (self.caller)(ctx, &self.props);
/*
SAFETY ALERT
@ -256,6 +258,7 @@ impl ActiveFrame {
}
}
#[cfg(old)]
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1,6 +1,6 @@
// use crate::{changelist::EditList, nodes::VNode};
use crate::{innerlude::*, scope::FC};
use crate::innerlude::*;
use crate::{
patch::Edit,
scope::{create_scoped, Scoped},
@ -60,17 +60,16 @@ impl VirtualDom {
///
/// 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 Fn(Context) -> DomTree) -> Self {
todo!()
// Self::new_with_props(root)
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
/// Start a new VirtualDom instance with a dependent props.
/// 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
/// to toss out the entire tree.
pub fn new_with_props<P: FC + 'static>(root: P) -> Self {
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
// let mut components = Arena::new();
// let mut components = Arena::new();
// Create a reference to the component in the arena