mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 22:20:19 +00:00
Feat: implememt nodes better
This commit is contained in:
parent
c9d95dd1dc
commit
edbb33b2ee
9 changed files with 317 additions and 387 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,4 +1,5 @@
|
|||
# Project: Live-View 🤲 🍨
|
||||
> Combine the server and client into a single file :)
|
||||
|
||||
|
||||
# Project: Sanitization (TBD)
|
||||
|
@ -8,17 +9,10 @@
|
|||
# Project: Examples
|
||||
> Get *all* the examples
|
||||
- [ ] (Examples) Tide example with templating
|
||||
- [ ] (Examples) Tide example with templating
|
||||
- [ ] (Examples) Tide example with templating
|
||||
- [ ] (Examples) Tide example with templating
|
||||
|
||||
# Project: State management
|
||||
> Get some global state management installed with the hooks API
|
||||
|
||||
# Project: Hooks + Context + Subscriptions (TBD)
|
||||
> Implement a light-weight string renderer with basic caching
|
||||
- [ ] Implement context object
|
||||
|
||||
|
||||
# Project: Concurrency (TBD)
|
||||
> Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
|
||||
|
@ -31,13 +25,26 @@
|
|||
- [ ] (SSR) Implement stateful 3rd party string renderer
|
||||
- [ ] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}" in place of {format!("Text is {}",blah)}
|
||||
|
||||
# Project: Hooks + Context + Subscriptions (TBD)
|
||||
> Implement the foundations for state management
|
||||
- [x] Implement context object
|
||||
- [ ] Implement use_state
|
||||
- [ ] Implement use_ref
|
||||
- [ ] Implement use_reducer
|
||||
- [ ] Implement use_context
|
||||
|
||||
# Project: QOL
|
||||
> Make it easier to write components
|
||||
- [ ] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed)
|
||||
- [ ] (Macro) Tweak component syntax to accept a new custom element
|
||||
- [ ] (Macro) Allow components to specify their props as function args
|
||||
|
||||
# Project: Initial VDOM support (TBD)
|
||||
> Get the initial VDom + Event System + Patching + Diffing + Component framework up and running
|
||||
- [x] (Core) Migrate virtual node into new VNode type
|
||||
- [ ] (Macro) Allow components to specify their props as function args
|
||||
- [ ] (Core) Arena allocate VNodes
|
||||
- [ ] (Core) Allow VNodes to borrow arena contents
|
||||
- [ ] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures)
|
||||
- [ ] (Macro) Tweak component syntax to accept a new custom element
|
||||
- [ ] (Core) Introduce the VDOM and patch API for 3rd party renderers
|
||||
- [x] (Core) Arena allocate VNodes
|
||||
- [x] (Core) Allow VNodes to borrow arena contents
|
||||
- [x] (Core) Introduce the VDOM and patch API for 3rd party renderers
|
||||
- [ ] (Core) Implement lifecycle
|
||||
- [ ] (Core) Implement an event system
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ This is the core crate for the Dioxus Virtual DOM. This README will focus on the
|
|||
Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
|
||||
|
||||
- React: hooks, concurrency, suspense
|
||||
- Dodrio: bump allocation, double buffering
|
||||
- Dodrio: bump allocation, double buffering, and source code for nodes + NodeBuilder
|
||||
- Percy: html! macro architecture, platform-agnostic edits
|
||||
- Yew: passion and inspiration ❤️
|
||||
|
||||
|
@ -17,7 +17,7 @@ We have big goals for Dioxus. The final implementation must:
|
|||
- Be **fast**. Allocators are typically slow in WASM/Rust, so we should have a smart way of allocating.
|
||||
- Be extremely memory efficient. Servers should handle tens of thousands of simultaneous VDoms with no problem.
|
||||
- Be concurrent. Components should be able to pause rendering using a threading mechanism.
|
||||
- Support "broadcasting". Edit lists should be separate from the Renderer implementation.
|
||||
- Be "remote". Edit lists should be separate from the Renderer implementation.
|
||||
- Support SSR. VNodes should render to a string that can be served via a web server.
|
||||
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
|
||||
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
|
||||
|
@ -25,8 +25,8 @@ 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 view fn is ran)
|
||||
- Support advanced diffing strategies
|
||||
- Support lazy DomTrees (ie DomTrees that are not actually created when the html! macro is used)
|
||||
- Support advanced diffing strategies (patience, Myers, etc)
|
||||
|
||||
## Design Quirks
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ fn test_use_state(ctx: Context<()>) -> VNode {
|
|||
// Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
|
||||
// are removed and UB is prevented from /affecting/ the program
|
||||
{
|
||||
VNode::Element(VElement::new("button"))
|
||||
VNode::text("blah")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use crate::scope::Hook;
|
||||
use bumpalo::Bump;
|
||||
use std::{
|
||||
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
|
||||
};
|
||||
|
||||
/// 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(ctx: &Context<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.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 struct Context<'src, T> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
pub idx: AtomicUsize,
|
||||
|
||||
// Borrowed from scope
|
||||
pub(crate) arena: &'src typed_arena::Arena<Hook>,
|
||||
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Access a parent context
|
||||
pub fn parent_context<C>(&self) -> C {
|
||||
todo!("Context API is not ready yet")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context<Props>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div>"Hello World"</div>};
|
||||
///
|
||||
/// // Actually build the tree and allocate it
|
||||
/// ctx.view(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend(
|
||||
&self,
|
||||
fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// use_hook provides a way to store data between renders for functional components.
|
||||
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
|
||||
&'comp 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(&'comp 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 raw_hook = {
|
||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
|
||||
let hook = self.arena.alloc(Hook::new(boxed_state));
|
||||
|
||||
// Push the raw pointer instead of the &mut
|
||||
// A "poor man's OwningRef"
|
||||
hooks.push(hook);
|
||||
}
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
*hooks.get(idx).unwrap()
|
||||
};
|
||||
|
||||
/*
|
||||
** UNSAFETY ALERT **
|
||||
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
||||
|
||||
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
||||
into the arena. During the first call of the function, we need to add the mutable reference given to us by
|
||||
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
||||
when the component itself is deallocated.
|
||||
|
||||
This is okay because:
|
||||
- The lifetime of the component arena is tied to the lifetime of these raw hooks
|
||||
- Usage of the raw hooks is tied behind the Vec refcell
|
||||
- Output is static, meaning it can't take a reference to the data
|
||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||
*/
|
||||
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
|
||||
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
|
||||
|
||||
// todo: set up an updater with the subscription API
|
||||
let updater = ();
|
||||
|
||||
runner(internal_state, updater)
|
||||
}
|
||||
}
|
|
@ -82,8 +82,9 @@ pub mod builder {
|
|||
// types used internally that are important
|
||||
pub(crate) mod inner {
|
||||
pub use crate::component::{Component, Properties};
|
||||
pub use crate::context::Context;
|
||||
use crate::nodes;
|
||||
pub use crate::scope::{Context, Hook, Scope};
|
||||
pub use crate::scope::{Hook, Scope};
|
||||
pub use crate::virtual_dom::VirtualDom;
|
||||
pub use nodes::*;
|
||||
|
||||
|
@ -106,8 +107,8 @@ pub(crate) mod inner {
|
|||
/// Essential when working with the html! macro
|
||||
pub mod prelude {
|
||||
pub use crate::component::{Component, Properties};
|
||||
pub use crate::context::Context;
|
||||
use crate::nodes;
|
||||
pub use crate::scope::Context;
|
||||
pub use crate::virtual_dom::VirtualDom;
|
||||
pub use nodes::*;
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
//! Helpers for building virtual DOM nodes.
|
||||
//! Helpers for building virtual DOM VNodes.
|
||||
|
||||
use crate::{
|
||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
||||
prelude::VElement,
|
||||
};
|
||||
|
||||
use crate::nodes::{Attribute, Listener, NodeKey, VNode};
|
||||
type Node<'g> = VNode<'g>;
|
||||
use bumpalo::Bump;
|
||||
|
||||
/// A virtual DOM element builder.
|
||||
|
@ -14,7 +17,7 @@ pub struct ElementBuilder<'a, Listeners, Attributes, Children>
|
|||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[Node<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
bump: &'a Bump,
|
||||
key: NodeKey,
|
||||
|
@ -30,7 +33,7 @@ impl<'a>
|
|||
'a,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, Node<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
>
|
||||
{
|
||||
/// Create a new `ElementBuilder` for an element with the given tag name.
|
||||
|
@ -77,7 +80,7 @@ impl<'a, Listeners, Attributes, Children> ElementBuilder<'a, Listeners, Attribut
|
|||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[Node<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Set the listeners for this element.
|
||||
///
|
||||
|
@ -187,7 +190,7 @@ where
|
|||
#[inline]
|
||||
pub fn children<C>(self, children: C) -> ElementBuilder<'a, Listeners, Attributes, C>
|
||||
where
|
||||
C: 'a + AsRef<[Node<'a>]>,
|
||||
C: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
bump: self.bump,
|
||||
|
@ -230,7 +233,7 @@ where
|
|||
/// Set this element's key.
|
||||
///
|
||||
/// When diffing sets of siblings, if an old sibling and new sibling share a
|
||||
/// key, then they will always reuse the same physical DOM node. This is
|
||||
/// key, then they will always reuse the same physical DOM VNode. This is
|
||||
/// important when using CSS animations, web components, third party JS, or
|
||||
/// anything else that makes the diffing implementation observable.
|
||||
///
|
||||
|
@ -244,7 +247,7 @@ where
|
|||
///
|
||||
/// Keys must be unique among siblings.
|
||||
///
|
||||
/// All sibling nodes must be keyed, or they must all not be keyed. You may
|
||||
/// All sibling VNodes must be keyed, or they must all not be keyed. You may
|
||||
/// not mix keyed and unkeyed siblings.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -266,25 +269,25 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Create the virtual DOM node described by this builder.
|
||||
/// Create the virtual DOM VNode described by this builder.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dodrio::{builder::*, bumpalo::Bump, Node};
|
||||
/// use dodrio::{builder::*, bumpalo::Bump, VNode};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Start with a builder...
|
||||
/// let builder: ElementBuilder<_, _, _> = div(&b);
|
||||
///
|
||||
/// // ...and finish it to create a virtual DOM node!
|
||||
/// let my_div: Node = builder.finish();
|
||||
/// // ...and finish it to create a virtual DOM VNode!
|
||||
/// let my_div: VNode = builder.finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn finish(self) -> Node<'a> {
|
||||
pub fn finish(self) -> VNode<'a> {
|
||||
let children: &'a Children = self.bump.alloc(self.children);
|
||||
let children: &'a [Node<'a>] = children.as_ref();
|
||||
let children: &'a [VNode<'a>] = children.as_ref();
|
||||
|
||||
let listeners: &'a Listeners = self.bump.alloc(self.listeners);
|
||||
let listeners: &'a [Listener<'a>] = listeners.as_ref();
|
||||
|
@ -292,63 +295,62 @@ where
|
|||
let attributes: &'a Attributes = self.bump.alloc(self.attributes);
|
||||
let attributes: &'a [Attribute<'a>] = attributes.as_ref();
|
||||
|
||||
todo!()
|
||||
// Node::element(
|
||||
// self.bump,
|
||||
// self.key,
|
||||
// self.tag_name,
|
||||
// listeners,
|
||||
// attributes,
|
||||
// children,
|
||||
// self.namespace,
|
||||
// )
|
||||
VNode::element(
|
||||
self.bump,
|
||||
self.key,
|
||||
self.tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
self.namespace,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a, Attributes, Children>
|
||||
// ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
|
||||
// where
|
||||
// Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
// Children: 'a + AsRef<[Node<'a>]>,
|
||||
// {
|
||||
// /// Add a new event listener to this element.
|
||||
// ///
|
||||
// /// The `event` string specifies which event will be listened for. The
|
||||
// /// `callback` function is the function that will be invoked if the
|
||||
// /// specified event occurs.
|
||||
// ///
|
||||
// /// # Example
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use dodrio::{builder::*, bumpalo::Bump};
|
||||
// ///
|
||||
// /// let b = Bump::new();
|
||||
// ///
|
||||
// /// // A button that does something when clicked!
|
||||
// /// let my_button = button(&b)
|
||||
// /// .on("click", |root, vdom, event| {
|
||||
// /// // ...
|
||||
// /// })
|
||||
// /// .finish();
|
||||
// /// ```
|
||||
// #[inline]
|
||||
// pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
|
||||
// where
|
||||
// F: 'static + Fn(&mut dyn RootRender, VdomWeak, web_sys::Event),
|
||||
// {
|
||||
// self.listeners.push(Listener {
|
||||
// event,
|
||||
// callback: self.bump.alloc(callback),
|
||||
// });
|
||||
// self
|
||||
// }
|
||||
// }
|
||||
impl<'a, Attributes, Children>
|
||||
ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
|
||||
where
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Add a new event listener to this element.
|
||||
///
|
||||
/// The `event` string specifies which event will be listened for. The
|
||||
/// `callback` function is the function that will be invoked if the
|
||||
/// specified event occurs.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dodrio::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // A button that does something when clicked!
|
||||
/// let my_button = button(&b)
|
||||
/// .on("click", |root, vdom, event| {
|
||||
/// // ...
|
||||
/// })
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(),
|
||||
{
|
||||
self.listeners.push(Listener {
|
||||
event,
|
||||
callback: self.bump.alloc(callback),
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Listeners, Children>
|
||||
ElementBuilder<'a, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Children: 'a + AsRef<[Node<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Add a new attribute to this element.
|
||||
///
|
||||
|
@ -404,7 +406,7 @@ where
|
|||
}
|
||||
|
||||
impl<'a, Listeners, Attributes>
|
||||
ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, Node<'a>>>
|
||||
ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
|
@ -425,7 +427,7 @@ where
|
|||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn child(mut self, child: Node<'a>) -> Self {
|
||||
pub fn child(mut self, child: VNode<'a>) -> Self {
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
@ -445,7 +447,7 @@ macro_rules! builder_constructors {
|
|||
'a,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, Node<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
>
|
||||
where
|
||||
B: Into<&'a Bump>
|
||||
|
@ -467,7 +469,7 @@ macro_rules! builder_constructors {
|
|||
'a,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, Node<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
> {
|
||||
let builder = ElementBuilder::new(bump, stringify!($name));
|
||||
builder.namespace(Some($namespace))
|
||||
|
@ -1018,9 +1020,9 @@ builder_constructors! {
|
|||
image <> "http://www.w3.org/2000/svg";
|
||||
}
|
||||
|
||||
/// Construct a text node.
|
||||
/// Construct a text VNode.
|
||||
///
|
||||
/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
|
||||
/// This is `dodrio`'s virtual DOM equivalent of `document.createTextVNode`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -1030,8 +1032,8 @@ builder_constructors! {
|
|||
/// let my_text = text("hello, dodrio!");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn text<'a>(contents: &'a str) -> Node<'a> {
|
||||
Node::text(contents)
|
||||
pub fn text<'a>(contents: &'a str) -> VNode<'a> {
|
||||
VNode::text(contents)
|
||||
}
|
||||
|
||||
/// Construct an attribute for an element.
|
||||
|
|
|
@ -22,7 +22,7 @@ mod vnode {
|
|||
|
||||
pub enum VNode<'src> {
|
||||
/// An element node (node type `ELEMENT_NODE`).
|
||||
Element(VElement<'src>),
|
||||
Element(&'src VElement<'src>),
|
||||
|
||||
/// A text node (node type `TEXT_NODE`).
|
||||
///
|
||||
|
@ -40,147 +40,54 @@ mod vnode {
|
|||
Component(VComponent),
|
||||
}
|
||||
|
||||
impl<'src> VNode<'src> {
|
||||
/// Create a new virtual element node with a given tag.
|
||||
impl<'a> VNode<'a> {
|
||||
/// Low-level constructor for making a new `Node` of type element with given
|
||||
/// parts.
|
||||
///
|
||||
/// These get patched into the DOM using `document.createElement`
|
||||
///
|
||||
/// ```ignore
|
||||
/// let div = VNode::element("div");
|
||||
/// ```
|
||||
pub fn element(tag: &'static str) -> Self {
|
||||
VNode::Element(VElement::new(tag))
|
||||
/// This is primarily intended for JSX and templating proc-macros to compile
|
||||
/// down into. If you are building nodes by-hand, prefer using the
|
||||
/// `dodrio::builder::*` APIs.
|
||||
#[inline]
|
||||
pub fn element(
|
||||
bump: &'a Bump,
|
||||
key: NodeKey,
|
||||
tag_name: &'a str,
|
||||
listeners: &'a [Listener<'a>],
|
||||
attributes: &'a [Attribute<'a>],
|
||||
children: &'a [VNode<'a>],
|
||||
namespace: Option<&'a str>,
|
||||
) -> VNode<'a> {
|
||||
let element = bump.alloc_with(|| VElement {
|
||||
key,
|
||||
tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
namespace,
|
||||
});
|
||||
VNode::Element(element)
|
||||
}
|
||||
|
||||
/// Construct a new text node with the given text.
|
||||
#[inline]
|
||||
pub(crate) fn text(text: &'src str) -> VNode<'src> {
|
||||
pub fn text(text: &'a str) -> VNode<'a> {
|
||||
VNode::Text(VText { text })
|
||||
}
|
||||
// /// Create a new virtual text node with the given text.
|
||||
// ///
|
||||
// /// These get patched into the DOM using `document.createTextNode`
|
||||
// ///
|
||||
// /// ```ignore
|
||||
// /// let div = VNode::text("div");
|
||||
// /// ```
|
||||
// pub fn text<S>(text: S) -> Self
|
||||
// where
|
||||
// S: Into<String>,
|
||||
// {
|
||||
// /*
|
||||
// TODO
|
||||
|
||||
// This is an opportunity to be extremely efficient when allocating/creating strings
|
||||
// To assemble a formatted string, we can, using the macro, borrow all the contents without allocating.
|
||||
|
||||
// String contents are therefore bump allocated automatically
|
||||
|
||||
// html!{
|
||||
// <>"Hello {world}"</>
|
||||
// }
|
||||
|
||||
// Should be
|
||||
|
||||
// ```
|
||||
// let mut root = VNode::text(["Hello", world]);
|
||||
|
||||
// ```
|
||||
|
||||
// */
|
||||
// VNode::Text(VText::new(text.into()))
|
||||
// }
|
||||
|
||||
// /// Return a [`VElement`] reference, if this is an [`Element`] variant.
|
||||
// ///
|
||||
// /// [`VElement`]: struct.VElement.html
|
||||
// /// [`Element`]: enum.VNode.html#variant.Element
|
||||
// pub fn as_velement_ref(&self) -> Option<&VElement> {
|
||||
// match self {
|
||||
// VNode::Element(ref element_node) => Some(element_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
|
||||
// ///
|
||||
// /// [`VElement`]: struct.VElement.html
|
||||
// /// [`Element`]: enum.VNode.html#variant.Element
|
||||
// pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
|
||||
// match self {
|
||||
// VNode::Element(ref mut element_node) => Some(element_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a [`VText`] reference, if this is an [`Text`] variant.
|
||||
// ///
|
||||
// /// [`VText`]: struct.VText.html
|
||||
// /// [`Text`]: enum.VNode.html#variant.Text
|
||||
// pub fn as_vtext_ref(&self) -> Option<&VText> {
|
||||
// match self {
|
||||
// VNode::Text(ref text_node) => Some(text_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
|
||||
// ///
|
||||
// /// [`VText`]: struct.VText.html
|
||||
// /// [`Text`]: enum.VNode.html#variant.Text
|
||||
// pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
|
||||
// match self {
|
||||
// VNode::Text(ref mut text_node) => Some(text_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Used by html-macro to insert space before text that is inside of a block that came after
|
||||
// /// an open tag.
|
||||
// ///
|
||||
// /// html! { <div> {world}</div> }
|
||||
// ///
|
||||
// /// So that we end up with <div> world</div> when we're finished parsing.
|
||||
// pub fn insert_space_before_text(&mut self) {
|
||||
// match self {
|
||||
// VNode::Text(text_node) => {
|
||||
// text_node.text = " ".to_string() + &text_node.text;
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Used by html-macro to insert space after braced text if we know that the next block is
|
||||
// /// another block or a closing tag.
|
||||
// ///
|
||||
// /// html! { <div>{Hello} {world}</div> } -> <div>Hello world</div>
|
||||
// /// html! { <div>{Hello} </div> } -> <div>Hello </div>
|
||||
// ///
|
||||
// /// So that we end up with <div>Hello world</div> when we're finished parsing.
|
||||
// pub fn insert_space_after_text(&mut self) {
|
||||
// match self {
|
||||
// VNode::Text(text_node) => {
|
||||
// text_node.text += " ";
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
#[inline]
|
||||
pub(crate) fn key(&self) -> NodeKey {
|
||||
match &self {
|
||||
VNode::Text(_) => NodeKey::NONE,
|
||||
VNode::Element(e) => e.key,
|
||||
VNode::Suspended => {
|
||||
todo!()
|
||||
}
|
||||
VNode::Component(_) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// Convert from DOM elements to the primary enum
|
||||
// -----------------------------------------------
|
||||
// impl From<VText> for VNode {
|
||||
// fn from(other: VText) -> Self {
|
||||
// VNode::Text(other)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<VElement> for VNode {
|
||||
// fn from(other: VElement) -> Self {
|
||||
// VNode::Element(other)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
mod velement {
|
||||
|
@ -188,11 +95,18 @@ mod velement {
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub struct VElement<'a> {
|
||||
/// The HTML tag, such as "div"
|
||||
pub tag: &'a str,
|
||||
|
||||
/// Elements have a tag name, zero or more attributes, and zero or more
|
||||
pub key: NodeKey,
|
||||
pub tag_name: &'a str,
|
||||
pub listeners: &'a [Listener<'a>],
|
||||
pub attributes: &'a [Attribute<'a>],
|
||||
pub children: &'a [VNode<'a>],
|
||||
pub namespace: Option<&'a str>,
|
||||
// The HTML tag, such as "div"
|
||||
// pub tag: &'a str,
|
||||
|
||||
// pub tag_name: &'a str,
|
||||
// pub attributes: &'a [Attribute<'a>],
|
||||
// todo: hook up listeners
|
||||
// pub listeners: &'a [Listener<'a>],
|
||||
// / HTML attributes such as id, class, style, etc
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use any::Any;
|
||||
use bumpalo::Bump;
|
||||
use generational_arena::{Arena, Index};
|
||||
use std::{
|
||||
any::{self, TypeId},
|
||||
cell::{RefCell, UnsafeCell},
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
sync::atomic::AtomicUsize,
|
||||
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
|
||||
};
|
||||
|
||||
/// The Scope that wraps a functional component
|
||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
|
||||
/// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
///
|
||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||
///
|
||||
/// 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 {
|
||||
arena: typed_arena::Arena<Hook>,
|
||||
// These hooks are actually references into the hook arena
|
||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||
// TODO @Jon
|
||||
hooks: RefCell<Vec<*mut Hook>>,
|
||||
hook_arena: typed_arena::Arena<Hook>,
|
||||
|
||||
props_type: TypeId,
|
||||
caller: *const i32,
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ impl Scope {
|
|||
let caller = f as *const i32;
|
||||
|
||||
Self {
|
||||
arena,
|
||||
hook_arena: arena,
|
||||
hooks,
|
||||
props_type,
|
||||
caller,
|
||||
|
@ -42,7 +43,7 @@ impl Scope {
|
|||
pub fn create_context<T: Properties>(&mut self) -> Context<T> {
|
||||
Context {
|
||||
_p: PhantomData {},
|
||||
arena: &self.arena,
|
||||
arena: &self.hook_arena,
|
||||
hooks: &self.hooks,
|
||||
idx: 0.into(),
|
||||
props: T::new(),
|
||||
|
@ -74,152 +75,10 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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(ctx: &Context<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.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 struct Context<'src, T> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
pub idx: AtomicUsize,
|
||||
|
||||
// Borrowed from scope
|
||||
arena: &'src typed_arena::Arena<Hook>,
|
||||
hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Access a parent context
|
||||
pub fn parent_context<C>(&self) -> C {
|
||||
todo!("Context API is not ready yet")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context<Props>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div>"Hello World"</div>};
|
||||
///
|
||||
/// // Actually build the tree and allocate it
|
||||
/// ctx.view(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend(
|
||||
&self,
|
||||
fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// use_hook provides a way to store data between renders for functional components.
|
||||
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
|
||||
&'comp 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(&'comp 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 raw_hook = {
|
||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
|
||||
let hook = self.arena.alloc(Hook::new(boxed_state));
|
||||
|
||||
// Push the raw pointer instead of the &mut
|
||||
// A "poor man's OwningRef"
|
||||
hooks.push(hook);
|
||||
}
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
*hooks.get(idx).unwrap()
|
||||
};
|
||||
|
||||
/*
|
||||
** UNSAFETY ALERT **
|
||||
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
||||
|
||||
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
||||
into the arena. During the first call of the function, we need to add the mutable reference given to us by
|
||||
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
||||
when the component itself is deallocated.
|
||||
|
||||
This is okay because:
|
||||
- The lifetime of the component arena is tied to the lifetime of these raw hooks
|
||||
- Usage of the raw hooks is tied behind the Vec refcell
|
||||
- Output is static, meaning it can't take a reference to the data
|
||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||
*/
|
||||
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
|
||||
let internal_state = borrowed_hook
|
||||
.state
|
||||
.downcast_mut::<InternalHookState>()
|
||||
.unwrap();
|
||||
|
||||
// todo: set up an updater with the subscription API
|
||||
let updater = ();
|
||||
|
||||
runner(internal_state, updater)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hook {
|
||||
state: Box<dyn std::any::Any>,
|
||||
}
|
||||
pub struct Hook(pub Box<dyn std::any::Any>);
|
||||
|
||||
impl Hook {
|
||||
fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||
Self { state }
|
||||
pub fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||
Self(state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// mod hooks;
|
||||
// pub use hooks::use_context;
|
||||
|
||||
pub mod prelude {
|
||||
use dioxus_core::prelude::Context;
|
||||
pub fn use_state<T, G>(ctx: &mut Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
|
||||
pub fn use_state<T, G>(ctx: &Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
|
||||
let g = init();
|
||||
(g, |_| {})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue