mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 00:17:17 +00:00
88af3e7eff
Hotreload the contents of for loops, if chains, component bodies, props, attributes, and any literals discovered in rsx! Add a TUI renderer to the CLI. Improve the CLI build system to be async and parallel. Refactor RSX to allow partial expansion of expressions. Merge autofmt implementations for consistency. Merge the representation of elements and components under the hood. Add a diagnostics system for rsx for improved error messages. Drop interprocess and move to websockets for communication between the CLI and the server. Assign IDs to nodes and attributes in a stable way to be used in non compiler contexts. Add hotreloading to any body of component/for loop/if chain/etc. --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Liam Mitchell <liamkarlmitchell@gmail.com>
301 lines
9.1 KiB
Rust
301 lines
9.1 KiB
Rust
//! A tour of the rsx! macro
|
|
//! ------------------------
|
|
//!
|
|
//! This example serves as an informal quick reference of all the things that the rsx! macro can do.
|
|
//!
|
|
//! A full in-depth reference guide is available at: https://www.notion.so/rsx-macro-basics-ef6e367dec124f4784e736d91b0d0b19
|
|
//!
|
|
//! ### Elements
|
|
//! - Create any element from its tag
|
|
//! - Accept compile-safe attributes for each tag
|
|
//! - Display documentation for elements
|
|
//! - Arguments instead of String
|
|
//! - Text
|
|
//! - Inline Styles
|
|
//!
|
|
//! ## General Concepts
|
|
//! - Iterators
|
|
//! - Keys
|
|
//! - Match statements
|
|
//! - Conditional Rendering
|
|
//!
|
|
//! ### Events
|
|
//! - Handle events with the "onXYZ" syntax
|
|
//! - Closures can capture their environment with the 'static lifetime
|
|
//!
|
|
//!
|
|
//! ### Components
|
|
//! - Components can be made by specifying the name
|
|
//! - Components can be referenced by path
|
|
//! - Components may have optional parameters
|
|
//! - Components may have their properties specified by spread syntax
|
|
//! - Components may accept child nodes
|
|
//! - Components that accept "onXYZ" get those closures bump allocated
|
|
//!
|
|
//! ### Fragments
|
|
//! - Allow fragments using the built-in `Fragment` component
|
|
//! - Accept a list of vnodes as children for a Fragment component
|
|
//! - Allow keyed fragments in iterators
|
|
//! - Allow top-level fragments
|
|
|
|
fn main() {
|
|
launch(app)
|
|
}
|
|
|
|
use core::{fmt, str::FromStr};
|
|
use std::fmt::Display;
|
|
|
|
use baller::Baller;
|
|
use dioxus::prelude::*;
|
|
|
|
fn app() -> Element {
|
|
let formatting = "formatting!";
|
|
let formatting_tuple = ("a", "b");
|
|
let lazy_fmt = format_args!("lazily formatted text");
|
|
let asd = 123;
|
|
|
|
rsx! {
|
|
div {
|
|
// Elements
|
|
div {}
|
|
h1 {"Some text"}
|
|
h1 {"Some text with {formatting}"}
|
|
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
|
|
h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
|
|
h2 {
|
|
"Multiple"
|
|
"Text"
|
|
"Blocks"
|
|
"Use comments as separators in html"
|
|
}
|
|
div {
|
|
h1 {"multiple"}
|
|
h2 {"nested"}
|
|
h3 {"elements"}
|
|
}
|
|
div {
|
|
class: "my special div",
|
|
h1 {"Headers and attributes!"}
|
|
}
|
|
div {
|
|
// pass simple rust expressions in
|
|
class: "{lazy_fmt}",
|
|
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
|
class: "asd",
|
|
class: "{asd}",
|
|
// if statements can be used to conditionally render attributes
|
|
class: if formatting.contains("form") { "{asd}" },
|
|
div {
|
|
class: {
|
|
const WORD: &str = "expressions";
|
|
format_args!("Arguments can be passed in through curly braces for complex {WORD}")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expressions can be used in element position too:
|
|
{rsx!(p { "More templating!" })}
|
|
|
|
// Iterators
|
|
{(0..10).map(|i| rsx!(li { "{i}" }))}
|
|
|
|
// Iterators within expressions
|
|
{
|
|
let data = std::collections::HashMap::<&'static str, &'static str>::new();
|
|
// Iterators *should* have keys when you can provide them.
|
|
// Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
|
|
// Using an "ID" associated with your data is a good idea.
|
|
data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
|
|
}
|
|
|
|
// Matching
|
|
match true {
|
|
true => rsx!( h1 {"Top text"}),
|
|
false => rsx!( h1 {"Bottom text"})
|
|
}
|
|
|
|
// Conditional rendering
|
|
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
|
|
// You can convert a bool condition to rsx! with .then and .or
|
|
{true.then(|| rsx!(div {}))}
|
|
|
|
// Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
|
if false {
|
|
h1 {"Top text"}
|
|
} else {
|
|
h1 {"Bottom text"}
|
|
}
|
|
|
|
// Using optionals for diverging branches
|
|
// Note that since this is wrapped in curlies, it's interpreted as an expression
|
|
{if true {
|
|
Some(rsx!(h1 {"Top text"}))
|
|
} else {
|
|
None
|
|
}}
|
|
|
|
// returning "None" without a diverging branch is a bit noisy... but rare in practice
|
|
{None as Option<()>}
|
|
|
|
// can also just use empty fragments
|
|
Fragment {}
|
|
|
|
// Fragments let you insert groups of nodes without a parent.
|
|
// This lets you make components that insert elements as siblings without a container.
|
|
div {"A"}
|
|
Fragment {
|
|
div {"B"}
|
|
div {"C"}
|
|
Fragment {
|
|
"D"
|
|
Fragment {
|
|
"E"
|
|
"F"
|
|
}
|
|
}
|
|
}
|
|
|
|
// Components
|
|
// Can accept any paths
|
|
// Notice how you still get syntax highlighting and IDE support :)
|
|
Baller {}
|
|
baller::Baller {}
|
|
crate::baller::Baller {}
|
|
|
|
// Can take properties
|
|
Taller { a: "asd" }
|
|
|
|
// Can take optional properties
|
|
Taller { a: "asd" }
|
|
|
|
// Can pass in props directly as an expression
|
|
{
|
|
let props = TallerProps {a: "hello", children: VNode::empty() };
|
|
rsx!(Taller { ..props })
|
|
}
|
|
|
|
// Spreading can also be overridden manually
|
|
Taller {
|
|
a: "not ballin!",
|
|
..TallerProps { a: "ballin!", children: VNode::empty() }
|
|
}
|
|
|
|
// Can take children too!
|
|
Taller { a: "asd", div {"hello world!"} }
|
|
|
|
// This component's props are defined *inline* with the `component` macro
|
|
WithInline { text: "using functionc all syntax" }
|
|
|
|
// Components can be generic too
|
|
// This component takes i32 type to give you typed input
|
|
TypedInput::<i32> {}
|
|
|
|
// Type inference can be used too
|
|
TypedInput { initial: 10.0 }
|
|
|
|
// generic with the `component` macro
|
|
Label { text: "hello generic world!" }
|
|
Label { text: 99.9 }
|
|
|
|
// Lowercase components work too, as long as they are access using a path
|
|
baller::lowercase_component {}
|
|
|
|
// For in-scope lowercase components, use the `self` keyword
|
|
self::lowercase_helper {}
|
|
|
|
// helper functions
|
|
// Anything that implements IntoVnode can be dropped directly into Rsx
|
|
{helper("hello world!")}
|
|
|
|
// Strings can be supplied directly
|
|
{String::from("Hello world!")}
|
|
|
|
// So can format_args
|
|
{format_args!("Hello {}!", "world")}
|
|
|
|
// Or we can shell out to a helper function
|
|
{format_dollars(10, 50)}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_dollars(dollars: u32, cents: u32) -> String {
|
|
format!("${dollars}.{cents:02}")
|
|
}
|
|
|
|
fn helper(text: &str) -> Element {
|
|
rsx! {
|
|
p { "{text}" }
|
|
}
|
|
}
|
|
|
|
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
|
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
|
// something like Clippy.
|
|
#[component(no_case_check)]
|
|
fn lowercase_helper() -> Element {
|
|
rsx! {
|
|
"asd"
|
|
}
|
|
}
|
|
|
|
mod baller {
|
|
use super::*;
|
|
|
|
#[component]
|
|
/// This component totally balls
|
|
pub fn Baller() -> Element {
|
|
rsx! { "ballin'" }
|
|
}
|
|
|
|
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
|
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
|
// something like Clippy.
|
|
#[component(no_case_check)]
|
|
pub fn lowercase_component() -> Element {
|
|
rsx! { "look ma, no uppercase" }
|
|
}
|
|
}
|
|
|
|
/// Documention for this component is visible within the rsx macro
|
|
#[component]
|
|
pub fn Taller(
|
|
/// Fields are documented and accessible in rsx!
|
|
a: &'static str,
|
|
children: Element,
|
|
) -> Element {
|
|
rsx! { {&children} }
|
|
}
|
|
|
|
#[derive(Props, Clone, PartialEq, Eq)]
|
|
pub struct TypedInputProps<T: 'static + Clone + PartialEq> {
|
|
#[props(optional, default)]
|
|
initial: Option<T>,
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn TypedInput<T>(props: TypedInputProps<T>) -> Element
|
|
where
|
|
T: FromStr + fmt::Display + PartialEq + Clone + 'static,
|
|
<T as FromStr>::Err: std::fmt::Display,
|
|
{
|
|
if let Some(props) = props.initial {
|
|
return rsx! { "{props}" };
|
|
}
|
|
|
|
VNode::empty()
|
|
}
|
|
|
|
#[component]
|
|
fn WithInline(text: String) -> Element {
|
|
rsx! {
|
|
p { "{text}" }
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn Label<T: Clone + PartialEq + Display + 'static>(text: T) -> Element {
|
|
rsx! {
|
|
p { "{text}" }
|
|
}
|
|
}
|