diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 519d7b44d..7f73234e5 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -20,28 +20,29 @@ struct ListItem { } fn app(cx: Context) -> VNode { - let (val, set_val) = use_state_classic(&cx, || 0); + let (val, set_val) = use_state_classic(cx, || 0); cx.render(LazyNodes::new(move |_nodecx| { - builder::ElementBuilder::new(_nodecx, "div") - .iter_child({ - cx.items.iter().map(|child| { - builder::virtual_child( - _nodecx, - ChildItem, - ChildProps { - item: child.clone(), - item_handler: set_val.clone(), - }, - None, - &[], - ) - }) - }) - .iter_child([builder::ElementBuilder::new(_nodecx, "div") - .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))]) - .finish()]) - .finish() + todo!() + // builder::ElementBuilder::new(_nodecx, "div") + // .iter_child({ + // cx.items.iter().map(|child| { + // builder::virtual_child( + // _nodecx, + // ChildItem, + // ChildProps { + // item: child.clone(), + // item_handler: set_val.clone(), + // }, + // None, + // &[], + // ) + // }) + // }) + // .iter_child([builder::ElementBuilder::new(_nodecx, "div") + // .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))]) + // .finish()]) + // .finish() })) } diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 27c5f1c21..b5fcecf6a 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -37,7 +37,7 @@ use std::{ /// } /// ``` pub fn use_state_classic<'a, 'c, T: 'static, F: FnOnce() -> T>( - cx: &impl Scoped<'a>, + cx: impl Scoped<'a>, initial_state_fn: F, ) -> (&'a T, &'a Rc) { struct UseState { @@ -159,7 +159,7 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState { /// } /// ``` pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( - cx: &impl Scoped<'a>, + cx: impl Scoped<'a>, initial_state_fn: F, ) -> &'a UseState { cx.use_hook( @@ -231,7 +231,7 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( /// 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>( - cx: &impl Scoped<'a>, + cx: impl Scoped<'a>, initial_state_fn: impl FnOnce() -> T + 'static, ) -> &'a RefCell { cx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {}) @@ -250,7 +250,7 @@ struct UseReducer { /// This is behaves almost exactly the same way as React's "use_state". /// pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>( - cx: &impl Scoped<'a>, + cx: impl Scoped<'a>, initial_state_fn: impl FnOnce() -> State, _reducer: impl Fn(&mut State, Action), ) -> (&'a State, &'a Box) { @@ -292,7 +292,7 @@ pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>( /// current model will be made, with a RefMut lock on it. Dioxus will never run your components multithreaded, so you can /// be relatively sure that this won't fail in practice pub fn use_model<'a, T: ToOwned + 'static>( - cx: &impl Scoped<'a>, + cx: impl Scoped<'a>, f: impl FnOnce() -> T, ) -> &'a UseModel { cx.use_hook( @@ -382,7 +382,7 @@ mod tests { } pub fn use_is_initialized

(cx: Context

) -> bool { - let val = use_ref(&cx, || false); + let val = use_ref(cx, || false); match *val.borrow() { true => true, false => { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 15093097d..4faeaafc0 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -571,97 +571,43 @@ impl Scope { Ok(()) } - pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.current_head_node() } - pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.prev_head_node() } - pub fn cur_frame(&self) -> &BumpFrame { + pub(crate) fn cur_frame(&self) -> &BumpFrame { self.frames.cur_frame() } - pub fn root<'a>(&'a self) -> &'a VNode<'a> { + pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> { &self.frames.cur_frame().head_node } -} - -/// Components in Dioxus use the "Context" object to interact with their lifecycle. -/// This lets components schedule updates, integrate hooks, and expose their context via the context api. -/// -/// Properties passed down from the parent component are also directly accessible via the exposed "props" field. -/// -/// ```ignore -/// #[derive(Properties)] -/// struct Props { -/// name: String -/// -/// } -/// -/// fn example(cx: Context) -> VNode { -/// html! { -///

"Hello, {cx.name}"
-/// } -/// } -/// ``` -// todo: force lifetime of source into T as a valid lifetime too -// it's definitely possible, just needs some more messing around - -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> Scoped<'src> for Context<'src, T> { - fn get_scope(&self) -> &'src Scope { - self.scope - } -} - -pub trait Scoped<'src>: Sized { - fn get_scope(&self) -> &'src Scope; /// Access the children elements passed into the component - fn children(&self) -> &'src [VNode<'src>] { + pub fn children(&self) -> &[VNode] { // We're re-casting the nodes back out // They don't really have a static lifetime unsafe { - let scope = self.get_scope(); + let scope = self; let nodes = scope.child_nodes; nodes } } /// Create a subscription that schedules a future render for the reference component - fn schedule_update(&self) -> Rc { - self.get_scope().event_channel.clone() + pub fn schedule_update(&self) -> Rc { + self.event_channel.clone() } - fn schedule_effect(&self) -> Rc { + pub fn schedule_effect(&self) -> Rc { todo!() } - fn schedule_layout_effect(&self) { + pub fn schedule_layout_effect(&self) { todo!() } @@ -680,11 +626,11 @@ pub trait Scoped<'src>: Sized { /// cx.render(lazy_tree) /// } ///``` - fn render) -> VNode<'src>>( - self, + pub fn render<'src, F: FnOnce(NodeFactory<'src>) -> VNode<'src>>( + &'src self, lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { - let scope_ref = self.get_scope(); + let scope_ref = self; let listener_id = &scope_ref.listener_idx; lazy_nodes.into_vnode(NodeFactory { scope_ref, @@ -692,7 +638,6 @@ pub trait Scoped<'src>: Sized { }) } - // impl<'scope> Context<'scope> { /// Store a value between renders /// /// - Initializer: closure used to create the initial hook state @@ -709,8 +654,8 @@ pub trait Scoped<'src>: Sized { /// ) /// } /// ``` - fn use_hook( - &self, + pub fn use_hook<'src, InternalHookState: 'static, Output: 'src>( + &'src self, // The closure that builds the hook state initializer: impl FnOnce() -> InternalHookState, @@ -722,7 +667,7 @@ pub trait Scoped<'src>: Sized { // TODO: add this to the "clean up" group for when the component is dropped _cleanup: impl FnOnce(InternalHookState), ) -> Output { - let scope = self.get_scope(); + let scope = self; let idx = scope.hookidx.get(); @@ -771,7 +716,7 @@ Any function prefixed with "use" should not be called conditionally. /// /// fn use_create_context(&self, init: impl Fn() -> T) { - let scope = self.get_scope(); + let scope = self; let mut cxs = scope.shared_contexts.borrow_mut(); let ty = TypeId::of::(); @@ -800,12 +745,12 @@ Any function prefixed with "use" should not be called conditionally. } /// There are hooks going on here! - fn use_context(&self) -> &'src Rc { + pub fn use_context<'src, T: 'static>(&'src self) -> &'src Rc { self.try_use_context().unwrap() } /// Uses a context, storing the cached value around - fn try_use_context(&self) -> Result<&'src Rc> { + pub fn try_use_context<'src, T: 'static>(&'src self) -> Result<&'src Rc> { struct UseContextHook { par: Option>, we: Option>, @@ -817,8 +762,7 @@ Any function prefixed with "use" should not be called conditionally. we: None as Option>, }, move |hook| { - let scope = self.get_scope(); - let mut scope = Some(scope); + let mut scope = Some(self); if let Some(we) = &hook.we { if let Some(re) = we.upgrade() { @@ -863,8 +807,12 @@ Any function prefixed with "use" should not be called conditionally. ) } - fn suspend VNode<'src> + 'src>( - &self, + pub fn suspend< + 'src, + Output: 'src, + Fut: FnOnce(SuspendedContext, Output) -> VNode<'src> + 'src, + >( + &'src self, fut: &'src mut Pin + 'static>>, callback: Fut, ) -> VNode<'src> { @@ -884,6 +832,120 @@ Any function prefixed with "use" should not be called conditionally. } } +/// Components in Dioxus use the "Context" object to interact with their lifecycle. +/// This lets components schedule updates, integrate hooks, and expose their context via the context api. +/// +/// Properties passed down from the parent component are also directly accessible via the exposed "props" field. +/// +/// ```ignore +/// #[derive(Properties)] +/// struct Props { +/// name: String +/// +/// } +/// +/// fn example(cx: Context) -> VNode { +/// html! { +///
"Hello, {cx.name}"
+/// } +/// } +/// ``` +// todo: force lifetime of source into T as a valid lifetime too +// it's definitely possible, just needs some more messing around +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 + } +} +/// The `Scoped` trait makes it slightly less cumbersome for hooks to use the underlying `Scope` without having to make +/// their hooks generic over the [`Props`] type that [`Context`] is +/// +/// ## Example: +/// +/// ``` +/// fn use_raw(cx: impl Scoped) -> &mut T { +/// +/// } +/// ``` +/// +pub trait Scoped<'src>: Sized + Clone + Copy { + /// + /// + /// + /// + /// + /// + /// + fn get_scope(self) -> &'src Scope; + + #[inline] + fn children(self) -> &'src [VNode<'src>] { + self.get_scope().children() + } + + #[inline] + fn schedule_update(self) -> Rc { + self.get_scope().schedule_update() + } + + #[inline] + fn use_hook( + self, + initializer: impl FnOnce() -> InternalHookState, + runner: impl FnOnce(&'src mut InternalHookState) -> Output, + _cleanup: impl FnOnce(InternalHookState), + ) -> Output { + self.get_scope().use_hook(initializer, runner, _cleanup) + } + + /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. + /// + /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. + /// + /// ## Example + /// + /// ```ignore + /// fn Component(cx: Context<()>) -> VNode { + /// // Lazy assemble the VNode tree + /// let lazy_tree = html! {
"Hello World"
}; + /// + /// // Actually build the tree and allocate it + /// cx.render(lazy_tree) + /// } + ///``` + #[inline] + fn render) -> VNode<'src>>( + self, + lazy_nodes: LazyNodes<'src, F>, + ) -> VNode<'src> { + self.get_scope().render(lazy_nodes) + } +} + +impl<'src, T> Scoped<'src> for Context<'src, T> { + #[inline] + fn get_scope(self) -> &'src Scope { + self.scope + } +} + #[derive(Clone)] pub struct SuspendedContext {}