Remove phantom markers and just default to Rc<dyn Fn(props) -> Element> where it makes sense

This commit is contained in:
Jonathan Kelley committed 2024-01-16 15:07:22 -08:00
1 parent 397015df31
commit c94af9538b
9 files changed
+108 -234

No files matched your search

+8 -13
View File
@@ -1,5 +1,5 @@
use crate::{nodes::RenderReturn, properties::ComponentFunction};
use std::{any::Any, marker::PhantomData, ops::Deref, panic::AssertUnwindSafe};
use crate::{nodes::RenderReturn, Component};
use std::{any::Any, ops::Deref, panic::AssertUnwindSafe};
/// A boxed version of AnyProps that can be cloned
pub(crate) struct BoxedAnyProps {
@@ -38,17 +38,16 @@ pub(crate) trait AnyProps {
fn duplicate(&self) -> Box<dyn AnyProps>;
}
pub(crate) struct VProps<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> {
pub render_fn: F,
pub(crate) struct VProps<P: 'static> {
pub render_fn: Component<P>,
pub memo: fn(&P, &P) -> bool,
pub props: P,
pub name: &'static str,
phantom: PhantomData<Phantom>,
}
impl<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> VProps<P, F, Phantom> {
impl<P: 'static> VProps<P> {
pub(crate) fn new(
render_fn: F,
render_fn: Component<P>,
memo: fn(&P, &P) -> bool,
props: P,
name: &'static str,
@@ -58,14 +57,11 @@ impl<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> VPr
memo,
props,
name,
phantom: PhantomData,
}
}
}
impl<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, Phantom> AnyProps
for VProps<P, F, Phantom>
{
impl<P: Clone + 'static> AnyProps for VProps<P> {
fn memoize(&self, other: &dyn Any) -> bool {
match other.downcast_ref::<P>() {
Some(other) => (self.memo)(&self.props, other),
@@ -80,7 +76,7 @@ impl<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, Phantom> AnyP
fn render(&self) -> RenderReturn {
let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
// Call the render function directly
self.render_fn.call(self.props.clone())
(self.render_fn)(self.props.clone())
}));
match res {
@@ -100,7 +96,6 @@ impl<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, Phantom> AnyP
memo: self.memo,
props: self.props.clone(),
name: self.name,
phantom: PhantomData,
})
}
}
+6 -6
View File
@@ -70,12 +70,12 @@ pub(crate) mod innerlude {
/// Example {}
/// )
/// ```
pub type Component<P = ()> = fn(P) -> Element;
pub type Component<P = ()> = std::rc::Rc<dyn Fn(P) -> Element>;
}
pub use crate::innerlude::{
fc_to_builder, generation, schedule_update, schedule_update_any, use_hook, vdom_is_rendering,
AnyValue, Attribute, AttributeValue, BoxedContext, CapturedError, Component, ComponentFunction,
AnyValue, Attribute, AttributeValue, BoxedContext, CapturedError, Component,
CrossPlatformConfig, DynamicNode, Element, ElementId, Event, Fragment, HasAttributes,
IntoDynNode, Mutation, Mutations, NoOpMutations, PlatformBuilder, Properties, RenderReturn,
ScopeId, ScopeState, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
@@ -90,9 +90,9 @@ pub mod prelude {
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
has_context, needs_update, parent_scope, provide_context, provide_root_context,
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element,
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, Task, Template,
TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom,
try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component,
ComponentFn, Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes,
IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState,
Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom,
};
}
+12 -13
View File
@@ -1,13 +1,14 @@
use crate::innerlude::{ElementRef, EventHandler, MountId};
use crate::properties::ComponentFunction;
use crate::{
any_props::{BoxedAnyProps, VProps},
innerlude::ScopeState,
};
use crate::{arena::ElementId, Element, Event};
use crate::{
innerlude::{ElementRef, EventHandler, MountId},
properties::ComponentFn,
};
use crate::{Properties, VirtualDom};
use core::panic;
use std::ops::Deref;
use std::rc::Rc;
use std::vec;
use std::{
@@ -15,6 +16,7 @@ use std::{
cell::Cell,
fmt::{Arguments, Debug},
};
use std::{ffi::c_void, ops::Deref};
pub type TemplateId = &'static str;
@@ -511,7 +513,7 @@ pub struct VComponent {
/// The function pointer of the component, known at compile time
///
/// It is possible that components get folded at compile time, so these shouldn't be really used as a key
pub(crate) render_fn: TypeId,
pub(crate) render_fn: *const c_void,
pub(crate) props: BoxedAnyProps,
}
@@ -531,22 +533,19 @@ impl VComponent {
/// fn(Props) -> Element;
/// async fn(Scope<Props<'_>>) -> Element;
/// ```
pub fn new<F: ComponentFunction<P> + 'static, P: 'static>(
component: F,
props: F::Props,
fn_name: &'static str,
) -> Self
pub fn new<P, M>(component: impl ComponentFn<P, M>, props: P, fn_name: &'static str) -> Self
where
// The properties must be valid until the next bump frame
F::Props: Properties + 'static,
P: Properties + 'static,
{
let render_fn_id = TypeId::of::<F>();
let vcomp = VProps::new(component, F::Props::memoize, props, fn_name);
let component = Rc::new(component).as_component();
let render_fn = component.as_ref() as *const _ as *const c_void;
let vcomp = VProps::new(component, <P as Properties>::memoize, props, fn_name);
VComponent {
name: fn_name,
render_fn: render_fn_id,
props: BoxedAnyProps::new(vcomp),
render_fn,
}
}
+13 -26
View File
@@ -1,6 +1,6 @@
use std::{any::Any, marker::PhantomData};
use std::any::Any;
use crate::{ComponentFunction, VirtualDom};
use crate::{properties::ComponentFn, Component, VirtualDom};
/// A boxed object that can be injected into a component's context.
pub struct BoxedContext(Box<dyn ClonableAny>);
@@ -40,33 +40,26 @@ impl<T: Any + Clone> ClonableAny for T {
}
/// The platform-independent part of the config needed to launch an application.
pub struct CrossPlatformConfig<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> {
pub struct CrossPlatformConfig<Props: Clone + 'static> {
/// The root component function.
pub component: Component,
pub component: Component<Props>,
/// The props for the root component.
pub props: Props,
/// The contexts to provide to the root component.
pub root_contexts: Vec<BoxedContext>,
_phantom: PhantomData<Phantom>,
}
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> CrossPlatformConfig<Component, Props, Phantom>
{
impl<Props: Clone + 'static> CrossPlatformConfig<Props> {
/// Create a new cross-platform config.
pub fn new(component: Component, props: Props, root_contexts: Vec<BoxedContext>) -> Self {
pub fn new<M>(
component: impl ComponentFn<Props, M>,
props: Props,
root_contexts: Vec<BoxedContext>,
) -> Self {
Self {
component,
component: ComponentFn::as_component(std::rc::Rc::new(component)),
props,
root_contexts,
_phantom: PhantomData,
}
}
@@ -88,19 +81,13 @@ pub trait PlatformBuilder<Props: Clone + 'static> {
type Config: Default;
/// Launch the app.
fn launch<Component: ComponentFunction<Phantom, Props = Props>, Phantom: 'static>(
config: CrossPlatformConfig<Component, Props, Phantom>,
platform_config: Self::Config,
);
fn launch(config: CrossPlatformConfig<Props>, platform_config: Self::Config);
}
impl<Props: Clone + 'static> PlatformBuilder<Props> for () {
type Config = ();
fn launch<Component: ComponentFunction<Phantom, Props = Props>, Phantom: 'static>(
_: CrossPlatformConfig<Component, Props, Phantom>,
_: Self::Config,
) {
fn launch(_: CrossPlatformConfig<Props>, _: Self::Config) {
panic!("No platform is currently enabled. Please enable a platform feature for the dioxus crate.");
}
}
+34 -83
View File
@@ -1,3 +1,5 @@
use std::rc::Rc;
use crate::innerlude::*;
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
@@ -29,11 +31,7 @@ pub trait Properties: Clone + Sized + 'static {
/// Create a builder for this component.
fn builder() -> Self::Builder;
/// Memoization can only happen if the props are valid for the 'static lifetime
///
/// # Safety
/// The user must know if their props are static, but if they make a mistake, UB happens
/// Therefore it's unsafe to memoize.
/// Compare two props to see if they are memoizable.
fn memoize(&self, other: &Self) -> bool;
}
@@ -56,98 +54,51 @@ impl EmptyBuilder {
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
/// to initialize a component's props.
pub fn fc_to_builder<F: ComponentFunction<P>, P>(
_: F,
) -> <<F as ComponentFunction<P>>::Props as Properties>::Builder
pub fn fc_to_builder<P, M>(_: impl ComponentFn<P, M>) -> <P as Properties>::Builder
where
F::Props: Properties,
P: Properties,
{
F::Props::builder()
P::builder()
}
/// Every component used in rsx must implement the `ComponentFunction` trait. This trait tells dioxus how your component should be rendered.
///
/// Dioxus automatically implements this trait for any function that either takes no arguments or a single props argument and returns an Element.
///
/// ## Example
///
/// For components that take no props:
///
/// ```rust
/// fn app() -> Element {
/// rsx! {
/// div {}
/// }
/// }
/// ```
///
/// For props that take a props struct:
///
/// ```rust
/// #[derive(Props, PartialEq, Clone)]
/// struct MyProps {
/// data: String
/// }
///
/// fn app(props: MyProps) -> Element {
/// rsx! {
/// div {
/// "{props.data}"
/// }
/// }
/// }
/// ```
///
/// Or you can use the #[component] macro to automatically implement create the props struct:
///
/// ```rust
/// #[component]
/// fn app(data: String) -> Element {
/// rsx! {
/// div {
/// "{data}"
/// }
/// }
/// }
/// ```
///
/// > Note: If you get an error about the `ComponentFunction` trait not being implemented: make sure your props implements the `Properties` trait or if you would like to declare your props inline, make sure you use the #[component] macro on your function.
pub trait ComponentFunction<P>: Clone + 'static {
/// The props type for this component.
type Props: 'static;
/// Run the component function with the given props.
fn call(&self, props: Self::Props) -> Element;
/// Any component that implements the `ComponentFn` trait can be used as a component.
pub trait ComponentFn<Props, Marker> {
/// Convert the component to a function that takes props and returns an element.
fn as_component(self: Rc<Self>) -> Component<Props>;
}
impl<T: 'static, F: Fn(T) -> Element + Clone + 'static> ComponentFunction<(T,)> for F {
type Props = T;
fn call(&self, props: T) -> Element {
self(props)
/// Accept pre-formed component render functions as components
impl<P> ComponentFn<P, ()> for Component<P> {
fn as_component(self: Rc<Self>) -> Component<P> {
self.as_ref().clone()
}
}
#[doc(hidden)]
pub struct ZeroElementMarker;
impl<F: Fn() -> Element + Clone + 'static> ComponentFunction<ZeroElementMarker> for F {
type Props = ();
/// Accept any callbacks that take props
impl<F: Fn(P) -> Element + 'static, P> ComponentFn<P, ()> for F {
fn as_component(self: Rc<Self>) -> Component<P> {
self
}
}
fn call(&self, _: ()) -> Element {
self()
/// Accept any callbacks that take no props
pub struct EmptyMarker;
impl<F: Fn() -> Element + 'static> ComponentFn<(), EmptyMarker> for F {
fn as_component(self: Rc<Self>) -> Rc<dyn Fn(()) -> Element> {
Rc::new(move |_| self())
}
}
#[test]
fn test_empty_builder() {
fn app() -> Element {
unimplemented!()
fn it_works_maybe() {
fn test(_: ()) -> Element {
todo!()
}
fn app2(_: ()) -> Element {
unimplemented!()
fn test2() -> Element {
todo!()
}
let builder = fc_to_builder(app);
builder.build();
let builder = fc_to_builder(app2);
builder.build();
let callable: Rc<dyn ComponentFn<(), ()>> = Rc::new(test) as Rc<dyn ComponentFn<_, _>>;
let callable2: Rc<dyn ComponentFn<(), EmptyMarker>> =
Rc::new(test2) as Rc<dyn ComponentFn<_, _>>;
}
+10 -8
View File
@@ -11,6 +11,7 @@ use crate::{
},
nodes::RenderReturn,
nodes::{Template, TemplateId},
properties::ComponentFn,
runtime::{Runtime, RuntimeGuard},
scopes::ScopeId,
AttributeValue, BoxedContext, Element, Event, Mutations,
@@ -227,7 +228,7 @@ impl VirtualDom {
///
/// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
pub fn new(app: fn() -> Element) -> Self {
Self::new_with_props(app, ())
Self::new_with_props(move || app(), ())
}
/// Create a new virtualdom and build it immediately
@@ -267,12 +268,8 @@ impl VirtualDom {
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<
F: crate::ComponentFunction<Phantom, Props = P>,
P: Clone + 'static,
Phantom: 'static,
>(
root: F,
pub fn new_with_props<P: Clone + 'static, M>(
root: impl ComponentFn<P, M>,
root_props: P,
) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
@@ -290,7 +287,12 @@ impl VirtualDom {
};
let root = dom.new_scope(
BoxedAnyProps::new(VProps::new(root, |_, _| true, root_props, "root")),
BoxedAnyProps::new(VProps::new(
Rc::new(root).as_component(),
|_, _| true,
root_props,
"root",
)),
"app",
);