This commit is contained in:
Jonathan Kelley 2021-07-01 14:14:59 -04:00
parent c5f9cce63a
commit 952a91d540
18 changed files with 154 additions and 48 deletions

0
.nojekyll Normal file
View File

View File

@ -9,7 +9,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
```rust
fn Example(cx: Context<()>) -> VNode {
let mut name = use_signal(cx, || "..?");
let name = use_state(cx, || "..?");
cx.render(rsx! {
h1 { "Hello, {name}" }
@ -81,8 +81,6 @@ If you know React, then you already know Dioxus.
## Why?
---
TypeScript is a great addition to JavaScript, but comes with a lot of tweaking flags, a slight performance hit, and an uneven ecosystem where some of the most important packages are not properly typed. TypeScript provides a lot of great benefits to JS projects, but comes with its own "tax" that can slow down dev teams. Rust can be seen as a step up from TypeScript, supporting:
- static types for _all_ libraries
@ -93,7 +91,7 @@ TypeScript is a great addition to JavaScript, but comes with a lot of tweaking f
- integrated documentation
- inline built-in unit/integration testing
- best-in-class error handling
- simple and fast build system
- simple and fast build system (compared to webpack!)
- powerful standard library (no need for lodash or underscore)
- include_str! for integrating html/css/svg templates directly
- various macros (`html!`, `rsx!`) for fast template iteration

View File

@ -45,7 +45,7 @@ fn Comp(cx: Context<()>) -> VNode {
Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates.
We can use signals generated a two-way binding between data and the input box. Our text input is now just a two-line component!
We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component!
```rust
fn Comp(cx: Context<()>) -> VNode {

View File

@ -16,7 +16,7 @@ static Example: FC<()> = |cx| {
))
};
pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a, P>(
pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a, P>(
cx: &'a Context<'src, P>,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {

View File

@ -0,0 +1,47 @@
# Signals
Signals provide a way of forcing updates directly through Dioxus without having to go through the diffing phase.
When diffing is too slow for your use-case, signals can be faster. Signals run at a higher priority than regular diffing, acting as a hint to Dioxus that a signal update needs to take precedence over a subtree update. This can be useful in real-time systems where getting data from a websocket to the screen ASAP is extremely important.
- High
- Medium
- Low
## Signals:
Producer -> Receiver
- The Dioxus VirtualDOM provides built-in receivers for signals.
- Elements themselves act as receivers.
- Any use of a signal schedules the current element and its children for updates.
- Attributes are valid receivers
- Text nodes are valid receivers
- Receivers may not be passed into child components (must be de-referenced)
- When receivers are derefed in a component's properties, the props will be updated in place and the component will re-render with the new value.
```rust
let sig = use_signal(|| 0);
// any updates to the signal will cause the child to re-render completely
Comp {
prop: *sig
}
```
Using 3 separate signals
```rust
let width = use_signal(|| 0);
cx.request_next_frame(move |frame| async {
sig1 += 1;
frame.again();
})
div {
h2 { "{sig1}" }
h3 { "{sig2}" }
h4 { "{sig3}" }
}
```

View File

@ -1,4 +1,4 @@
- [] Move the builder API onto NodeCtx
- [] Move the builder API onto NodeFactory
- [] Transition away from names and towards compile-time safe tags
- [] Fix diffing of fragments
- [] Properly integrate memoization to prevent safety issues with children

View File

@ -21,7 +21,14 @@ pub fn html(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.
/// ```
/// rsx! {
/// div {
/// class: "some special class"
/// h1 { "Children too" }
/// }
/// }
/// ```
#[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream {
match syn::parse::<rsx::RsxRender>(s) {

View File

@ -105,7 +105,7 @@ impl ToTokens for RsxRender {
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
dioxus::prelude::LazyNodes::new(move |__cx|{
dioxus::prelude::LazyNodes::new(move |__cx: &NodeFactory|{
let bump = &__cx.bump();
#inner
})

View File

@ -51,7 +51,7 @@ impl DebugRenderer {
// Does not handle children or lifecycles and will always fail the test if they show up in the rhs
pub fn compare<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
Ok(())
}
@ -60,7 +60,7 @@ impl DebugRenderer {
// Ignores listeners and children components
pub fn compare_full<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
Ok(())
}
@ -71,7 +71,7 @@ impl DebugRenderer {
pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
Ok(())
}
@ -86,7 +86,7 @@ impl DebugVNodeSource {
}
fn render_nodes(&self) -> VNode {
// let cx = NodeCtx
// let cx = NodeFactory
todo!()
}
}

View File

@ -67,7 +67,7 @@ pub mod on {
use crate::{
builder::ElementBuilder,
builder::NodeCtx,
builder::NodeFactory,
innerlude::{Attribute, Listener, RealDomNode, VNode},
};
use std::cell::Cell;
@ -79,7 +79,7 @@ pub mod on {
$(
$(
pub fn $name<'a>(
c: &'_ NodeCtx<'a>,
c: &'_ NodeFactory<'a>,
callback: impl Fn(Rc<dyn $eventdata>) + 'a,
) -> Listener<'a> {
let bump = &c.bump();

View File

@ -20,6 +20,7 @@ pub mod events; // Manages the synthetic event API
pub mod hooks; // Built-in hooks
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
pub mod nodes; // Logic for the VNodes
pub mod signals;
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
pub mod builder {
@ -57,7 +58,7 @@ pub mod prelude {
pub use crate::nodebuilder::LazyNodes;
pub use crate::nodebuilder::ChildrenList;
pub use crate::nodebuilder::NodeCtx;
pub use crate::nodebuilder::NodeFactory;
// 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;

View File

@ -29,7 +29,7 @@ where
Attributes: 'a + AsRef<[Attribute<'a>]>,
Children: 'a + AsRef<[VNode<'a>]>,
{
cx: &'b NodeCtx<'a>,
cx: &'b NodeFactory<'a>,
key: NodeKey<'a>,
tag_name: &'static str,
// tag_name: &'a str,
@ -71,7 +71,7 @@ impl<'a, 'b>
/// let my_element_builder = ElementBuilder::new(&b, tag_name);
/// # fn flip_coin() -> bool { true }
/// ```
pub fn new(cx: &'b NodeCtx<'a>, tag_name: &'static str) -> Self {
pub fn new(cx: &'b NodeFactory<'a>, tag_name: &'static str) -> Self {
let bump = cx.bump();
ElementBuilder {
cx,
@ -532,20 +532,20 @@ impl<'a> IntoIterator for VNode<'a> {
}
}
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> {
self
}
}
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> {
// cloning is cheap since vnodes are just references into bump arenas
self.clone()
}
}
pub trait IntoVNode<'a> {
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a>;
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a>;
}
pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
@ -555,7 +555,7 @@ where
}
impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a
{
}
@ -564,7 +564,7 @@ impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
// 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,
G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
inner: G,
_p: std::marker::PhantomData<&'a ()>,
@ -572,7 +572,7 @@ where
impl<'a, G> LazyNodes<'a, G>
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
pub fn new(f: G) -> Self {
Self {
@ -589,9 +589,9 @@ where
// rsx! { {nodes } }
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> {
(self.inner)(cx)
}
}
@ -599,7 +599,7 @@ where
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
impl<'a, G> IntoIterator for LazyNodes<'a, G>
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a,
{
type Item = Self;
type IntoIter = std::iter::Once<Self::Item>;
@ -609,7 +609,7 @@ where
}
impl<'a> IntoVNode<'a> for () {
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> {
todo!();
VNode::Suspended {
real: Cell::new(RealDomNode::empty()),
@ -618,7 +618,7 @@ impl<'a> IntoVNode<'a> for () {
}
impl<'a> IntoVNode<'a> for Option<()> {
fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> {
fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> {
todo!();
VNode::Suspended {
real: Cell::new(RealDomNode::empty()),
@ -684,7 +684,7 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
}
pub fn virtual_child<'a, T: Properties + 'a>(
cx: &NodeCtx<'a>,
cx: &NodeFactory<'a>,
f: FC<T>,
props: T,
key: Option<&'a str>, // key: NodeKey<'a>,
@ -702,7 +702,7 @@ pub fn virtual_child<'a, T: Properties + 'a>(
}
pub fn vfragment<'a>(
cx: &NodeCtx<'a>,
cx: &NodeFactory<'a>,
key: Option<&'a str>, // key: NodeKey<'a>,
children: &'a [VNode<'a>],
) -> VNode<'a> {
@ -710,12 +710,12 @@ pub fn vfragment<'a>(
}
pub struct ChildrenList<'a, 'b> {
cx: &'b NodeCtx<'a>,
cx: &'b NodeFactory<'a>,
children: bumpalo::collections::Vec<'a, VNode<'a>>,
}
impl<'a, 'b> ChildrenList<'a, 'b> {
pub fn new(cx: &'b NodeCtx<'a>) -> Self {
pub fn new(cx: &'b NodeFactory<'a>) -> Self {
Self {
cx,
children: bumpalo::collections::Vec::new_in(cx.bump()),
@ -735,16 +735,16 @@ impl<'a, 'b> ChildrenList<'a, 'b> {
}
}
// NodeCtx is used to build VNodes in the component's memory space.
// NodeFactory is used to build VNodes in the component's memory space.
// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeCtx<'a> {
pub struct NodeFactory<'a> {
pub scope_ref: &'a Scope,
pub listener_id: Cell<usize>,
// pub listener_id: RefCell<usize>,
}
impl<'a> NodeCtx<'a> {
impl<'a> NodeFactory<'a> {
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
&self.scope_ref.cur_frame().bump
@ -771,7 +771,7 @@ impl<'a> NodeCtx<'a> {
}
use std::fmt::Debug;
impl Debug for NodeCtx<'_> {
impl Debug for NodeFactory<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}

View File

@ -7,7 +7,7 @@ use crate::{
arena::ScopeArena,
events::VirtualEvent,
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
nodebuilder::{text3, NodeCtx},
nodebuilder::{text3, NodeFactory},
virtual_dom::RealDomNode,
};
use bumpalo::Bump;
@ -336,7 +336,7 @@ impl<'a> VComponent<'a> {
///
/// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
pub fn new<P: Properties + 'a>(
cx: &NodeCtx<'a>,
cx: &NodeFactory<'a>,
component: FC<P>,
props: P,
key: Option<&'a str>,

View File

@ -0,0 +1 @@
//! Support for jumping out of the diff engine

View File

@ -42,7 +42,6 @@ impl<'a> RealDom<'a> for DebugDom {
realnode: RealDomNode,
) {
}
fn remove_event_listener(&mut self, event: &str) {}
fn set_text(&mut self, text: &str) {}

View File

@ -13,7 +13,7 @@
//! - The [`Scope`] object for mangning component lifecycle
//! - The [`ActiveFrame`] object for managing the Scope`s microheap
//! - The [`Context`] object for exposing VirtualDOM API to components
//! - The [`NodeCtx`] object for lazyily exposing the `Context` API to the nodebuilder API
//! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API
//! - The [`Hook`] object for exposing state management in components.
//!
//! This module includes just the barebones for a complete VirtualDOM API.
@ -677,11 +677,11 @@ pub trait Scoped<'src>: Sized {
/// cx.render(lazy_tree)
/// }
///```
fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
fn render<'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {
lazy_nodes.into_vnode(&NodeCtx {
lazy_nodes.into_vnode(&NodeFactory {
scope_ref: self.get_scope(),
listener_id: 0.into(),
})
@ -883,7 +883,7 @@ Any function prefixed with "use" should not be called conditionally.
pub struct SuspendedContext {}
impl SuspendedContext {
pub fn render<'a, 'src, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
pub fn render<'a, 'src, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {

View File

@ -8,11 +8,11 @@
//!
//!
struct NodeCtx {}
struct NodeFactory {}
struct div<'a>(&NodeCtx);
struct div<'a>(&NodeFactory);
impl<'a> div<'a> {
fn new(cx: &NodeCtx) -> Self {
fn new(cx: &NodeFactory) -> Self {
div(cx)
}
}
@ -21,7 +21,7 @@ fn main() {}
fn factory(
// this is your mom
cx: &NodeCtx,
cx: &NodeFactory,
) {
div::new(cx);
rsx! {

View File

@ -0,0 +1,53 @@
fn main() {
// render the
let transition = move |cx, (width, height)| {};
cx.render(rsx! {
div {
Transition {
start: (0, 5),
stop: (10, 10),
render: transition
}
Transition {
start: (0, 5),
stop: (10, 10),
render: move |cx, (width, height)| {
//
cx.render(rsx!{
div {
style {
width: width,
width: height
}
}
})
}
}
}
})
}
// use signals to directly update values outside of the diffing phase
fn signal_based(cx: ()) {
const InitPos: (i32, i32) = (0, 0);
const EndPos: (i32, i32) = (100, 200);
let spring = use_spring(cx, move |spring| spring.from(InitPos).to(EndPos));
cx.render(rsx! {
div {
style {
width: spring.0,
width: spring.1
}
button { "Reset"
onclick: move |_| spring.set(InitPos)
}
button { "Animate"
onclick: move |_| spring.set(EndPos)
}
}
})
}