Feat: some docs

This commit is contained in:
Jonathan Kelley 2021-06-02 11:07:30 -04:00
parent 508c560320
commit 1919f88f03
10 changed files with 179 additions and 182 deletions

View file

@ -43,3 +43,5 @@ virtualdom
namespaced
namespacing
impl
destructured
linting

View file

@ -10,7 +10,7 @@ struct MyProps {
fn Example(ctx: Context<MyProps>) -> VNode {
ctx.render(html! {
<div> "Hello {ctx.props().name}!" </div>
<div> "Hello {ctx.name}!" </div>
})
}
```

View file

@ -35,7 +35,7 @@ struct ExampleProps {
fn Example(ctx: &mut Context<ExampleProps>) -> VNode {
let ExampleProps {
name, pending, count
} = ctx.props();
} = ctx.props;
rsx! {
<div>

View file

@ -4,10 +4,12 @@ Dioxus comes preloaded with two macros for creating VNodes.
## html! macro
The html! macro supports the html standard. This macro will happily accept a copy-paste from something like tailwind builder. Writing this one by hand is a bit tedious and doesn't come with much help from Rust IDE tools.
The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools.
There is also limited support for dynamic handlers, but it will function similarly to JSX.
You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX.
```rust
#[fc]
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
@ -23,9 +25,9 @@ fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
## rsx! macro
The rsx! macro is a VNode builder macro designed especially for Rust. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting.
The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting.
The Dioxus VSCode extension provides a function to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand.
The Dioxus VSCode extension will eventually provide a macro to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand.
It's also a bit easier on the eyes 🙂.
@ -51,30 +53,55 @@ Each element takes a comma-separated list of expressions to build the node. Roug
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be IntoCtx<Vnode> to work properly
Lists must include commas, much like how struct definitions work.
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena.
```rust
static Example: FC<()> = |ctx| {
let text = "example";
ctx.render(rsx!{
div {
h1 { "Example" },
// fstring interpolation
"{text}"
p {
// Props
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
class: "big small wide short",
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
// bool-based classnames
classes: [("big", true), ("small", false)]
// Bool-based props
// *must* be in the tuple form, cannot enter as a variable
tag: ("type", false)
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with props
h2 { "whatsup", class: "abc-123" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ 0..3.map(|i| rsx!{ h1 {"{:i}"} }) },
@ -82,7 +109,34 @@ static Example: FC<()> = |ctx| {
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Any expression that is Into<VNode>
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => rsx!(in ctx, h1 { "big" })
2 => rsx!(in ctx, h2 { "medium" })
_ => rsx!(in ctx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })}
// Bool options
{(rsx!{ h1 {"Conditional Rendering"}, true)}
// Child nodes
// Returns &[VNode]
{ctx.children()}
// Duplicating nodes
// Clones the nodes by reference, so they are literally identical
{{
let node = rsx!(in ctx, h1{ "TopNode" });
(0..10).map(|_| node.clone())
}}
// Any expression that is `IntoVNode`
{expr}
}
}

View file

@ -0,0 +1,56 @@
# A Simple Component
```rust
#[derive(PartialEq, Properties)]
struct Props { name: &'static str }
static HelloMessage: FC<Props> = |ctx| {
ctx.render(rsx!{
div { "Hello {ctx.props.name}" }
})
}
```
# Any syntax you like
Choose from a close-to-html syntax or the standard rsx! syntax
```rust
static HelloMessage: FC<()> = |ctx| {
ctx.render(html!{
<div> Hello World! </div>
})
}
```
# A Stateful Component
Store state with hooks!
```rust
enum LightState {
Green
Yellow,
Red,
}
static HelloMessage: FC<()> = |ctx| {
let (color, set_color) = use_state(ctx, || LightState::Green);
let title = match color {
Green => "Green means go",
Yellow => "Yellow means slow down",
Red => "Red means stop",
};
ctx.render(rsx!{
h1 {"{title}"}
button { "tick"
onclick: move |_| set_color(match color {
Green => Yellow,
Yellow => Red,
Red => Green,
})
}
})
}
```

View file

@ -22,6 +22,7 @@ static Example: FC<()> = |ctx| {
//
rsx! { in ctx,
div {
h1 {"these are children nodes: "}
{nodes}
}
}

View file

@ -14,7 +14,11 @@ static Header: FC<()> = |ctx| {
ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| {
builder::ElementBuilder::new(nodectx, "div")
.child(VNode::Component(VComponent::new(Bottom, (), None)))
.child(VNode::Component(nodectx.bump().alloc(VComponent::new(
Bottom,
(),
None,
))))
.finish()
}))
};
@ -36,128 +40,3 @@ fn Top(ctx: Context<()>) -> VNode {
</div>
})
}
struct Callback<T>(Box<T>);
// impl<O, T: Fn() -> O> From<T> for Callback<T> {
// fn from(_: T) -> Self {
// todo!()
// }
// }
impl<O, A> From<&dyn Fn(A) -> O> for Callback<&dyn Fn(A) -> O> {
fn from(_: &dyn Fn(A) -> O) -> Self {
todo!()
}
}
impl<O, A, B> From<&dyn Fn(A, B) -> O> for Callback<&dyn Fn(A, B) -> O> {
fn from(_: &dyn Fn(A, B) -> O) -> Self {
todo!()
}
}
// compile time reordering of arguments
// Allows for transparently calling
#[derive(Default)]
pub struct Args<A, B, C> {
pub a: CuOpt<A>,
pub b: CuOpt<B>,
pub c: CuOpt<C>,
}
pub enum CuOpt<T> {
Some(T),
None,
}
impl<T> Default for CuOpt<T> {
fn default() -> Self {
CuOpt::None
}
}
impl<T> CuOpt<T> {
fn unwrap(self) -> T {
match self {
CuOpt::Some(t) => t,
CuOpt::None => panic!(""),
}
}
}
trait IsMemo {
fn memo(&self, other: &Self) -> bool {
false
}
}
impl<T: PartialEq> IsMemo for CuOpt<T> {
fn memo(&self, other: &Self) -> bool {
self == other
}
}
impl<T: PartialEq> PartialEq for CuOpt<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(CuOpt::Some(a), CuOpt::Some(b)) => a == b,
(CuOpt::Some(_), CuOpt::None) => false,
(CuOpt::None, CuOpt::Some(_)) => false,
(CuOpt::None, CuOpt::None) => true,
}
}
}
impl<T> IsMemo for &CuOpt<T> {
fn memo(&self, other: &Self) -> bool {
false
}
}
// #[test]
#[test]
fn try_test() {
// test_poc()
}
// fn test_poc(ctx: Context) {
// let b = Bump::new();
// let h = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// // c: CuOpt::Some(b.alloc(|| {})),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// };
// let h2 = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// // c: CuOpt::Some(b.alloc(|| {})),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// };
// // dbg!((&h.a).memo((&&h2.a)));
// // dbg!((&h.b).memo((&&h2.b)));
// // dbg!((&h.c).memo((&&h2.c)));
// //
// // ctx: Context
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
// }
// fn test_realzies() {
// let h = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// };
// let g = |ctx: Context| {
// //
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
// };
// }

View file

@ -486,14 +486,6 @@ where
}
}
// impl IntoIterator for VNode {
// type Item = VNode;
// type IntoIter = std::iter::Once<Self::Item>;
// fn into_iter(self) -> Self::IntoIter {
// std::iter::once(self)
// }
// }
impl<'a> IntoIterator for VNode<'a> {
type Item = VNode<'a>;
type IntoIter = std::iter::Once<Self::Item>;
@ -665,8 +657,9 @@ pub fn virtual_child<'a, T: Properties>(
) -> VNode<'a> {
// currently concerned about if props have a custom drop implementation
// might override it with the props macro
VNode::Component(
ctx.bump()
.alloc(crate::nodes::VComponent::new(f, props, key)),
)
todo!()
// VNode::Component(
// ctx.bump()
// .alloc(crate::nodes::VComponent::new(f, props, key)),
// )
}

View file

@ -235,7 +235,7 @@ pub struct VComponent<'src> {
pub children: &'src [VNode<'src>],
// a pointer into the bump arena (given by the 'src lifetime)
raw_props: Box<dyn Any>,
// raw_props: Box<dyn Any>,
// raw_props: *const (),
// a pointer to the raw fn typ
@ -243,13 +243,24 @@ pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
}
unsafe fn transmogrify<'a>(p: impl Properties + 'a) -> impl Properties + 'static {
todo!()
}
impl<'a> VComponent<'a> {
// use the type parameter on props creation and move it into a portable context
// this lets us keep scope generic *and* downcast its props when we need to:
// - perform comparisons when diffing (memoization)
// TODO: lift the requirement that props need to be static
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
pub fn new<P: Properties>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
pub fn new<P: Properties>(
component: FC<P>,
// props: bumpalo::boxed::Box<'a, P>,
props: P,
key: Option<&'a str>,
) -> Self {
// pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
// let bad_props = unsafe { transmogrify(props) };
let caller_ref = component as *const ();
// let raw_props = props as *const P as *const ();
@ -271,7 +282,19 @@ impl<'a> VComponent<'a> {
// }
// };
let caller: Rc<dyn Fn(&Scope) -> VNode + 'a> = Rc::new(move |scp| todo!());
// let prref: &'a P = props.as_ref();
let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(move |scope| {
//
// let props2 = bad_props;
// props.as_ref()
// let ctx = Context {
// props: prref,
// scope,
// };
todo!()
// component(ctx)
});
// let caller = Rc::new(create_closure(component, raw_props));
let key = match key {
@ -283,7 +306,7 @@ impl<'a> VComponent<'a> {
key,
ass_scope: Rc::new(RefCell::new(None)),
user_fc: caller_ref,
raw_props: Box::new(props),
// raw_props: Box::new(props),
_p: PhantomData,
children: &[],
caller,
@ -292,18 +315,3 @@ impl<'a> VComponent<'a> {
}
}
}
// fn create_closure<P: Properties>(
// component: FC<P>,
// raw_props: *const (),
// ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> {
// move |ctx| -> VNode {
// // cast back into the right lifetime
// let safe_props: &P = unsafe { &*(raw_props as *const P) };
// component(Context {
// props: safe_props,
// scope: ctx,
// })
// // component(ctx, safe_props)
// }
// }

View file

@ -137,12 +137,11 @@ impl VirtualDom {
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
todo!()
// root(Context {
// props: &root_props,
// scope: ctx,
// })
let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
// the lifetime of this closure is just as long as the lifetime on the scope reference
// this closure moves root props (which is static) into this closure
let props = unsafe { &*(&root_props as *const _) };
root(Context { props, scope })
});
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
@ -618,18 +617,23 @@ impl Scope {
.ok_or(Error::FatalInternal("Failed to get caller"))?;
// Cast the caller ptr from static to one with our own reference
let new_head = unsafe {
std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'sel>>(
caller.as_ref(),
)
}(&self);
let c2: &OpaqueComponent<'static> = caller.as_ref();
let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) };
todo!();
// self.frames.cur_frame_mut().head_node = new_head;
let unsafe_head = unsafe { self.own_vnodes(c3) };
self.frames.cur_frame_mut().head_node = unsafe_head;
Ok(())
}
// this is its own function so we can preciesly control how lifetimes flow
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> {
let new_head: VNode<'a> = f(self);
let out: VNode<'static> = std::mem::transmute(new_head);
out
}
// A safe wrapper around calling listeners
// calling listeners will invalidate the list of listeners
// The listener list will be completely drained because the next frame will write over previous listeners