mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Feat: massive changes to definition of components
This change switches back to the original `ctx<props>` syntax for commponents. This lets lifetime elision to remove the need to match exactly which lifetime (props or ctx) gets carried to the output. As such, `Props` is currently required to be static. It *is* possible to loosen this restriction, and will be done in the future, though only through adding metadata about the props through the Props derive macro. Implementing the IS_STATIC trait is unsafe, so the derive macro will do it through some heuristics. For now, this unlocks sharing vnodes from parents to children, enabling pass-thru components, fragments, portals, etc.
This commit is contained in:
parent
c1fd848f89
commit
508c560320
82 changed files with 692 additions and 558 deletions
80
README.md
80
README.md
|
@ -7,13 +7,14 @@
|
|||
|
||||
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
||||
|
||||
|
||||
```rust
|
||||
//! A complete dioxus web app
|
||||
use dioxus_web::*;
|
||||
|
||||
fn Example(ctx: Context, props: &()) -> DomTree {
|
||||
let selection = use_state(ctx, || "...?");
|
||||
struct Props { initial_text: &'static str }
|
||||
|
||||
fn Example(ctx: Context<Props>) -> VNode {
|
||||
let selection = use_state(ctx, move || ctx.initial_text);
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
@ -25,23 +26,27 @@ fn Example(ctx: Context, props: &()) -> DomTree {
|
|||
};
|
||||
|
||||
fn main() {
|
||||
dioxus_web::start(Example).block_on();
|
||||
dioxus_web::start_with_props(Example, Props { initial_text: "..?" }).block_on();
|
||||
}
|
||||
```
|
||||
|
||||
Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps, Android apps, iOS Apps, and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
|
||||
|
||||
If you know React, then you already know Dioxus.
|
||||
|
||||
### **Things you'll love ❤️:**
|
||||
|
||||
- Ergonomic design
|
||||
- Minimal boilerplate
|
||||
- Simple build, test, and deploy
|
||||
- Familiar design and semantics
|
||||
- Simple build, test, and deploy
|
||||
- Support for html! and rsx! templating
|
||||
- SSR, WASM, desktop, and mobile support
|
||||
- Powerful and simple integrated state management
|
||||
- Rust! (enums, static types, modules, efficiency)
|
||||
|
||||
|
||||
## Get Started with...
|
||||
|
||||
<table style="width:100%" align="center">
|
||||
<tr >
|
||||
<th><a href="http://github.com/jkelleyrtp/dioxus">Web</a></th>
|
||||
|
@ -54,6 +59,7 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
|
|||
</table>
|
||||
|
||||
## Explore
|
||||
|
||||
- [**HTML Templates**: Drop in existing HTML5 templates with html! macro](docs/guides/00-index.md)
|
||||
- [**RSX Templates**: Clean component design with rsx! macro](docs/guides/00-index.md)
|
||||
- [**Running the examples**: Explore the vast collection of samples, tutorials, and demos](docs/guides/00-index.md)
|
||||
|
@ -64,11 +70,71 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
|
|||
- [**1st party hooks**: Cross-platform router hook](docs/guides/01-ssr.md)
|
||||
- [**Community hooks**: 3D renderers](docs/guides/01-ssr.md)
|
||||
|
||||
|
||||
## Blog Posts
|
||||
|
||||
- [Why we need a stronger typed web]()
|
||||
- [Isomorphic webapps in 10 minutes]()
|
||||
- [Rust is high level too]()
|
||||
- [Eliminating crashes with Rust webapps]()
|
||||
- [Tailwind for Dioxus]()
|
||||
- [The monoglot startup]()
|
||||
|
||||
## FAQ
|
||||
|
||||
### 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
|
||||
- advanced pattern matching
|
||||
- immutability by default
|
||||
- clean, composable iterators
|
||||
- a good module system
|
||||
- integrated documentation
|
||||
- inline built-in unit/integration testing
|
||||
- best-in-class error handling
|
||||
- simple and fast build system
|
||||
- include_str! for integrating html/css/svg templates directly
|
||||
- various macros (html!, rsx!) for fast template iteration
|
||||
|
||||
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. Dioxus also works on the server, on the web, on mobile, on desktop - and it runs completely natively so performance is never an issue.
|
||||
|
||||
### Immutability by default?
|
||||
|
||||
---
|
||||
|
||||
Rust, like JS and TS, supports both mutable and immutable data. With JS, `const` would be used to signify immutable data, while in rust, the absence of `mut` signifies immutable data.
|
||||
|
||||
Mutability:
|
||||
|
||||
```rust
|
||||
let mut val = 10; // rust
|
||||
let val = 10; // js
|
||||
```
|
||||
|
||||
Immutability
|
||||
|
||||
```rust
|
||||
let val = 10; // rust
|
||||
const val = 10; // js
|
||||
```
|
||||
|
||||
However, `const` in JS does not prohibit you from modify the value itself only disallowing assignment. In Rust, immutable **is immutable**. You _never_ have to work about accidentally mutating data; mutating immutable data in Rust requires deliberate advanced datastructures that you won't find in your typical frontend code.
|
||||
|
||||
## How do strings work?
|
||||
|
||||
---
|
||||
|
||||
In rust, we have `&str`, `&'static str` `String`, and `Rc<str>`. It's a lot, yes, and it might be confusing at first. But it's actually not too bad.
|
||||
|
||||
In Rust, UTF-8 is supported natively, allowing for emoji and extended character sets (like Chinese and Arabic!) instead of the typical ASCII. The primitive `str` can be seen as a couple of UTF-8 code points squished together with a dynamic size. Because this size is variable (not known at compile time for any single character), we reference an array of UTF-8 code points as `&str`. Essentially, we're referencing (the & symbol) some dynamic `str` (a collection of UTF-8 points).
|
||||
|
||||
For text encoded directly in your code, this collection of UTF-8 code points is given the `'static` reference lifetime - essentially meaning the text can be safely referenced for the entire runtime of your program. Contrast this with JS, where a string will only exist for as long as code references it before it gets cleaned up by the garbage collector.
|
||||
|
||||
For text that needs to have characters added, removed, sorted, uppercased, formatted, accessed for mutation, etc, Rust has the `String` type, which is essentially just a dynamically sized `str`. In JS, if you add a character to your string, you actually create an entirely new string (completely cloning the old one first). In Rust, you can safely added characters to strings _without_ having to clone first, making string manipulation in Rust very efficient.
|
||||
|
||||
Finally, we have `Rc<str>`. This is essentially Rust's version of JavaScript's `string`. In JS, whenever you pass a `string` around (and don't mutate it), you don't actually clone it, but rather just increment a counter that says "this code is using this string." This counter prevents the garbage collector from deleting the string before your code is done using it. Only when all parts of your code are done with the string, will the string be deleted. `Rc<str>` works exactly the same way in Rust, but requires a deliberate `.clone()` to get the same behavior. In most instances, Dioxus will automatically do this for you, saving the trouble of having to `clone` when you pass an `Rc<str>` into child components. `Rc<str>` is typically better than `String` for Rust - it allows cheap sharing of strings, and through `make_mut` you can always produce your own mutable copy for modifying. You might not see `Rc<str>` in other Rust libraries as much, but you will see it in Dioxus due to Dioxus' aggressive memoization and focus on efficiency and performance.
|
||||
|
||||
If you run into issues with `&str`, `String`, `Rc<str>`, just try cloning and `to_string` first. For the vast majority of apps, the slight performance hit will be unnoticeable. Once you get better with Strings, it's very easy to go back and remove all the clones for more efficient alternatives, but you will likely never need to.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
## Components
|
||||
|
||||
Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
|
||||
|
||||
```rust
|
||||
|
@ -9,7 +9,7 @@ struct MyProps {
|
|||
}
|
||||
|
||||
fn Example(ctx: Context<MyProps>) -> VNode {
|
||||
html! { <div> "Hello {ctx.props.name}!" </div> }
|
||||
html! { <div> "Hello {ctx.ctx.name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -25,8 +25,7 @@ fn Example(ctx: Context, name: String) -> VNode {
|
|||
// or
|
||||
|
||||
#[functional_component]
|
||||
static Example: FC = |ctx, name: String| html! { <div> "Hello {name}!" </div> };
|
||||
static Example: FC = |ctx, name: String| html! { <div> "Hello {name}!" </div> };
|
||||
```
|
||||
|
||||
The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.
|
||||
|
||||
The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Utilities
|
||||
|
||||
There are a few macros and utility functions that make life easier when writing Dioxus components.
|
||||
|
||||
There are a few macros and utility functions that make life easier when writing Dioxus components.
|
||||
|
||||
## The `functional_component` procedural macro
|
||||
|
||||
The `functional_component` proc macro allows you to inline props into the generic parameter of the component's context. This is useful When writing "pure" components, or when you don't want the extra clutter of structs, derives, and burden of naming things.
|
||||
|
||||
This macro allows allows a classic struct definition to be embedded directly into the function arguments. The props are automatically pulled from the context and destructured into the function's body, saving an extra step.
|
||||
|
@ -11,13 +11,13 @@ This macro allows allows a classic struct definition to be embedded directly int
|
|||
```rust
|
||||
// Inlines and destructure props *automatically*
|
||||
#[functional_component]
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
|
||||
html! {
|
||||
<div>
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
|
||||
html! {
|
||||
<div>
|
||||
<p> "Hello, {name}!" </p>
|
||||
<p> "Status: {pending}!" </p>
|
||||
<p> "Count {count}!" </p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -37,18 +37,16 @@ fn Example(ctx: &mut Context<ExampleProps>) -> VNode {
|
|||
name, pending, count
|
||||
} = ctx.props();
|
||||
|
||||
rsx! {
|
||||
<div>
|
||||
rsx! {
|
||||
<div>
|
||||
<p> "Hello, {name}!" </p>
|
||||
<p> "Status: {pending}!" </p>
|
||||
<p> "Count {count}!" </p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## The rsx! macro
|
||||
|
||||
The rsx! macacro is similar to the html! macro in other libraries, but with a few add-ons to make it fun and easy to work with. We'll cover the rsx macro more in depth in the [vnode-macro](3-vnode-macros.md) chapter.
|
||||
|
||||
|
|
|
@ -1,39 +1,38 @@
|
|||
# VNode Macros
|
||||
|
||||
Dioxus comes preloaded with two macros for creating VNodes.
|
||||
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 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.
|
||||
|
||||
There is also limited support for dynamic handlers, but it will function similarly to JSX.
|
||||
|
||||
```rust
|
||||
#[fc]
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<p> "Hello, {name}!" </p>
|
||||
<p> "Status: {pending}!" </p>
|
||||
<p> "Count {count}!" </p>
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 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. 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.
|
||||
|
||||
It's also a bit easier on the eyes 🙂.
|
||||
|
||||
It's also a bit easier on the eyes 🙂.
|
||||
|
||||
```rust
|
||||
#[fc]
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
|
||||
ctx.render(rsx! {
|
||||
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
p {"Hello, {name}!"}
|
||||
p {"Status: {pending}!"}
|
||||
|
@ -45,16 +44,17 @@ fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
|
|||
```
|
||||
|
||||
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
|
||||
|
||||
- `name: value` sets the property on this element.
|
||||
- `"text"` adds a new text element
|
||||
- `tag {}` adds a new child element
|
||||
- `tag {}` adds a new child element
|
||||
- `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.
|
||||
|
||||
```rust
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
|
|
|
@ -21,12 +21,12 @@ struct RootProps {
|
|||
navigator: Receiver<&'static str>,
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &RootProps) -> DomTree {
|
||||
fn App(ctx: Context, props: &RootProps) -> VNode {
|
||||
let router = use_router(&ctx, |router| {});
|
||||
|
||||
let navigator = use_history(&ctx);
|
||||
|
||||
use_receiver(&ctx, || props.recv.clone(), |to| navigator.navigate(to));
|
||||
use_receiver(&ctx, || ctx.recv.clone(), |to| navigator.navigate(to));
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
|
|
@ -12,7 +12,7 @@ fn main() {
|
|||
diouxs_webview::launch(App).run().await
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let router = use_router(&ctx, |router| {
|
||||
//
|
||||
router.get("/dogs/:dogId/").render(|ctx, request| {
|
||||
|
|
|
@ -19,7 +19,7 @@ mod state {
|
|||
}
|
||||
}
|
||||
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
|
||||
|
@ -28,7 +28,7 @@ static APP: FC<()> = |ctx, props| {
|
|||
};
|
||||
|
||||
/// Draw the navbar on top of the screen
|
||||
static Navbar: FC<state::Route> = |ctx, props| {
|
||||
static Navbar: FC<state::Route> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
|
||||
|
@ -36,7 +36,7 @@ static Navbar: FC<state::Route> = |ctx, props| {
|
|||
})
|
||||
};
|
||||
|
||||
static Homepage: FC<()> = |ctx, props| {
|
||||
static Homepage: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
|
||||
|
|
|
@ -113,11 +113,12 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
- [ ] Re-exported through the `dioxus` crate (not core)
|
||||
- [ ] Hooks
|
||||
- [ ] Re-exported through the `dioxus` crate (not essential to core virtualdom)
|
||||
- [ ] fragments
|
||||
- [ ] pass-thru components
|
||||
|
||||
## Less-essential todos
|
||||
|
||||
- [ ] HTML doesn't require strings between elements (copy-paste from internet)
|
||||
- [ ] Make events lazy (use traits + Box<dyn>) - not sure what this means anymore
|
||||
- [ ] Beef up the dioxus CLI tool to report build progress
|
||||
- [ ] Extract arena logic out for better safety guarantees
|
||||
- [ ] Extract BumpFrame logic out for better safety guarantees
|
||||
|
@ -130,8 +131,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
|
||||
lower priority features
|
||||
|
||||
- [ ] Attributes on elements should implement format_args instead of string fmt
|
||||
- [ ] fragments
|
||||
- [x] Attributes on elements should implement format_args instead of string fmt
|
||||
- [ ] node refs (postpone for future release?)
|
||||
- [ ] styling built-in (future release?)
|
||||
- [ ] key handler?
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
# Solved problems while building Dioxus
|
||||
|
||||
focuses:
|
||||
|
||||
- ergonomics
|
||||
- render agnostic
|
||||
- remote coupling
|
||||
- memory efficient
|
||||
- concurrent
|
||||
- concurrent
|
||||
- global context
|
||||
- scheduled updates
|
||||
-
|
||||
|
||||
-
|
||||
|
||||
## FC Macro for more elegant components
|
||||
|
||||
Originally the syntax of the FC macro was meant to look like:
|
||||
|
||||
```rust
|
||||
|
@ -21,7 +22,7 @@ fn example(ctx: &Context<{ name: String }>) -> VNode {
|
|||
}
|
||||
```
|
||||
|
||||
`Context` was originally meant to be more obviously parameterized around a struct definition. However, while this works with rustc, this does not work well with Rust Analyzer. Instead, the new form was chosen which works with Rust Analyzer and happens to be more ergonomic.
|
||||
`Context` was originally meant to be more obviously parameterized around a struct definition. However, while this works with rustc, this does not work well with Rust Analyzer. Instead, the new form was chosen which works with Rust Analyzer and happens to be more ergonomic.
|
||||
|
||||
```rust
|
||||
#[fc]
|
||||
|
@ -51,7 +52,8 @@ impl FunctionProvider for SomeComponent {
|
|||
|
||||
pub type SomeComponent = FunctionComponent<function_name>;
|
||||
```
|
||||
By default, the underlying component is defined as a "functional" implementation of the `Component` trait with all the lifecycle methods. In Dioxus, we don't allow components as structs, and instead take a "hooks-only" approach. However, we still need props. To get these without dealing with traits, we just assume functional components are modules. This lets the macros assume an FC is a module, and `FC::Props` is its props and `FC::component` is the component. Yew's method does a similar thing, but with associated types on traits.
|
||||
|
||||
By default, the underlying component is defined as a "functional" implementation of the `Component` trait with all the lifecycle methods. In Dioxus, we don't allow components as structs, and instead take a "hooks-only" approach. However, we still need ctx. To get these without dealing with traits, we just assume functional components are modules. This lets the macros assume an FC is a module, and `FC::Props` is its props and `FC::component` is the component. Yew's method does a similar thing, but with associated types on traits.
|
||||
|
||||
Perhaps one day we might use traits instead.
|
||||
|
||||
|
@ -68,7 +70,7 @@ mod Example {
|
|||
struct Props {
|
||||
name: String
|
||||
}
|
||||
|
||||
|
||||
fn component(ctx: &Context<Props>) -> VNode {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
|
@ -95,24 +97,23 @@ fn example(ctx: &Context, name: String) -> VNode {
|
|||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
|
||||
// .. expands to
|
||||
// .. expands to
|
||||
|
||||
mod Example {
|
||||
use super::*;
|
||||
static NAME: &'static str = "Example";
|
||||
struct Props {
|
||||
name: String
|
||||
}
|
||||
}
|
||||
fn component(ctx: &Context<Props>) -> VNode {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Live Components
|
||||
Live components are a very important part of the Dioxus ecosystem. However, the goal with live components was to constrain their implementation purely to APIs available through Context (concurrency, context, subscription).
|
||||
|
||||
Live components are a very important part of the Dioxus ecosystem. However, the goal with live components was to constrain their implementation purely to APIs available through Context (concurrency, context, subscription).
|
||||
|
||||
From a certain perspective, live components are simply server-side-rendered components that update when their props change. Here's more-or-less how live components work:
|
||||
|
||||
|
@ -143,10 +144,12 @@ static LiveFc: FC = |ctx, refresh_handler: impl FnOnce| {
|
|||
}
|
||||
```
|
||||
|
||||
Notice that LiveComponent receivers (the client-side interpretation of a LiveComponent) are simply suspended components waiting for updates from the LiveContext (the context that wraps the app to make it "live").
|
||||
Notice that LiveComponent receivers (the client-side interpretation of a LiveComponent) are simply suspended components waiting for updates from the LiveContext (the context that wraps the app to make it "live").
|
||||
|
||||
## Allocation Strategy (ie incorporating Dodrio research)
|
||||
----
|
||||
|
||||
---
|
||||
|
||||
The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like:
|
||||
|
||||
```rust
|
||||
|
@ -157,7 +160,7 @@ static Example: FC<()> = |ctx| {
|
|||
// expands to...
|
||||
|
||||
static Example: FC<()> = |ctx| {
|
||||
// This function converts a Fn(allocator) -> VNode closure to a DomTree struct that will later be evaluated.
|
||||
// This function converts a Fn(allocator) -> VNode closure to a VNode struct that will later be evaluated.
|
||||
html_macro_to_vnodetree(move |allocator| {
|
||||
let mut node0 = allocator.alloc(VElement::div);
|
||||
let node1 = allocator.alloc_text("blah");
|
||||
|
@ -166,45 +169,45 @@ static Example: FC<()> = |ctx| {
|
|||
})
|
||||
};
|
||||
```
|
||||
|
||||
At runtime, the new closure is created that captures references to `ctx`. Therefore, this closure can only be evaluated while `ctx` is borrowed and in scope. However, this closure can only be evaluated with an `allocator`. Currently, the global and Bumpalo allocators are available, though in the future we will add support for creating a VDom with any allocator or arena system (IE Jemalloc, wee-alloc, etc). The intention here is to allow arena allocation of VNodes (no need to box nested VNodes). Between diffing phases, the arena will be overwritten as old nodes are replaced with new nodes. This saves allocation time and enables bump allocators.
|
||||
|
||||
|
||||
|
||||
## Context and lifetimes
|
||||
|
||||
We want components to be able to fearlessly "use_context" for use in state management solutions.
|
||||
|
||||
However, we cannot provide these guarantees without compromising the references. If a context mutates, it cannot lend out references.
|
||||
|
||||
Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need to be updated, even if they themselves aren't updated. Otherwise, a reference would be pointing at data that could have potentially been moved.
|
||||
Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need to be updated, even if they themselves aren't updated. Otherwise, a reference would be pointing at data that could have potentially been moved.
|
||||
|
||||
To do this safely is a pretty big challenge. We need to provide a method of sharing data that is safe, ergonomic, and that fits the abstraction model.
|
||||
|
||||
Enter, the "ContextGuard".
|
||||
|
||||
The "ContextGuard" is very similar to a Ref/RefMut from the RefCell implementation, but one that derefs into actual underlying value.
|
||||
The "ContextGuard" is very similar to a Ref/RefMut from the RefCell implementation, but one that derefs into actual underlying value.
|
||||
|
||||
However, derefs of the ContextGuard are a bit more sophisticated than the Ref model.
|
||||
However, derefs of the ContextGuard are a bit more sophisticated than the Ref model.
|
||||
|
||||
For RefCell, when a Ref is taken, the RefCell is now "locked." This means you cannot take another `borrow_mut` instance while the Ref is still active. For our purposes, our modification phase is very limited, so we can make more assumptions about what is safe.
|
||||
|
||||
1. We can pass out ContextGuards from any use of use_context. These don't actually lock the value until used.
|
||||
2. The ContextGuards only lock the data while the component is executing and when a callback is running.
|
||||
3. Modifications of the underlying context occur after a component is rendered and after the event has been run.
|
||||
|
||||
|
||||
With the knowledge that usage of ContextGuard can only be achieved in a component context and the above assumptions, we can design a guard that prevents any poor usage but also is ergonomic.
|
||||
|
||||
As such, the design of the ContextGuard must:
|
||||
|
||||
- be /copy/ for easy moves into closures
|
||||
- never point to invalid data (no dereferencing of raw pointers after movable data has been changed (IE a vec has been resized))
|
||||
- not allow references of underlying data to leak into closures
|
||||
|
||||
To solve this, we can be clever with lifetimes to ensure that any data is protected, but context is still ergonomic.
|
||||
|
||||
1. As such, deref context guard returns an element with a lifetime bound to the borrow of the guard.
|
||||
2. Because we cannot return locally borrowed data AND we consume context, this borrow cannot be moved into a closure.
|
||||
3. ContextGuard is *copy* so the guard itself can be moved into closures
|
||||
4. ContextGuard derefs with its unique lifetime *inside* closures
|
||||
1. As such, deref context guard returns an element with a lifetime bound to the borrow of the guard.
|
||||
2. Because we cannot return locally borrowed data AND we consume context, this borrow cannot be moved into a closure.
|
||||
3. ContextGuard is _copy_ so the guard itself can be moved into closures
|
||||
4. ContextGuard derefs with its unique lifetime _inside_ closures
|
||||
5. Derefing a ContextGuard evaluates the underlying selector to ensure safe temporary access to underlying data
|
||||
|
||||
```rust
|
||||
|
@ -235,34 +238,38 @@ fn Example<'src>(ctx: Context<'src, ()>) -> VNode<'src> {
|
|||
```
|
||||
|
||||
A few notes:
|
||||
- this does *not* protect you from data races!!!
|
||||
- this does *not* force rendering of components
|
||||
- this *does* protect you from invalid + UB use of data
|
||||
|
||||
- this does _not_ protect you from data races!!!
|
||||
- this does _not_ force rendering of components
|
||||
- this _does_ protect you from invalid + UB use of data
|
||||
- this approach leaves lots of room for fancy state management libraries
|
||||
- this approach is fairly quick, especially if borrows can be cached during usage phases
|
||||
|
||||
|
||||
## Concurrency
|
||||
|
||||
For Dioxus, concurrency is built directly into the VirtualDOM lifecycle and event system. Suspended components prove "no changes" while diffing, and will cause a lifecycle update later. This is considered a "trigger" and will cause targeted diffs and re-renders. Renderers will need to await the Dioxus suspense queue if they want to process these updates. This will typically involve joining the suspense queue and event listeners together like:
|
||||
|
||||
```rust
|
||||
// wait for an even either from the suspense queue or our own custom listener system
|
||||
let (left, right) = join!(vdom.suspense_queue, self.custom_event_listener);
|
||||
```
|
||||
|
||||
LiveView is built on this model, and updates from the WebSocket connection to the host server are treated as external updates. This means any renderer can feed targeted EditLists (the underlying message of this event) directly into the VirtualDOM.
|
||||
|
||||
## Execution Model
|
||||
|
||||
<!-- todo -->
|
||||
|
||||
## Diffing
|
||||
|
||||
Diffing is an interesting story. Since we don't re-render the entire DOM, we need a way to patch up the DOM without visiting every component. To get this working, we need to think in cycles, queues, and stacks. Most of the original logic is pulled from Dodrio as Dioxus and Dodrio share much of the same DNA.
|
||||
Diffing is an interesting story. Since we don't re-render the entire DOM, we need a way to patch up the DOM without visiting every component. To get this working, we need to think in cycles, queues, and stacks. Most of the original logic is pulled from Dodrio as Dioxus and Dodrio share much of the same DNA.
|
||||
|
||||
When an event is triggered, we find the callback that installed the listener and run it. We then record all components affected by the running of the "subscription" primitive. In practice, many hooks will initiate a subscription, so it is likely that many components throughout the entire tree will need to be re-rendered. For each component, we attach its index and the type of update it needs.
|
||||
When an event is triggered, we find the callback that installed the listener and run it. We then record all components affected by the running of the "subscription" primitive. In practice, many hooks will initiate a subscription, so it is likely that many components throughout the entire tree will need to be re-rendered. For each component, we attach its index and the type of update it needs.
|
||||
|
||||
In practice, removals trump prop updates which trump subscription updates. Therefore, we only process updates where props are directly changed first, as this will likely flow into child components.
|
||||
In practice, removals trump prop updates which trump subscription updates. Therefore, we only process updates where props are directly changed first, as this will likely flow into child components.
|
||||
|
||||
Roughly, the flow looks like:
|
||||
|
||||
- Process the initiating event
|
||||
- Mark components affected by the subscription API (the only way of causing forward updates)
|
||||
- Descend from the root into children, ignoring those not affected by the subscription API. (walking the tree until we hit the first affected component, or choosing the highest component)
|
||||
|
@ -281,13 +288,9 @@ struct DiffMachine {
|
|||
|
||||
On the actual diffing level, we're using the diffing algorithm pulled from Dodrio, but plan to move to a dedicated crate that implements Meyers/Patience for us. During the diffing phase, we track our current position using a "Traversal" which implements the "MoveTo". When "MoveTo" is combined with "Edit", it is possible for renderers to fully interpret a series of Moves and Edits together to update their internal node structures for rendering.
|
||||
|
||||
|
||||
|
||||
|
||||
## Patch Stream
|
||||
|
||||
One of the most important parts of Dioxus is the ability to stream patches from server to client. However, this inherently has challenges where raw VNodes attach listeners to themselves, and are therefore not serializable.
|
||||
|
||||
One of the most important parts of Dioxus is the ability to stream patches from server to client. However, this inherently has challenges where raw VNodes attach listeners to themselves, and are therefore not serializable.
|
||||
|
||||
### How do properties work?
|
||||
|
||||
|
@ -295,16 +298,13 @@ How should properties passing work? Should we directly call the child? Should we
|
|||
|
||||
Here's (generally) my head is at:
|
||||
|
||||
Components need to store their props on them if they want to be updated remotely. These props *can* be updated after the fact.
|
||||
Components need to store their props on them if they want to be updated remotely. These props _can_ be updated after the fact.
|
||||
|
||||
Perf concerns:
|
||||
unnecessary function runs
|
||||
- list-y components
|
||||
- hook calls?
|
||||
- making vnodes?
|
||||
|
||||
unnecessary function runs - list-y components - hook calls? - making vnodes?
|
||||
|
||||
Does any of this matter?
|
||||
Should we just run any component we see, immediately and imperatively? That will cause checks throughout the whole tree, no matter where the update occurred
|
||||
Should we just run any component we see, immediately and imperatively? That will cause checks throughout the whole tree, no matter where the update occurred
|
||||
|
||||
https://calendar.perfplanet.com/2013/diff/
|
||||
|
||||
|
@ -313,7 +313,7 @@ Here's how react does it:
|
|||
Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design:
|
||||
|
||||
```rust
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
let title = use_context(Title);
|
||||
ctx.render(html!{
|
||||
<div>
|
||||
|
@ -321,7 +321,7 @@ static APP: FC<()> = |ctx, props| {
|
|||
<HeavyList /> // VComponent::new(|| (FC, PropsForFc)) -> needs a context to immediately update the component's props imperatively? store the props in a box on bump? store the props on the child?
|
||||
// if props didnt change, then let the refernece stay invalid?.... no, cant do that, bump gets reset
|
||||
// immediately update props on the child component if it can be found? -> interesting, feels wrong, but faster, at the very least.
|
||||
// can box on bump for the time being (fast enough), and then move it over? during the comparison phase? props only need to matter
|
||||
// can box on bump for the time being (fast enough), and then move it over? during the comparison phase? props only need to matter
|
||||
// cant downcast (can with transmute, but yikes)
|
||||
// how does chain borrowing work? a -> b -> c -> d
|
||||
// if b gets marked as dirty, then c and d are invalidated (semantically, UB, but not *bad* UB, just data races)
|
||||
|
@ -330,11 +330,11 @@ static APP: FC<()> = |ctx, props| {
|
|||
// treat like a context selector?
|
||||
// use_props::<P>(2)
|
||||
// child_props: Map<Scope, Box<dyn Props>>
|
||||
// vs children: BTreeSet<Scope> -> to get nth
|
||||
// vs children: BTreeSet<Scope> -> to get nth
|
||||
</div>
|
||||
})
|
||||
};
|
||||
static HEAVY_LIST: FC<()> = |ctx, props| {
|
||||
static HEAVY_LIST: FC<()> = |ctx| {
|
||||
ctx.render({
|
||||
{0.100.map(i => <BigElement >)}
|
||||
})
|
||||
|
@ -345,25 +345,23 @@ An update to the use_context subscription will mark the node as dirty. The node
|
|||
|
||||
## FC Layout
|
||||
|
||||
The FC layout was altered to make life easier for us inside the VirtualDom. The "view" function returns an unbounded DomTree object. Calling the "view" function is unsafe under the hood, but prevents lifetimes from leaking out of the function call. Plus, it's easier to write. Because there are no lifetimes on the output (occur purely under the hood), we can escape needing to annotate them.
|
||||
The FC layout was altered to make life easier for us inside the VirtualDom. The "view" function returns an unbounded VNode object. Calling the "view" function is unsafe under the hood, but prevents lifetimes from leaking out of the function call. Plus, it's easier to write. Because there are no lifetimes on the output (occur purely under the hood), we can escape needing to annotate them.
|
||||
|
||||
```rust
|
||||
fn component(ctx: Context, props: &Props) -> DomTree {
|
||||
fn component(ctx: Context, props: &Props) -> VNode {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The DomTree object purely represents a viewable "key". It also forces components to use the "view" function as there is no other way to generate the DomTree object. Because the DomTree is a required type of FC, we can guarantee the same usage and flow patterns for all components.
|
||||
The VNode object purely represents a viewable "key". It also forces components to use the "view" function as there is no other way to generate the VNode object. Because the VNode is a required type of FC, we can guarantee the same usage and flow patterns for all components.
|
||||
|
||||
## Events
|
||||
|
||||
## Events
|
||||
|
||||
Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event
|
||||
|
||||
Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event
|
||||
|
||||
## Optional Props on Components
|
||||
|
||||
A major goal here is ergonomics. Any field that is Option<T> should default to none.
|
||||
A major goal here is ergonomics. Any field that is Option<T> should default to none.
|
||||
|
||||
```rust
|
||||
|
||||
|
@ -380,7 +378,7 @@ struct Props {
|
|||
|
||||
}
|
||||
|
||||
static Component: FC<Props> = |ctx, props| {
|
||||
static Component: FC<Props> = |ctx| {
|
||||
|
||||
}
|
||||
```
|
||||
|
|
|
@ -14,9 +14,9 @@ proc-macro = true
|
|||
[dependencies]
|
||||
once_cell = "1.7.2"
|
||||
proc-macro-hack = "0.5.19"
|
||||
proc-macro2 = "1.0.6"
|
||||
proc-macro2 = { version = "1.0.6" }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
|
||||
# testing
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -5,9 +5,7 @@ use syn::{
|
|||
parse::{Parse, ParseStream},
|
||||
Signature,
|
||||
};
|
||||
use syn::{
|
||||
Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
|
||||
};
|
||||
use syn::{Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility};
|
||||
|
||||
/// A parsed version of the user's input
|
||||
pub struct FunctionComponent {
|
||||
|
@ -135,7 +133,7 @@ impl ToTokens for FunctionComponent {
|
|||
}
|
||||
|
||||
impl<'a> FC for #function_name<'a> {
|
||||
fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree {
|
||||
fn render(ctx: Context<'_>, props: &#function_name<'a>) -> VNode {
|
||||
let #function_name {
|
||||
..
|
||||
} = props;
|
||||
|
@ -244,7 +242,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
|
|||
match output {
|
||||
ReturnType::Default => Err(syn::Error::new_spanned(
|
||||
output,
|
||||
"function components must return a `DomTree`",
|
||||
"function components must return a `VNode`",
|
||||
)),
|
||||
ReturnType::Type(_, ty) => Ok(ty),
|
||||
}
|
||||
|
|
|
@ -40,6 +40,16 @@ pub fn rsx_template(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.
|
||||
#[proc_macro]
|
||||
pub fn html_template(s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsxtemplate::RsxTemplate>(s) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
// #[proc_macro_attribute]
|
||||
// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
|
@ -51,7 +61,7 @@ pub fn rsx_template(s: TokenStream) -> TokenStream {
|
|||
/// ```ignore
|
||||
///
|
||||
/// #[fc]
|
||||
/// fn Example(ctx: Context, name: &str) -> DomTree {
|
||||
/// fn Example(ctx: Context, name: &str) -> VNode {
|
||||
/// ctx.render(rsx! { h1 {"hello {name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
@ -22,7 +22,19 @@ impl Parse for RsxTemplate {
|
|||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
if s.peek(LitStr) {
|
||||
use std::str::FromStr;
|
||||
|
||||
let lit = s.parse::<LitStr>()?;
|
||||
let g = lit.span();
|
||||
let mut value = lit.value();
|
||||
if value.ends_with('\n') {
|
||||
value.pop();
|
||||
if value.ends_with('\r') {
|
||||
value.pop();
|
||||
}
|
||||
}
|
||||
let lit = LitStr::new(&value, lit.span());
|
||||
|
||||
// panic!("{:#?}", lit);
|
||||
match lit.parse::<crate::rsxt::RsxRender>() {
|
||||
Ok(r) => Ok(Self { inner: r }),
|
||||
Err(e) => Err(e),
|
||||
|
|
|
@ -28,7 +28,7 @@ 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 html! macro is used)
|
||||
- Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
|
||||
- Support advanced diffing strategies (patience, Meyers, etc)
|
||||
|
||||
## Design Quirks
|
||||
|
|
|
@ -2,7 +2,7 @@ fn main() {}
|
|||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(dioxus_core::prelude::LazyNodes::new(move |ctx| {
|
||||
let bump = ctx.bump();
|
||||
dioxus::builder::ElementBuilder::new(ctx, "h1")
|
||||
|
@ -15,3 +15,16 @@ static Example: FC<()> = |ctx, props| {
|
|||
.finish()
|
||||
}))
|
||||
};
|
||||
|
||||
struct Props {
|
||||
text: String,
|
||||
}
|
||||
static Example2: FC<Props> = |ctx| {
|
||||
ctx.render(dioxus_core::prelude::LazyNodes::new(move |__ctx| {
|
||||
let bump = __ctx.bump();
|
||||
dioxus::builder::ElementBuilder::new(__ctx, "h1")
|
||||
.children([{ dioxus::builder::text3(bump, format_args!("{}", ctx.text)) }])
|
||||
// .children([{ dioxus::builder::text3(bump, format_args!("hello")) }])
|
||||
.finish()
|
||||
}))
|
||||
};
|
||||
|
|
|
@ -21,12 +21,12 @@ struct ListItem {
|
|||
age: u32,
|
||||
}
|
||||
|
||||
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
|
||||
fn app(ctx: Context<Props>) -> VNode {
|
||||
let (val, set_val) = use_state(&ctx, || 0);
|
||||
|
||||
ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
|
||||
let mut root = builder::ElementBuilder::new(c, "div");
|
||||
for child in &props.items {
|
||||
for child in &ctx.items {
|
||||
// notice that the child directly borrows from our vec
|
||||
// this makes lists very fast (simply views reusing lifetimes)
|
||||
// <ChildItem item=child hanldler=setter />
|
||||
|
@ -55,11 +55,11 @@ struct ChildProps {
|
|||
item_handler: Callback<i32>,
|
||||
}
|
||||
|
||||
fn ChildItem<'a>(ctx: Context<'a>, props: &ChildProps) -> DomTree {
|
||||
fn ChildItem<'a>(ctx: Context<ChildProps>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
onclick: move |evt| (props.item_handler)(10)
|
||||
h1 { "abcd123" }
|
||||
// onclick: move |evt| (ctx.item_handler)(10)
|
||||
h1 { "abcd123 {ctx.item.name}" }
|
||||
h2 { "abcd123" }
|
||||
div {
|
||||
"abcd123"
|
||||
|
|
|
@ -16,7 +16,7 @@ fn main() {
|
|||
};
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let nodes = ctx.children();
|
||||
|
||||
//
|
||||
|
@ -26,43 +26,3 @@ static Example: FC<()> = |ctx, props| {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct MyContext<'a, T> {
|
||||
props: &'a T,
|
||||
inner: &'a Scope,
|
||||
}
|
||||
impl<'a, T> MyContext<'a, T> {
|
||||
fn children(&self) -> Vec<VNode<'a>> {
|
||||
todo!()
|
||||
}
|
||||
pub fn render2<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
&self,
|
||||
lazy_nodes: LazyNodes<'a, F>,
|
||||
) -> VNode<'a> {
|
||||
self.inner.render2(lazy_nodes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for MyContext<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.props
|
||||
}
|
||||
}
|
||||
|
||||
struct MyProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn example(scope: MyContext<MyProps>) -> VNode {
|
||||
let childs = scope.children();
|
||||
|
||||
scope.inner.render2(rsx! {
|
||||
div {
|
||||
"{scope.title}"
|
||||
{childs}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,19 +10,20 @@ struct Props {
|
|||
}
|
||||
|
||||
#[allow(unused)]
|
||||
static Example: FC<Props> = |ctx, props| {
|
||||
static Example: FC<Props> = |cpt| {
|
||||
todo!()
|
||||
|
||||
// let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
|
||||
|
||||
// ctx.render(LazyNodes::new(move |bump| {
|
||||
// builder::ElementBuilder::new(bump, "button")
|
||||
// .on("click", move |_| {
|
||||
// println!("Value is {}", props.name);
|
||||
// println!("Value is {}", ctx.name);
|
||||
// println!("Value is {}", value.as_str());
|
||||
// println!("Value is {}", *value);
|
||||
// })
|
||||
// .on("click", move |_| {
|
||||
// println!("Value is {}", props.name);
|
||||
// println!("Value is {}", ctx.name);
|
||||
// })
|
||||
// .finish()
|
||||
// }))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use dioxus_core::component::fc_to_builder;
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
static BLAH: FC<()> = |ctx, _props| {
|
||||
static BLAH: FC<()> = |ctx| {
|
||||
let g = "asd".to_string();
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
@ -17,7 +17,7 @@ pub struct ExampleProps {
|
|||
some_field: String,
|
||||
}
|
||||
|
||||
static SomeComponent: FC<ExampleProps> = |ctx, _props| {
|
||||
static SomeComponent: FC<ExampleProps> = |ctx| {
|
||||
let blah = rsx! {
|
||||
div {}
|
||||
};
|
||||
|
|
|
@ -10,14 +10,21 @@ fn main() {
|
|||
.map(|f| f / 3);
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (name, set_name) = use_state(&ctx, || "...?");
|
||||
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "Hello, {name}" </h1>
|
||||
<button onclick={move |_| set_name("jack")}> "jack!" </button>
|
||||
<button onclick={move |_| set_name("jill")}> "jill!" </button>
|
||||
</div>
|
||||
})
|
||||
ctx.render(rsx!(
|
||||
div {
|
||||
h1 { "Hello, {name}" }
|
||||
// look ma - we can rsx! and html! together
|
||||
{["jack", "jill"].iter().map(|f| html!(<button onclick={move |_| set_name(f)}> "{f}" </button>))}
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a, P>(
|
||||
ctx: &'a Context<'src, P>,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> VNode<'src> {
|
||||
ctx.render(lazy_nodes)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ mod baller {
|
|||
use super::*;
|
||||
pub struct BallerProps {}
|
||||
|
||||
pub fn Baller(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn Baller(ctx: Context<()>) -> VNode {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub struct TallerProps {
|
|||
a: &'static str,
|
||||
}
|
||||
|
||||
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
|
||||
pub fn Taller(ctx: Context<TallerProps>) -> VNode {
|
||||
let b = true;
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ fn main() {}
|
|||
// div(bump)
|
||||
// .attr("class", "edit")
|
||||
// .child(text("Hello"))
|
||||
// .child(text(ctx.props.text.as_ref()))
|
||||
// .child(text(ctx.ctx.text.as_ref()))
|
||||
// .finish()
|
||||
// })
|
||||
// }
|
||||
|
@ -123,7 +123,7 @@ fn main() {}
|
|||
// _ => html! { <div> </div> },
|
||||
// };
|
||||
|
||||
// if ctx.props.blah {
|
||||
// if ctx.ctx.blah {
|
||||
// html! { <div> </div> }
|
||||
// } else {
|
||||
// tex
|
||||
|
|
|
@ -7,23 +7,19 @@ use dioxus_core::prelude::*;
|
|||
|
||||
fn main() {}
|
||||
|
||||
static Header: FC<()> = |ctx, props| {
|
||||
let inner = use_ref(&ctx, || 0);
|
||||
static Header: FC<()> = |ctx| {
|
||||
let inner = use_ref(ctx, || 0);
|
||||
|
||||
let handler1 = move || println!("Value is {}", inner.borrow());
|
||||
|
||||
ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| {
|
||||
builder::ElementBuilder::new(nodectx, "div")
|
||||
.child(VNode::Component(VComponent::new(
|
||||
Bottom,
|
||||
nodectx.bump().alloc(()),
|
||||
None,
|
||||
)))
|
||||
.child(VNode::Component(VComponent::new(Bottom, (), None)))
|
||||
.finish()
|
||||
}))
|
||||
};
|
||||
|
||||
static Bottom: FC<()> = |ctx, props| {
|
||||
static Bottom: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "bruh 1" </h1>
|
||||
|
@ -32,7 +28,7 @@ static Bottom: FC<()> = |ctx, props| {
|
|||
})
|
||||
};
|
||||
|
||||
fn Top(ctx: Context, a: &str, b: &i32, c: &impl Fn()) -> DomTree {
|
||||
fn Top(ctx: Context<()>) -> VNode {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "bruh 1" </h1>
|
||||
|
@ -124,44 +120,44 @@ fn try_test() {
|
|||
// test_poc()
|
||||
}
|
||||
|
||||
fn test_poc(ctx: Context) {
|
||||
let b = Bump::new();
|
||||
// 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 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()>),
|
||||
};
|
||||
// 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());
|
||||
}
|
||||
// // 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(|| {}),
|
||||
};
|
||||
// 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())
|
||||
};
|
||||
}
|
||||
// let g = |ctx: Context| {
|
||||
// //
|
||||
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
|
||||
// };
|
||||
// }
|
||||
|
|
|
@ -16,22 +16,22 @@ fn test() {
|
|||
// let g: FC<ButtonProps> = CustomButton;
|
||||
}
|
||||
|
||||
trait Render {
|
||||
fn render(ctx: Context, props: &Self) -> DomTree;
|
||||
trait Render: Sized {
|
||||
fn render(ctx: Context<Self>) -> VNode;
|
||||
}
|
||||
// include as much as you might accept
|
||||
struct Button<'a> {
|
||||
onhover: Option<&'a dyn Fn()>,
|
||||
struct Button {
|
||||
onhover: Option<Box<dyn Fn()>>,
|
||||
}
|
||||
|
||||
impl Render for Button<'_> {
|
||||
fn render(ctx: Context, _props: &Self) -> DomTree {
|
||||
impl Render for Button {
|
||||
fn render(ctx: Context<Self>) -> VNode {
|
||||
let _onfocus = move |_evt: ()| log::debug!("Focused");
|
||||
|
||||
// todo!()
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
// ..props.attrs,
|
||||
// ..ctx.attrs,
|
||||
class: "abc123",
|
||||
// style: { a: 2, b: 3, c: 4 },
|
||||
onclick: move |_evt| {
|
||||
|
@ -47,7 +47,7 @@ impl Render for Button<'_> {
|
|||
}
|
||||
|
||||
// #[fc]
|
||||
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {}
|
||||
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> VNode {}
|
||||
|
||||
// h1 {
|
||||
// tag: "type", abc: 123, class: "big small wide short",
|
||||
|
|
|
@ -14,10 +14,28 @@ struct SomeProps {
|
|||
name: String,
|
||||
}
|
||||
|
||||
static Example: FC<SomeProps> = |ctx, _props| {
|
||||
static Example: FC<SomeProps> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<h1> "hello world!" </h1>
|
||||
</div>
|
||||
})
|
||||
};
|
||||
|
||||
// #[test]
|
||||
#[derive(PartialEq, Clone)]
|
||||
struct MyStruct {
|
||||
a: String,
|
||||
}
|
||||
|
||||
fn check_before_to_owned() {
|
||||
let new_str = MyStruct {
|
||||
a: "asd".to_string(),
|
||||
};
|
||||
|
||||
let out = town(&new_str);
|
||||
}
|
||||
|
||||
fn town<T: ToOwned + PartialEq>(t: &T) -> T::Owned {
|
||||
t.to_owned()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//!
|
||||
//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
|
||||
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||
//! that ensures compile-time required and optional fields on props.
|
||||
//! that ensures compile-time required and optional fields on ctx.
|
||||
|
||||
use crate::innerlude::FC;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ impl DebugRenderer {
|
|||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ impl DebugRenderer {
|
|||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new_with_props<T: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
|
||||
root_props: T,
|
||||
) -> Self {
|
||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
||||
|
@ -78,7 +78,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn ensure_creation() -> Result<(), ()> {
|
||||
// static Example: FC<()> = |ctx, props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// //
|
||||
// ctx.render(html! { <div> "hello world" </div> })
|
||||
// };
|
||||
|
|
|
@ -65,22 +65,22 @@ pub struct DiffMachine<'a> {
|
|||
}
|
||||
pub enum LifeCycleEvent<'a> {
|
||||
Mount {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
PropsChanged {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
SameProps {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
Replace {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
||||
old_scope: Weak<VCompAssociatedScope>,
|
||||
new_scope: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
|
|
|
@ -48,8 +48,8 @@ mod use_state_def {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
ctx.use_hook(
|
||||
|
@ -169,8 +169,8 @@ mod new_use_state_def {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: F,
|
||||
) -> &'a UseState<T> {
|
||||
ctx.use_hook(
|
||||
|
@ -246,8 +246,8 @@ mod use_ref_def {
|
|||
/// To change it, use modify.
|
||||
/// 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>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_ref<'a, T: 'static, P>(
|
||||
ctx: Context<'a, P>,
|
||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||
) -> &'a RefCell<T> {
|
||||
ctx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
|
||||
|
@ -270,11 +270,11 @@ mod use_reducer_def {
|
|||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
|
||||
ctx: &'c Context<'a>,
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static, P: 'static>(
|
||||
ctx: &'c Context<'a, P>,
|
||||
initial_state_fn: impl FnOnce() -> State,
|
||||
_reducer: impl Fn(&mut State, Action),
|
||||
) -> (&'a State, &'a impl Fn(Action)) {
|
||||
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
|
||||
ctx.use_hook(
|
||||
move || UseReducer {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
|
@ -320,7 +320,7 @@ mod use_reducer_def {
|
|||
}
|
||||
|
||||
// #[allow(unused)]
|
||||
// static Example: FC<()> = |ctx, props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// let (count, reduce) = use_reducer(
|
||||
// &ctx,
|
||||
// || 0,
|
||||
|
@ -347,7 +347,7 @@ mod use_reducer_def {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn use_is_initialized(ctx: Context) -> bool {
|
||||
pub fn use_is_initialized<P>(ctx: Context<P>) -> bool {
|
||||
let val = use_ref(ctx, || false);
|
||||
match *val.borrow() {
|
||||
true => true,
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
//! #[derive(Properties)]
|
||||
//! struct Props { name: String }
|
||||
//!
|
||||
//! fn Example(ctx: Context, props: &Props) -> DomTree {
|
||||
//! html! { <div> "Hello {props.name}!" </div> }
|
||||
//! fn Example(ctx: Context, props: &Props) -> VNode {
|
||||
//! html! { <div> "Hello {ctx.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
|
||||
|
@ -31,8 +31,8 @@
|
|||
//! #[derive(Properties)]
|
||||
//! struct Props { name: String }
|
||||
//!
|
||||
//! static Example: FC<Props> = |ctx, props| {
|
||||
//! html! { <div> "Hello {props.name}!" </div> }
|
||||
//! static Example: FC<Props> = |ctx| {
|
||||
//! html! { <div> "Hello {ctx.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
@ -101,7 +101,7 @@ pub(crate) mod innerlude {
|
|||
pub use crate::patch::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||
|
||||
// Re-export the FC macro
|
||||
pub use crate as dioxus;
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128};
|
|||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{DomTree, Properties, VComponent, FC},
|
||||
innerlude::{Properties, VComponent, FC},
|
||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
||||
prelude::VElement,
|
||||
virtual_dom::NodeCtx,
|
||||
|
@ -477,7 +477,7 @@ where
|
|||
/// .iter_child((0..10).map(|f| span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoDomTree<'a>>) -> Self {
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||
for item in nodes {
|
||||
let child = item.into_vnode(&self.ctx);
|
||||
self.children.push(child);
|
||||
|
@ -486,13 +486,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DomTree {
|
||||
type Item = DomTree;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
// 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>;
|
||||
|
@ -501,23 +501,30 @@ impl<'a> IntoIterator for VNode<'a> {
|
|||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDomTree<'a> for VNode<'a> {
|
||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoDomTree<'a> {
|
||||
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
// cloning is cheap since vnodes are just references into bump arenas
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoVNode<'a> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
|
||||
}
|
||||
|
||||
pub trait DomTreeBuilder<'a, G>: IntoIterator<Item = G>
|
||||
pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
|
||||
where
|
||||
G: IntoDomTree<'a>,
|
||||
G: IntoVNode<'a>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
||||
impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
||||
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
|
||||
{
|
||||
}
|
||||
|
@ -527,15 +534,15 @@ impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
|
|||
// ----
|
||||
// let nodes = ctx.render(rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a> IntoDomTree<'a> for DomTree {
|
||||
fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
self.root
|
||||
}
|
||||
}
|
||||
// impl<'a> IntoVNode<'a> for VNode {
|
||||
// fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
// self.root
|
||||
// }
|
||||
// }
|
||||
|
||||
// Wrap the the node-builder closure in a concrete type.
|
||||
// ---
|
||||
// This is a bit of a hack to implement the IntoDomTree trait for closure types.
|
||||
// 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,
|
||||
|
@ -561,7 +568,7 @@ where
|
|||
// ---
|
||||
// let nodes = rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a, G> IntoDomTree<'a> for LazyNodes<'a, G>
|
||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||
where
|
||||
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
|
||||
{
|
||||
|
@ -581,13 +588,13 @@ where
|
|||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDomTree<'a> for () {
|
||||
impl<'a> IntoVNode<'a> for () {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDomTree<'a> for Option<()> {
|
||||
impl<'a> IntoVNode<'a> for Option<()> {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
|
@ -650,15 +657,16 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
|
|||
Attribute { name, value }
|
||||
}
|
||||
|
||||
pub fn virtual_child<'a, T: Properties + 'a>(
|
||||
pub fn virtual_child<'a, T: Properties>(
|
||||
ctx: &NodeCtx<'a>,
|
||||
f: FC<T>,
|
||||
p: T,
|
||||
props: T,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
) -> VNode<'a> {
|
||||
// currently concerned about if props have a custom drop implementation
|
||||
// might override it with the props macro
|
||||
let bump = &ctx.bump();
|
||||
let propsd: &'a mut _ = bump.alloc(p);
|
||||
VNode::Component(crate::nodes::VComponent::new(f, propsd, key))
|
||||
VNode::Component(
|
||||
ctx.bump()
|
||||
.alloc(crate::nodes::VComponent::new(f, props, key)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,17 +5,10 @@
|
|||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{Context, Properties, ScopeIdx, FC},
|
||||
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// A domtree represents the result of "Viewing" the context
|
||||
/// It's a placeholder over vnodes, to make working with lifetimes easier
|
||||
pub struct DomTree {
|
||||
// this should *never* be publicly accessible to external
|
||||
pub root: VNode<'static>,
|
||||
}
|
||||
use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// Tools for the base unit of the virtual dom - the VNode
|
||||
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
||||
|
@ -35,7 +28,19 @@ pub enum VNode<'src> {
|
|||
Suspended,
|
||||
|
||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
||||
Component(VComponent<'src>),
|
||||
Component(&'src VComponent<'src>),
|
||||
}
|
||||
|
||||
// it's okay to clone because vnodes are just references to places into the bump
|
||||
impl<'a> Clone for VNode<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
VNode::Element(el) => VNode::Element(el),
|
||||
VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
|
||||
VNode::Suspended => VNode::Suspended,
|
||||
VNode::Component(c) => VNode::Component(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
|
@ -196,6 +201,12 @@ pub struct VText<'bump> {
|
|||
pub text: &'bump str,
|
||||
}
|
||||
|
||||
impl<'b> Clone for VText<'b> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { text: self.text }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VText<'a> {
|
||||
// / Create an new `VText` instance with the specified text.
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
|
@ -218,13 +229,14 @@ pub struct VComponent<'src> {
|
|||
pub stable_addr: Rc<StableScopeAddres>,
|
||||
pub ass_scope: Rc<VCompAssociatedScope>,
|
||||
|
||||
pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||
pub caller: Rc<dyn Fn(Context) -> DomTree + 'src>,
|
||||
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||
pub caller: Rc<dyn Fn(&Scope) -> VNode + 'src>,
|
||||
|
||||
pub children: &'src [VNode<'src>],
|
||||
|
||||
// a pointer into the bump arena (given by the 'src lifetime)
|
||||
raw_props: *const (),
|
||||
raw_props: Box<dyn Any>,
|
||||
// raw_props: *const (),
|
||||
|
||||
// a pointer to the raw fn typ
|
||||
pub user_fc: *const (),
|
||||
|
@ -237,28 +249,30 @@ impl<'a> VComponent<'a> {
|
|||
// - 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 + 'a>(component: FC<P>, props: &'a P, key: Option<&'a str>) -> Self {
|
||||
pub fn new<P: Properties>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
||||
let caller_ref = component as *const ();
|
||||
|
||||
let raw_props = props as *const P as *const ();
|
||||
// let raw_props = props as *const P as *const ();
|
||||
|
||||
let props_comparator = move |other: &VComponent| {
|
||||
// Safety:
|
||||
// We are guaranteed that the props will be of the same type because
|
||||
// there is no way to create a VComponent other than this `new` method.
|
||||
//
|
||||
// Therefore, if the render functions are identical (by address), then so will be
|
||||
// props type paramter (because it is the same render function). Therefore, we can be
|
||||
// sure
|
||||
if caller_ref == other.user_fc {
|
||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
real_other == props
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
// let props_comparator = move |other: &VComponent| {
|
||||
// // Safety:
|
||||
// // We are guaranteed that the props will be of the same type because
|
||||
// // there is no way to create a VComponent other than this `new` method.
|
||||
// //
|
||||
// // Therefore, if the render functions are identical (by address), then so will be
|
||||
// // props type paramter (because it is the same render function). Therefore, we can be
|
||||
// // sure
|
||||
// if caller_ref == other.user_fc {
|
||||
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
||||
// // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
// &props == g
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// };
|
||||
|
||||
let caller = Rc::new(create_closure(component, raw_props));
|
||||
let caller: Rc<dyn Fn(&Scope) -> VNode + 'a> = Rc::new(move |scp| todo!());
|
||||
// let caller = Rc::new(create_closure(component, raw_props));
|
||||
|
||||
let key = match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
|
@ -269,23 +283,27 @@ impl<'a> VComponent<'a> {
|
|||
key,
|
||||
ass_scope: Rc::new(RefCell::new(None)),
|
||||
user_fc: caller_ref,
|
||||
raw_props: props as *const P as *const _,
|
||||
raw_props: Box::new(props),
|
||||
_p: PhantomData,
|
||||
children: &[],
|
||||
caller,
|
||||
comparator: Rc::new(props_comparator),
|
||||
// comparator: Rc::new(props_comparator),
|
||||
stable_addr: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_closure<'a, P: Properties + 'a>(
|
||||
component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> impl for<'r> Fn(Context<'r>) -> DomTree + 'a {
|
||||
move |ctx: Context| -> DomTree {
|
||||
// cast back into the right lifetime
|
||||
let safe_props: &'a P = unsafe { &*(raw_props as *const P) };
|
||||
component(ctx, safe_props)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -28,6 +28,7 @@ use std::{
|
|||
collections::{HashMap, HashSet, VecDeque},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
@ -53,7 +54,7 @@ pub struct VirtualDom {
|
|||
#[doc(hidden)]
|
||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
||||
|
||||
/// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
|
||||
/// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic.
|
||||
///
|
||||
/// Whenver props need to be updated, an Error will be thrown if the new props do not
|
||||
/// match the props used to create the VirtualDom.
|
||||
|
@ -77,11 +78,11 @@ impl VirtualDom {
|
|||
/// ```ignore
|
||||
/// // Directly from a closure
|
||||
///
|
||||
/// let dom = VirtualDom::new(|ctx, _| ctx.render(rsx!{ div {"hello world"} }));
|
||||
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
|
||||
///
|
||||
/// // or pass in...
|
||||
///
|
||||
/// let root = |ctx, _| {
|
||||
/// let root = |ctx| {
|
||||
/// ctx.render(rsx!{
|
||||
/// div {"hello world"}
|
||||
/// })
|
||||
|
@ -90,17 +91,17 @@ impl VirtualDom {
|
|||
///
|
||||
/// // or directly from a fn
|
||||
///
|
||||
/// fn Example(ctx: Context, props: &()) -> DomTree {
|
||||
/// fn Example(ctx: Context<()>) -> VNode {
|
||||
/// ctx.render(rsx!{ div{"hello world"} })
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// ```
|
||||
pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl Fn(Context<()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
/// Start a new VirtualDom instance with a dependent props.
|
||||
/// Start a new VirtualDom instance with a dependent ctx.
|
||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||
///
|
||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||
|
@ -109,11 +110,11 @@ impl VirtualDom {
|
|||
/// ```ignore
|
||||
/// // Directly from a closure
|
||||
///
|
||||
/// let dom = VirtualDom::new(|ctx, props| ctx.render(rsx!{ div {"hello world"} }));
|
||||
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
|
||||
///
|
||||
/// // or pass in...
|
||||
///
|
||||
/// let root = |ctx, props| {
|
||||
/// let root = |ctx| {
|
||||
/// ctx.render(rsx!{
|
||||
/// div {"hello world"}
|
||||
/// })
|
||||
|
@ -122,21 +123,27 @@ impl VirtualDom {
|
|||
///
|
||||
/// // or directly from a fn
|
||||
///
|
||||
/// fn Example(ctx: Context, props: &SomeProps) -> DomTree {
|
||||
/// fn Example(ctx: Context, props: &SomeProps) -> VNode {
|
||||
/// ctx.render(rsx!{ div{"hello world"} })
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// ```
|
||||
pub fn new_with_props<P: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static,
|
||||
root: impl Fn(Context<P>) -> VNode + 'static,
|
||||
root_props: P,
|
||||
) -> Self {
|
||||
let components = ScopeArena::new(Arena::new());
|
||||
|
||||
// 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| root(ctx, &root_props));
|
||||
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
|
||||
todo!()
|
||||
// root(Context {
|
||||
// props: &root_props,
|
||||
// scope: ctx,
|
||||
// })
|
||||
});
|
||||
|
||||
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
|
||||
let caller_ref = Rc::downgrade(&_root_caller);
|
||||
|
@ -396,7 +403,7 @@ impl VirtualDom {
|
|||
stable_scope_addr,
|
||||
..
|
||||
} => {
|
||||
// In this case, the parent made a new DomTree that resulted in the same props for us
|
||||
// In this case, the parent made a new VNode that resulted in the same props for us
|
||||
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
||||
log::debug!("Received the same props");
|
||||
|
||||
|
@ -617,7 +624,8 @@ impl Scope {
|
|||
)
|
||||
}(&self);
|
||||
|
||||
self.frames.cur_frame_mut().head_node = new_head.root;
|
||||
todo!();
|
||||
// self.frames.cur_frame_mut().head_node = new_head;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -676,41 +684,63 @@ impl Scope {
|
|||
///
|
||||
/// fn example(ctx: Context, props: &Props -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.name}" </div>
|
||||
/// <div> "Hello, {ctx.ctx.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 type Context<'src> = &'src Scope;
|
||||
|
||||
impl Scope {
|
||||
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> Context<'src, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> &[VNode] {
|
||||
pub fn children(&self) -> &'src [VNode<'src>] {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn schedule_update(&self) -> impl Fn() -> () {
|
||||
self.event_queue.schedule_update(&self)
|
||||
pub fn schedule_update(&self) -> impl Fn() -> () + 'static {
|
||||
self.scope.event_queue.schedule_update(&self.scope)
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
&'a self,
|
||||
_fut: impl Future<Output = LazyNodes<'a, F>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
// /// Create a suspended component from a future.
|
||||
// ///
|
||||
// /// When the future completes, the component will be renderered
|
||||
// pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
|
||||
// &'a self,
|
||||
// _fut: impl Future<Output = LazyNodes<'a, F>>,
|
||||
// ) -> VNode<'src> {
|
||||
// todo!()
|
||||
// }
|
||||
}
|
||||
|
||||
// ================================================
|
||||
// Render Implementation for Components
|
||||
// ================================================
|
||||
//
|
||||
impl Scope {
|
||||
impl<'src, T> Context<'src, T> {
|
||||
// impl<'scope> Context<'scope> {
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
|
@ -719,7 +749,7 @@ impl Scope {
|
|||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context, props: &()) -> VNode {
|
||||
/// fn Component(ctx: Context<()>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div> "Hello World" </div>};
|
||||
///
|
||||
|
@ -727,31 +757,22 @@ impl Scope {
|
|||
/// ctx.render(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn render<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
|
||||
&'scope self,
|
||||
lazy_nodes: LazyNodes<'scope, F>,
|
||||
) -> DomTree {
|
||||
pub fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
|
||||
self,
|
||||
// &'a/ self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> VNode<'src> {
|
||||
let ctx = NodeCtx {
|
||||
scope_ref: self,
|
||||
scope_ref: self.scope,
|
||||
listener_id: 0.into(),
|
||||
};
|
||||
|
||||
DomTree {
|
||||
root: unsafe {
|
||||
std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render2<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
|
||||
&'scope self,
|
||||
lazy_nodes: LazyNodes<'scope, F>,
|
||||
) -> VNode<'scope> {
|
||||
let ctx = NodeCtx {
|
||||
scope_ref: self,
|
||||
listener_id: 0.into(),
|
||||
};
|
||||
lazy_nodes.into_vnode(&ctx)
|
||||
todo!()
|
||||
// VNode {
|
||||
// root: unsafe {
|
||||
// std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
|
||||
// },
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -762,7 +783,7 @@ impl Scope {
|
|||
// We need to pin the hook so it doesn't move as we initialize the list of hooks
|
||||
type Hook = Pin<Box<dyn std::any::Any>>;
|
||||
|
||||
impl Scope {
|
||||
impl<'src, T> Context<'src, T> {
|
||||
// impl<'scope> Context<'scope> {
|
||||
/// Store a value between renders
|
||||
///
|
||||
|
@ -775,28 +796,28 @@ impl Scope {
|
|||
/// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
|
||||
/// use_hook(
|
||||
/// || Rc::new(RefCell::new(initial_value())),
|
||||
/// |state, _| state.clone(),
|
||||
/// |state| state.clone(),
|
||||
/// |_| {},
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_hook<'scope, InternalHookState: 'static, Output: 'scope>(
|
||||
&'scope self,
|
||||
pub fn use_hook<InternalHookState: 'static, Output: 'src>(
|
||||
&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(&'scope mut InternalHookState) -> Output,
|
||||
runner: impl FnOnce(&'src 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 idx = *self.hookidx.borrow();
|
||||
let idx = *self.scope.hookidx.borrow();
|
||||
|
||||
// Grab out the hook list
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
let mut hooks = self.scope.hooks.borrow_mut();
|
||||
|
||||
// If the idx is the same as the hook length, then we need to add the current hook
|
||||
if idx >= hooks.len() {
|
||||
|
@ -804,7 +825,7 @@ impl Scope {
|
|||
hooks.push(Box::pin(new_state));
|
||||
}
|
||||
|
||||
*self.hookidx.borrow_mut() += 1;
|
||||
*self.scope.hookidx.borrow_mut() += 1;
|
||||
|
||||
let stable_ref = hooks
|
||||
.get_mut(idx)
|
||||
|
@ -831,7 +852,7 @@ Any function prefixed with "use" should not be called conditionally.
|
|||
// ================================================
|
||||
// Context API Implementation for Components
|
||||
// ================================================
|
||||
impl Scope {
|
||||
impl<'src, P> Context<'src, P> {
|
||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
///
|
||||
/// This is a hook, so it may not be called conditionally!
|
||||
|
@ -845,7 +866,7 @@ impl Scope {
|
|||
///
|
||||
///
|
||||
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
|
||||
let mut ctxs = self.shared_contexts.borrow_mut();
|
||||
let mut ctxs = self.scope.shared_contexts.borrow_mut();
|
||||
let ty = TypeId::of::<T>();
|
||||
|
||||
let is_initialized = self.use_hook(
|
||||
|
@ -873,12 +894,12 @@ impl Scope {
|
|||
}
|
||||
|
||||
/// There are hooks going on here!
|
||||
pub fn use_context<'a, T: 'static>(&'a self) -> &'a Rc<T> {
|
||||
pub fn use_context<T: 'static>(&self) -> &'src Rc<T> {
|
||||
self.try_use_context().unwrap()
|
||||
}
|
||||
|
||||
/// Uses a context, storing the cached value around
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<&Rc<T>> {
|
||||
pub fn try_use_context<T: 'static>(&self) -> Result<&'src Rc<T>> {
|
||||
struct UseContextHook<C> {
|
||||
par: Option<Rc<C>>,
|
||||
we: Option<Weak<C>>,
|
||||
|
@ -890,7 +911,7 @@ impl Scope {
|
|||
we: None as Option<Weak<T>>,
|
||||
},
|
||||
move |hook| {
|
||||
let mut scope = Some(self);
|
||||
let mut scope = Some(self.scope);
|
||||
|
||||
if let Some(we) = &hook.we {
|
||||
if let Some(re) = we.upgrade() {
|
||||
|
@ -942,7 +963,7 @@ impl Scope {
|
|||
|
||||
// We actually allocate the properties for components in their parent's properties
|
||||
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
||||
pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
|
||||
pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
|
||||
|
@ -978,7 +999,7 @@ impl PartialOrd for HeightMarker {
|
|||
}
|
||||
|
||||
// NodeCtx is used to build VNodes in the component's memory space.
|
||||
// This struct adds metadata to the final DomTree about listeners, attributes, and children
|
||||
// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
#[derive(Clone)]
|
||||
pub struct NodeCtx<'a> {
|
||||
pub scope_ref: &'a Scope,
|
||||
|
@ -1100,7 +1121,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn simulate() {
|
||||
let dom = VirtualDom::new(|ctx, props| {
|
||||
let dom = VirtualDom::new(|ctx| {
|
||||
//
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
@ -1122,7 +1143,7 @@ mod tests {
|
|||
todo!()
|
||||
}
|
||||
|
||||
let _ = check_send(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
|
||||
let _ = check_sync(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
|
||||
let _ = check_send(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
|
||||
let _ = check_sync(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,43 +3,44 @@
|
|||
This crate provides all the batteries required to build Dioxus apps.
|
||||
|
||||
Included in this crate is:
|
||||
|
||||
- Dioxus core
|
||||
- Essential hooks (use_state, use_ref, use_reducer, etc)
|
||||
- rsx! and html! macros
|
||||
|
||||
You'll still need to pull in a renderer to render the Dioxus VDOM. Any one of:
|
||||
|
||||
- dioxus-web (to run in WASM)
|
||||
- dioxus-ssr (to run on the server or for static sites)
|
||||
- dioxus-webview (to run on the desktop)
|
||||
- dioxus-mobile (to run on iOS/Android)
|
||||
|
||||
|
||||
Make sure dioxus and its renderer share the same major version; the renderers themselves rely on dioxus.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
dioxus = "0.2"
|
||||
dioxus-web = "0.2"
|
||||
dioxus = "0.2"
|
||||
dioxus-web = "0.2"
|
||||
```
|
||||
|
||||
```rust
|
||||
use dioxus::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_web::start(|ctx, _| {
|
||||
dioxus_web::start(|ctx| {
|
||||
rsx!{in ctx, div { "Hello world" }}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Additionally, you'll want to look at other projects for more batteries
|
||||
|
||||
- essential-hooks (use_router, use_storage, use_cache, use_channel)
|
||||
- Recoil.rs or Reducer.rs for state management
|
||||
- 3D renderer (ThreeD), charts (Sciviz), game engine (Bevy)
|
||||
|
||||
|
||||
Extra resources:
|
||||
|
||||
- The guide is available at:
|
||||
- The crate docs are at:
|
||||
- Video tutorials are at:
|
||||
|
|
|
@ -37,7 +37,7 @@ impl SomeState {
|
|||
}
|
||||
}
|
||||
|
||||
pub static ExampleReducer: FC<()> = |ctx, props| {
|
||||
pub static ExampleReducer: FC<()> = |ctx| {
|
||||
let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
|
||||
|
||||
let is_playing = state.is_playing();
|
||||
|
@ -85,7 +85,7 @@ enum KindaState {
|
|||
Erred,
|
||||
}
|
||||
|
||||
static EnumReducer: FC<()> = |ctx, props| {
|
||||
static EnumReducer: FC<()> = |ctx| {
|
||||
let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
|
||||
|
||||
let contents = helper(&ctx);
|
||||
|
@ -126,7 +126,7 @@ static EnumReducer: FC<()> = |ctx, props| {
|
|||
})
|
||||
};
|
||||
|
||||
fn helper(ctx: &Context) -> DomTree {
|
||||
fn helper(ctx: &Context) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use dioxus_ssr::{
|
||||
prelude::*,
|
||||
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent},
|
||||
prelude::{builder::IntoVNode, dioxus::events::on::MouseEvent},
|
||||
TextRenderer,
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,6 @@ fn main() {
|
|||
TextRenderer::new(App);
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -112,8 +112,8 @@ impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
|
|||
|
||||
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
||||
Self {
|
||||
children: props.children,
|
||||
context: Rc::new(props.context),
|
||||
children: ctx.children,
|
||||
context: Rc::new(ctx.context),
|
||||
consumers: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
@ -123,14 +123,14 @@ impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
|
|||
}
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> bool {
|
||||
let should_render = if self.children == props.children {
|
||||
let should_render = if self.children == ctx.children {
|
||||
false
|
||||
} else {
|
||||
self.children = props.children;
|
||||
self.children = ctx.children;
|
||||
true
|
||||
};
|
||||
|
||||
let new_context = Rc::new(props.context);
|
||||
let new_context = Rc::new(ctx.context);
|
||||
if self.context != new_context {
|
||||
self.context = new_context;
|
||||
self.notify_consumers();
|
||||
|
@ -357,10 +357,10 @@ mod tests {
|
|||
log::info!("Render counter {:?}", counter);
|
||||
return html! {
|
||||
<>
|
||||
<div id=props.id.clone()>
|
||||
<div id=ctx.id.clone()>
|
||||
{ format!("total: {}", counter.borrow()) }
|
||||
</div>
|
||||
{ props.children.clone() }
|
||||
{ ctx.children.clone() }
|
||||
</>
|
||||
};
|
||||
}
|
||||
|
@ -384,14 +384,14 @@ mod tests {
|
|||
let ctx = use_context::<Rc<MyContext>>().expect("context not passed down");
|
||||
log::info!("============");
|
||||
log::info!("ctx is {:#?}", ctx);
|
||||
log::info!("magic is {:#?}", props.magic);
|
||||
log::info!("magic is {:#?}", ctx.magic);
|
||||
log::info!("outlet counter is {:#?}", ctx);
|
||||
log::info!("============");
|
||||
|
||||
return html! {
|
||||
<>
|
||||
<div>{ format!("magic: {}\n", props.magic) }</div>
|
||||
<div id=props.id.clone()>
|
||||
<div>{ format!("magic: {}\n", ctx.magic) }</div>
|
||||
<div id=ctx.id.clone()>
|
||||
{ format!("current: {}, total: {}", ctx.0, counter.borrow()) }
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -277,8 +277,8 @@ mod tests {
|
|||
type TProps = FunctionProps;
|
||||
|
||||
fn run(props: &Self::TProps) -> Html {
|
||||
let effect_called = props.effect_called.clone();
|
||||
let destroy_called = props.destroy_called.clone();
|
||||
let effect_called = ctx.effect_called.clone();
|
||||
let destroy_called = ctx.destroy_called.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
effect_called();
|
||||
|
@ -297,7 +297,7 @@ mod tests {
|
|||
if *show {
|
||||
let effect_called: Rc<dyn Fn()> = Rc::new(move || set_show(false));
|
||||
return html! {
|
||||
<UseEffectComponent destroy_called=props.destroy_called.clone() effect_called=effect_called />
|
||||
<UseEffectComponent destroy_called=ctx.destroy_called.clone() effect_called=effect_called />
|
||||
};
|
||||
} else {
|
||||
return html! {
|
||||
|
|
|
@ -48,7 +48,7 @@ use std::{cell::RefCell, rc::Rc};
|
|||
pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
|
||||
use_hook(
|
||||
|| Rc::new(RefCell::new(initial_value())),
|
||||
|state, _| state.clone(),
|
||||
|state| state.clone(),
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ uses the same memoization on top of the use_context API.
|
|||
|
||||
Here's a fully-functional todo app using the use_map API:
|
||||
```rust
|
||||
static TodoList: FC<()> = |ctx, props| {
|
||||
static TodoList: FC<()> = |ctx| {
|
||||
let todos = use_map(ctx, || HashMap::new());
|
||||
let input = use_ref(|| None);
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Dioxus for iOS
|
||||
|
||||
This crate implements a renderer of the Dioxus DomTree to an iOS app
|
||||
This crate implements a renderer of the Dioxus VNode to an iOS app
|
||||
|
|
|
@ -27,7 +27,7 @@ mod client {
|
|||
Ok(dioxus_web::WebsysRenderer::start(APP).await)
|
||||
}
|
||||
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
todo!()
|
||||
// let (selected_stream, set_stream) = use_state(&ctx, || SelectedStream::Football);
|
||||
|
||||
|
@ -102,9 +102,9 @@ mod server {
|
|||
selected_stream: SelectedStream,
|
||||
}
|
||||
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx, props| {
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx| {
|
||||
//
|
||||
let g = match props.selected_stream {
|
||||
let g = match ctx.selected_stream {
|
||||
SelectedStream::Football => ctx.render(rsx! {
|
||||
li {
|
||||
"watch football!"
|
||||
|
|
|
@ -27,7 +27,7 @@ mod client {
|
|||
Ok(dioxus_web::WebsysRenderer::start(APP).await)
|
||||
}
|
||||
|
||||
static APP: FC<()> = |ctx, props| {
|
||||
static APP: FC<()> = |ctx| {
|
||||
todo!()
|
||||
// let (selected_stream, set_stream) = use_state(&ctx, || SelectedStream::Football);
|
||||
|
||||
|
@ -102,9 +102,9 @@ mod server {
|
|||
selected_stream: SelectedStream,
|
||||
}
|
||||
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx, props| {
|
||||
static STREAM_LIST: FC<SreamListProps> = |ctx| {
|
||||
//
|
||||
match props.selected_stream {
|
||||
match ctx.selected_stream {
|
||||
SelectedStream::Football => ctx.render(rsx! {
|
||||
li {
|
||||
"watch football!"
|
||||
|
|
|
@ -21,7 +21,7 @@ This atom of state is initialized with a value of `"Green"`. The atom that is re
|
|||
This is then later used in components like so:
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
// The recoil root must be initialized at the top of the application before any use_recoil hooks
|
||||
recoil::init_recoil_root(&ctx, |_| {});
|
||||
|
||||
|
@ -36,7 +36,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
|||
Atoms are considered "Writable" objects since any consumer may also set the Atom's value with their own:
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let color = recoil::use_read(ctx, Light);
|
||||
let set_color = recoil::use_write(ctx, Light);
|
||||
rsx!{in ctx,
|
||||
|
@ -53,7 +53,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
|||
"Reading" a value with use_read subscribes that component to any changes to that value while "Writing" a value does not. It's a good idea to use `write-only` whenever it makes sense to prevent unnecessary evaluations. Both `read` and `write` may be combined together to provide a `use_state` style hook.
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let (color, set_color) = recoil::use_read_write(ctx, Light);
|
||||
rsx!{in ctx,
|
||||
div {
|
||||
|
@ -125,7 +125,7 @@ const CloudRatings: AtomFamily<&str, i32> = |api| {
|
|||
Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subscribers will only be updated if they too select the same family with the same key and that value is updated. Otherwise, these subscribers will never re-render on an "insert", "remove", or "update" of the collection. You could easily implement this yourself with Atoms, immutable datastructures, and selectors, but our implementation is more efficient and first-class.
|
||||
|
||||
```rust
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let (rating, set_rating) = recoil::use_read_write(ctx, CloudRatings.select("AWS"));
|
||||
rsx!{in ctx,
|
||||
div {
|
||||
|
|
|
@ -28,7 +28,7 @@ impl TitleController {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx| {
|
||||
let title = use_read(ctx, &TITLE);
|
||||
let subtitle = use_read(ctx, &SUBTITLE);
|
||||
let controller = use_recoil_api(ctx, TitleController::new);
|
||||
|
|
|
@ -19,7 +19,7 @@ fn update_title(api: &RecoilApi) {
|
|||
}
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let title = use_read(ctx, &TITLE);
|
||||
let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api));
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const TODOS: EcsModel<u32, TodoModel> = |builder| {};
|
|||
// const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(0).select(k);
|
||||
// const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
// let title = use_recoil_value(ctx, &C_SELECTOR);
|
||||
|
|
|
@ -13,7 +13,7 @@ struct Todo {
|
|||
content: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, move |cfg| {});
|
||||
|
||||
let todos = use_read_family(ctx, &TODOS);
|
||||
|
@ -36,8 +36,8 @@ struct ChildProps {
|
|||
id: Uuid,
|
||||
}
|
||||
|
||||
static Child: FC<ChildProps> = |ctx, props| {
|
||||
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id));
|
||||
static Child: FC<ChildProps> = |ctx| {
|
||||
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&ctx.id));
|
||||
|
||||
rsx! { in ctx,
|
||||
li {
|
||||
|
|
|
@ -12,7 +12,7 @@ struct Todo {
|
|||
contents: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
let todos = use_read(ctx, &TODOS);
|
||||
|
||||
|
@ -28,8 +28,8 @@ struct ChildProps {
|
|||
id: Uuid,
|
||||
}
|
||||
|
||||
static Child: FC<ChildProps> = |ctx, props| {
|
||||
let todo = use_read(ctx, &TODOS).get(&props.id).unwrap();
|
||||
static Child: FC<ChildProps> = |ctx| {
|
||||
let todo = use_read(ctx, &TODOS).get(&ctx.id).unwrap();
|
||||
// let (todo, set_todo) = use_read_write(ctx, &TODOS);
|
||||
|
||||
rsx! { in ctx,
|
||||
|
|
|
@ -3,7 +3,7 @@ use recoil::*;
|
|||
|
||||
const COUNT: Atom<i32> = |_| 0;
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
let (count, set_count) = use_read_write(ctx, &COUNT);
|
||||
|
|
|
@ -17,7 +17,7 @@ const D_SELECTOR: SelectorFamilyBorrowed<i32, i32> = |api, key| -> &i32 {
|
|||
a
|
||||
};
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
|
||||
let title = use_read(ctx, &C_SELECTOR);
|
||||
|
|
|
@ -5,7 +5,7 @@ const A: Atom<i32> = |_| 0;
|
|||
const B: Atom<i32> = |_| 0;
|
||||
const C: Selector<i32> = |api| api.get(&A) + api.get(&B);
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
rsx! { in ctx,
|
||||
div {
|
||||
|
@ -16,12 +16,12 @@ static App: FC<()> = |ctx, _| {
|
|||
}
|
||||
};
|
||||
|
||||
static Banner: FC<()> = |ctx, _| {
|
||||
static Banner: FC<()> = |ctx| {
|
||||
let count = use_read(ctx, &C);
|
||||
ctx.render(rsx! { h1 { "Count: {count}" } })
|
||||
};
|
||||
|
||||
static BtnA: FC<()> = |ctx, _| {
|
||||
static BtnA: FC<()> = |ctx| {
|
||||
let (a, set) = use_read_write(ctx, &A);
|
||||
rsx! { in ctx,
|
||||
div { "a"
|
||||
|
@ -31,7 +31,7 @@ static BtnA: FC<()> = |ctx, _| {
|
|||
}
|
||||
};
|
||||
|
||||
static BtnB: FC<()> = |ctx, _| {
|
||||
static BtnB: FC<()> = |ctx| {
|
||||
let (b, set) = use_read_write(ctx, &B);
|
||||
rsx! { in ctx,
|
||||
div { "b"
|
||||
|
|
|
@ -93,8 +93,8 @@ const ContentCards: SelectorFamily<Uuid, ContentCard> = |api, key| api.on_get_as
|
|||
data
|
||||
})
|
||||
|
||||
static ContentCard: FC<()> = |ctx, props| {
|
||||
let body = async match use_recoil_value()(props.id).await {
|
||||
static ContentCard: FC<()> = |ctx| {
|
||||
let body = async match use_recoil_value()(ctx.id).await {
|
||||
Ok(content) => rsx!{in ctx, p {"{content}"} }
|
||||
Err(e) => rsx!{in ctx, p {"Failed to load"}}
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@ mod traits {
|
|||
|
||||
// Atoms, selectors, and their family variants are readable
|
||||
pub trait Readable<T: AtomValue>: Sized + Copy {
|
||||
fn use_read<'a>(self, ctx: Context<'a>) -> &'a T {
|
||||
fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
|
||||
hooks::use_read(ctx, self)
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ mod traits {
|
|||
// If the atom is currently pending, that future will resolve to pending
|
||||
// If the atom is currently ready, the future will immediately resolve
|
||||
// if the atom switches from ready to pending, the component will re-run, returning a pending future
|
||||
fn use_read_async<'a>(self, ctx: Context<'a>) -> &'a T {
|
||||
fn use_read_async<'a, P>(self, ctx: Context<'a, P>) -> &'a T {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -53,11 +53,11 @@ mod traits {
|
|||
// Only atoms and atom families are writable
|
||||
// Selectors and selector families are not
|
||||
pub trait Writable<T: AtomValue>: Readable<T> + Sized {
|
||||
fn use_read_write<'a>(self, ctx: Context<'a>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
fn use_read_write<'a, P>(self, ctx: Context<'a, P>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn use_write<'a>(self, ctx: Context<'a>) -> &'a Rc<dyn Fn(T)> {
|
||||
fn use_write<'a, P>(self, ctx: Context<'a, P>) -> &'a Rc<dyn Fn(T)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ mod atoms {
|
|||
use super::*;
|
||||
use dioxus_core::prelude::Context;
|
||||
|
||||
fn _test(ctx: Context) {
|
||||
fn _test(ctx: Context<()>) {
|
||||
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
|
||||
|
||||
// ensure that atoms are both read and write
|
||||
|
@ -159,7 +159,7 @@ mod atomfamily {
|
|||
use super::*;
|
||||
const Titles: AtomHashMap<u32, &str> = |map| {};
|
||||
|
||||
fn test(ctx: Context) {
|
||||
fn test(ctx: Context<()>) {
|
||||
let title = Titles.select(&10).use_read(ctx);
|
||||
let t2 = use_read(ctx, &Titles.select(&10));
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ mod hooks {
|
|||
use super::*;
|
||||
use dioxus_core::{hooks::use_ref, prelude::Context};
|
||||
|
||||
pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) {
|
||||
pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
|
||||
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
|
||||
}
|
||||
|
||||
|
@ -381,12 +381,12 @@ mod hooks {
|
|||
///
|
||||
/// You can use this method to create controllers that perform much more complex actions than set/get
|
||||
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
|
||||
pub fn use_recoil_api<'a>(ctx: Context<'a>) -> &Rc<RecoilContext> {
|
||||
pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc<RecoilContext> {
|
||||
ctx.use_context::<RecoilContext>()
|
||||
}
|
||||
|
||||
pub fn use_write<'a, T: AtomValue>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_write<'a, T: AtomValue, P>(
|
||||
ctx: Context<'a, P>,
|
||||
// todo: this shouldn't need to be static
|
||||
writable: impl Writable<T>,
|
||||
) -> &'a Rc<dyn Fn(T)> {
|
||||
|
@ -412,7 +412,10 @@ mod hooks {
|
|||
/// Read the atom and get the Rc directly to the Atom's slot
|
||||
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
|
||||
/// work with as
|
||||
pub fn use_read_raw<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &Rc<T> {
|
||||
pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
readable: impl Readable<T>,
|
||||
) -> &Rc<T> {
|
||||
struct ReadHook<T> {
|
||||
value: Rc<T>,
|
||||
consumer_id: u32,
|
||||
|
@ -446,7 +449,10 @@ mod hooks {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn use_read<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &'a T {
|
||||
pub fn use_read<'a, T: AtomValue, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
readable: impl Readable<T>,
|
||||
) -> &'a T {
|
||||
use_read_raw(ctx, readable).as_ref()
|
||||
}
|
||||
|
||||
|
@ -465,8 +471,8 @@ mod hooks {
|
|||
/// // equivalent to:
|
||||
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
|
||||
/// ```
|
||||
pub fn use_read_write<'a, T: AtomValue + 'static>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
|
||||
ctx: Context<'a, P>,
|
||||
writable: impl Writable<T>,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
(use_read(ctx, writable), use_write(ctx, writable))
|
||||
|
@ -492,8 +498,8 @@ mod hooks {
|
|||
///
|
||||
/// modify_atom(&|a| *a += 1)
|
||||
/// ```
|
||||
pub fn use_modify<'a, T: AtomValue + 'static + Clone>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_modify<'a, T: AtomValue + 'static + Clone, P>(
|
||||
ctx: Context<'a, P>,
|
||||
writable: impl Writable<T>,
|
||||
) -> impl Fn(&dyn Fn()) {
|
||||
|_| {}
|
||||
|
@ -502,8 +508,8 @@ mod hooks {
|
|||
/// Use a family collection directly
|
||||
/// !! Any changes to the family will cause this subscriber to update
|
||||
/// Try not to put this at the very top-level of your app.
|
||||
pub fn use_read_family<'a, K, V>(
|
||||
ctx: Context<'a>,
|
||||
pub fn use_read_family<'a, K, V, P>(
|
||||
ctx: Context<'a, P>,
|
||||
t: &AtomHashMap<K, V>,
|
||||
) -> &'a im_rc::HashMap<K, V> {
|
||||
todo!()
|
||||
|
@ -525,11 +531,11 @@ mod utils {
|
|||
/// This tiny util wraps your main component with the initializer for the recoil root.
|
||||
/// This is useful for small programs and the examples in this crate
|
||||
pub fn RecoilApp<T: 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree,
|
||||
) -> impl for<'a> Fn(Context<'a>, &'a T) -> DomTree {
|
||||
move |ctx, props| {
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode,
|
||||
) -> impl for<'a> Fn(Context<'a, T>) -> VNode {
|
||||
move |ctx| {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
root(ctx, props)
|
||||
root(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Router hook for Dioxus apps
|
||||
|
||||
Dioxus-router provides a use_router hook that returns a different value depending on the route.
|
||||
The router is generic over any value, however it makes sense to return a different set of VNodes
|
||||
Dioxus-router provides a use_router hook that returns a different value depending on the route.
|
||||
The router is generic over any value, however it makes sense to return a different set of VNodes
|
||||
and feed them into the App's return VNodes.
|
||||
|
||||
Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head.
|
||||
|
@ -11,20 +11,20 @@ Here's an example of how to use the router hook:
|
|||
```rust
|
||||
static App: FC<()> = |ctx| {
|
||||
|
||||
// Route returns the associated VNodes
|
||||
// Route returns the associated VNodes
|
||||
// This hook re-fires when the route changes
|
||||
let route = use_router(ctx, |cfg| {
|
||||
cfg.at("/").serve(|ctx| {
|
||||
html!{ <LandingPage /> }
|
||||
});
|
||||
|
||||
|
||||
cfg.at("/shoes/:id").serve(|ctx| {
|
||||
let id: Uuid = ctx.props.parse().unwrap();
|
||||
let id: Uuid = ctx.ctx.parse().unwrap();
|
||||
html!{ <ShoesPage id=id /> }
|
||||
});
|
||||
|
||||
cfg.at("/pants/:id").serve(|ctx| {
|
||||
let id: Uuid = ctx.props.parse().unwrap();
|
||||
let id: Uuid = ctx.ctx.parse().unwrap();
|
||||
html!{ <PantsPage id=id /> }
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,8 @@ struct ExampleProps {
|
|||
initial_name: String,
|
||||
}
|
||||
|
||||
static Example: FC<ExampleProps> = |ctx, props| {
|
||||
let dispaly_name = use_state_new(&ctx, move || props.initial_name.clone());
|
||||
static Example: FC<ExampleProps> = |ctx| {
|
||||
let dispaly_name = use_state_new(&ctx, move || ctx.initial_name.clone());
|
||||
|
||||
let buttons = ["Jack", "Jill", "Bob"].iter().map(|name| {
|
||||
rsx!{
|
||||
|
|
|
@ -61,7 +61,7 @@ impl<T> TextRenderer<T> {
|
|||
todo!()
|
||||
}
|
||||
|
||||
/// Immediately render a DomTree to string
|
||||
/// Immediately render a VNode to string
|
||||
pub fn to_text(root: VNode) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ fn html_render(
|
|||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let mut dom = VirtualDom::new(|ctx, props| {
|
||||
let mut dom = VirtualDom::new(|ctx| {
|
||||
//
|
||||
//
|
||||
//
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::*;
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
h1 {"hello"}
|
||||
|
@ -22,7 +22,7 @@ static App: FC<()> = |ctx, _| {
|
|||
})
|
||||
};
|
||||
|
||||
static C1: FC<()> = |ctx, props| {
|
||||
static C1: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
"numba 1"
|
||||
|
@ -30,7 +30,7 @@ static C1: FC<()> = |ctx, props| {
|
|||
})
|
||||
};
|
||||
|
||||
static C2: FC<()> = |ctx, props| {
|
||||
static C2: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
button {
|
||||
"numba 2"
|
||||
|
|
|
@ -24,7 +24,7 @@ fn main() {
|
|||
#[derive(Debug)]
|
||||
struct CustomContext([&'static str; 3]);
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"]));
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
@ -52,9 +52,9 @@ struct ButtonProps {
|
|||
id: u8,
|
||||
}
|
||||
|
||||
fn CustomButton(ctx: Context, props: &ButtonProps) -> DomTree {
|
||||
fn CustomButton(ctx: Context, props: &ButtonProps) -> VNode {
|
||||
let names = ctx.use_context::<CustomContext>();
|
||||
let name = names.0[props.id as usize];
|
||||
let name = names.0[ctx.id as usize];
|
||||
|
||||
ctx.render(rsx!{
|
||||
button {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||
|
||||
fn main() {
|
||||
|
@ -7,8 +9,8 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
|
||||
}
|
||||
|
||||
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
|
||||
fn CustomA(ctx: Context<()>) -> VNode {
|
||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string() as String);
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
@ -23,46 +25,48 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
|||
onclick: move |_| set_val(val.to_ascii_lowercase())
|
||||
}
|
||||
components::CustomB {
|
||||
val: val.as_ref()
|
||||
val: val.clone()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mod components {
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Props, PartialEq)]
|
||||
pub struct PropsB<'src> {
|
||||
val: &'src str,
|
||||
pub struct PropsB {
|
||||
val: String,
|
||||
}
|
||||
|
||||
pub fn CustomB<'a>(ctx: Context<'a>, props: &'a PropsB<'a>) -> DomTree {
|
||||
let (first, last) = props.val.split_at(3);
|
||||
pub fn CustomB(ctx: Context<PropsB>) -> VNode {
|
||||
let (first, last) = ctx.val.split_at(3);
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "m-8"
|
||||
"CustomB {props.val}"
|
||||
"CustomB {ctx.val}"
|
||||
CustomC {
|
||||
val: first
|
||||
val: first.to_string()
|
||||
}
|
||||
CustomC {
|
||||
val: last
|
||||
val: last.to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Props, PartialEq)]
|
||||
struct PropsC<'src> {
|
||||
val: &'src str,
|
||||
struct PropsC {
|
||||
val: String,
|
||||
}
|
||||
|
||||
fn CustomC<'a>(ctx: Context<'a>, props: &'a PropsC<'a>) -> DomTree {
|
||||
fn CustomC(ctx: Context<PropsC>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "m-8"
|
||||
"CustomC {props.val}"
|
||||
"CustomC {ctx.val}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
main { class: "dark:bg-gray-800 bg-white relative h-screen"
|
||||
NavBar {}
|
||||
|
@ -15,7 +15,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
|||
})
|
||||
}
|
||||
|
||||
fn NavBar(ctx: Context, props: &()) -> DomTree {
|
||||
fn NavBar(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
|
||||
div { class: "container mx-auto px-6 flex items-center justify-between"
|
||||
|
@ -55,7 +55,7 @@ fn NavBar(ctx: Context, props: &()) -> DomTree {
|
|||
})
|
||||
}
|
||||
|
||||
fn Landing(ctx: Context, props: &()) -> DomTree {
|
||||
fn Landing(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
|
||||
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
|
||||
|
|
|
@ -8,7 +8,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
let cansee = use_state_new(&ctx, || false);
|
||||
rsx! { in ctx,
|
||||
div {
|
||||
|
@ -22,7 +22,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
|
|||
}
|
||||
}
|
||||
|
||||
fn Child(ctx: Context, props: &()) -> DomTree {
|
||||
fn Child(ctx: Context<()>) -> VNode {
|
||||
rsx! { in ctx,
|
||||
section { class: "py-6 bg-coolGray-100 text-coolGray-900"
|
||||
div { class: "container mx-auto flex flex-col items-center justify-center p-4 space-y-8 md:p-10 md:px-24 xl:px-48"
|
||||
|
|
|
@ -9,7 +9,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
span {
|
||||
|
|
|
@ -11,7 +11,7 @@ fn main() {
|
|||
}
|
||||
|
||||
// this is a component
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (event, set_event) = use_state(&ctx, || None);
|
||||
|
||||
let handler = move |evt: MouseEvent| {
|
||||
|
@ -48,10 +48,10 @@ struct ExampleProps {
|
|||
name: String
|
||||
}
|
||||
|
||||
static Example2: FC<ExampleProps> = |ctx, props| {
|
||||
static Example2: FC<ExampleProps> = |ctx| {
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
h1 {"hello {props.name}"}
|
||||
h1 {"hello {ctx.name}"}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
let (name, set_name) = use_state(&ctx, || "...?");
|
||||
|
||||
log::debug!("Running component....");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Basic example that renders a simple domtree to the browser.
|
||||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
|
|
@ -25,7 +25,7 @@ pub struct TodoItem {
|
|||
pub contents: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
static App: FC<()> = |ctx| {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
|
@ -143,7 +143,7 @@ static App: FC<()> = |ctx, _| {
|
|||
))
|
||||
};
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
// let reducer = recoil::use_callback(&ctx, || ());
|
||||
// let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
// }
|
||||
|
||||
// fn Example(ctx: Context, props: ()) -> DomTree {
|
||||
// fn Example(ctx: Context, props: ()) -> VNode {
|
||||
// let user_data = use_sql_query(&ctx, USER_DATA_QUERY);
|
||||
|
||||
// ctx.render(rsx! {
|
||||
|
|
|
@ -21,8 +21,8 @@ struct ExampleProps {
|
|||
initial_name: &'static str,
|
||||
}
|
||||
|
||||
static Example: FC<ExampleProps> = |ctx, props| {
|
||||
let name = use_state_new(&ctx, move || props.initial_name);
|
||||
static Example: FC<ExampleProps> = |ctx| {
|
||||
let name = use_state_new(&ctx, move || ctx.initial_name);
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
@ -53,12 +53,12 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> {
|
|||
handler: F
|
||||
}
|
||||
|
||||
fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> DomTree {
|
||||
fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
button {
|
||||
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
||||
onmouseover: {&props.handler}
|
||||
"{props.name}"
|
||||
onmouseover: {&ctx.handler}
|
||||
"{ctx.name}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -74,10 +74,10 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
|
|||
struct PlaceholderProps {
|
||||
val: &'static str
|
||||
}
|
||||
fn Placeholder(ctx: Context, props: &PlaceholderProps) -> DomTree {
|
||||
fn Placeholder(ctx: Context, props: &PlaceholderProps) -> VNode {
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
"child: {props.val}"
|
||||
"child: {ctx.val}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::recoil;
|
|||
use crate::state::{FilterState, TODOS};
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let reducer = recoil::use_callback(&ctx, || ());
|
||||
let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ mod todolist;
|
|||
static APP_STYLE: &'static str = include_str!("./style.css");
|
||||
|
||||
fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
id: "app"
|
||||
|
|
|
@ -7,9 +7,9 @@ pub struct TodoEntryProps {
|
|||
id: uuid::Uuid,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
let todo = use_atom_family(&ctx, &TODOS, props.id);
|
||||
let todo = use_atom_family(&ctx, &TODOS, ctx.id);
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
pub fn TodoList(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
|
||||
let filter = use_atom(&ctx, &FILTER);
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct TodoItem {
|
|||
// =======================
|
||||
// Components
|
||||
// =======================
|
||||
pub fn App(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn App(ctx: Context<()>) -> VNode {
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
id: "app"
|
||||
|
@ -51,7 +51,7 @@ pub fn App(ctx: Context, props: &()) -> DomTree {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn TodoList(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (todos, set_todos) = use_state(&ctx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
|
||||
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||
|
@ -100,17 +100,17 @@ pub struct TodoEntryProps {
|
|||
item: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
// #[inline_props]
|
||||
pub fn TodoEntry(
|
||||
ctx: Context,
|
||||
baller: &impl Fn() -> (),
|
||||
caller: &impl Fn() -> (),
|
||||
todo: &Rc<TodoItem>,
|
||||
) -> DomTree {
|
||||
// pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> DomTree {
|
||||
) -> VNode {
|
||||
// pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
// let todo = &props.item;
|
||||
// let todo = &ctx.item;
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
|
@ -129,7 +129,7 @@ pub fn TodoEntry(
|
|||
))
|
||||
}
|
||||
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let toggles = [
|
||||
("All", "", FilterState::All),
|
||||
("Active", "active", FilterState::Active),
|
||||
|
|
|
@ -72,7 +72,7 @@ impl TodoManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn TodoList(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||
let draft = use_state_new(&ctx, || "".to_string());
|
||||
let todos = use_read(&ctx, &TODO_LIST);
|
||||
let filter = use_read(&ctx, &FILTER);
|
||||
|
@ -116,9 +116,9 @@ pub struct TodoEntryProps {
|
|||
id: Uuid,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
let todo = use_read(&ctx, &TODO_LIST).get(&props.id).unwrap();
|
||||
let todo = use_read(&ctx, &TODO_LIST).get(&ctx.id).unwrap();
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
|
@ -137,7 +137,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn FilterToggles(ctx: Context<()>) -> VNode {
|
||||
let reducer = TodoManager(use_recoil_api(ctx));
|
||||
let items_left = use_read(ctx, &TODOS_LEFT);
|
||||
|
||||
|
@ -178,7 +178,7 @@ pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn Footer(ctx: Context, _props: &()) -> DomTree {
|
||||
pub fn Footer(ctx: Context<()>) -> VNode {
|
||||
rsx! { in ctx,
|
||||
footer {
|
||||
class: "info"
|
||||
|
@ -197,7 +197,7 @@ pub fn Footer(ctx: Context, _props: &()) -> DomTree {
|
|||
|
||||
const APP_STYLE: &'static str = include_str!("./todomvc/style.css");
|
||||
|
||||
fn App(ctx: Context, _props: &()) -> DomTree {
|
||||
fn App(ctx: Context<()>) -> VNode {
|
||||
use_init_recoil_root(ctx, |_| {});
|
||||
rsx! { in ctx,
|
||||
div { id: "app"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! basic example that renders a simple domtree to the page :)
|
||||
//! basic example that renders a simple VNode to the page :)
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
@ -9,7 +9,7 @@ fn main() {
|
|||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-col">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//! --------------
|
||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||
|
||||
use dioxus::prelude::{Context, DomTree, Properties};
|
||||
use dioxus::prelude::{Context, Properties, VNode};
|
||||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
|
@ -33,7 +33,7 @@ impl WebsysRenderer {
|
|||
///
|
||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||
/// Pairs well with the wasm_bindgen async handler
|
||||
pub async fn start(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) {
|
||||
pub async fn start(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) {
|
||||
Self::new(root).run().await.expect("Virtual DOM failed :(");
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ impl WebsysRenderer {
|
|||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
|
||||
pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ impl WebsysRenderer {
|
|||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new_with_props<T: Properties + 'static>(
|
||||
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
|
||||
root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
|
||||
root_props: T,
|
||||
) -> Self {
|
||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
||||
|
@ -178,7 +178,7 @@ mod tests {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Hello!");
|
||||
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx| {
|
||||
todo!()
|
||||
// ctx.render(html! {
|
||||
// <div>
|
||||
|
|
|
@ -147,7 +147,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
fn Component(ctx: Context, props: ()) -> DomTree {
|
||||
fn Component(ctx: Context, props: ()) -> VNode {
|
||||
let user_data = use_sql_query(&ctx, USER_DATA_QUERY);
|
||||
|
||||
ctx.render(rsx! {
|
||||
|
|
|
@ -36,22 +36,22 @@ fn blah() {
|
|||
let handler = dioxus_liveview::new_handler()
|
||||
.from_directory("abc123") // serve a given directory as the root
|
||||
.with_context(|| SomeContext {}) // build out a new context for all of the server-rendered components to share
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props: &ServerRendered| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx: &ServerRendered| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx, props| {
|
||||
.with_route(SERVER_RENDERED_KEY, |ctx| {
|
||||
//
|
||||
})
|
||||
// depend on the framework, build a different type of handler
|
||||
|
|
|
@ -17,7 +17,7 @@ fn main() {
|
|||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
static Example: FC<()> = |ctx| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<svg class="octicon octicon-star v-align-text-bottom"
|
||||
|
@ -36,7 +36,7 @@ static Example: FC<()> = |ctx, _props| {
|
|||
</div>
|
||||
})
|
||||
};
|
||||
// static Example: FC<()> = |ctx, _props| {
|
||||
// static Example: FC<()> = |ctx| {
|
||||
// ctx.render(rsx! {
|
||||
// div {
|
||||
// class: "flex items-center justify-center flex-col"
|
||||
|
|
Loading…
Reference in a new issue