wip: docs
Worked a bit on adding more examples. Trying out a new "antipattern" example to show how *not* to use Dioxus.
This commit is contained in:
1 parent
9d7ee79826
commit
f5683a2346
9 files changed
+197
-174
No files matched your search
@@ -0,0 +1,62 @@
|
|||||||
|
//! Example: Antipatterns
|
||||||
|
//! ---------------------
|
||||||
|
//!
|
||||||
|
//! This example shows what *not* to do and provides a reason why a given pattern is considered an "AntiPattern". Most
|
||||||
|
//! anti-patterns are considered wrong to due performance reasons or violate the "rules" of Dioxus. These rules are
|
||||||
|
//! borrowed from other successful UI frameworks, and Dioxus is more focused on providing a familiar, ergonomic interface
|
||||||
|
//! rather than building new harder-to-misuse patterns.
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
/// Antipattern: Iterators without keys
|
||||||
|
/// -----------------------------------
|
||||||
|
///
|
||||||
|
/// This is considered an anti-pattern for performance reasons. Dioxus must diff your current and old layout and must
|
||||||
|
/// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the
|
||||||
|
/// "slow" path, so you're strongly encouraged to provide a unique ID stable between renders.
|
||||||
|
///
|
||||||
|
/// Dioxus will log an error in the console if it detects that your iterator does not properly generate keys
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
struct NoKeysProps {
|
||||||
|
data: HashMap<u32, String>,
|
||||||
|
}
|
||||||
|
static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
|
||||||
|
// WRONG: Make sure to add keys!
|
||||||
|
rsx!(in cx, ul {
|
||||||
|
{cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
|
||||||
|
});
|
||||||
|
// Like this:
|
||||||
|
rsx!(in cx, ul {
|
||||||
|
{cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Antipattern: Deeply nested fragments
|
||||||
|
/// ------------------------------------
|
||||||
|
///
|
||||||
|
/// This particular antipattern is not necessarily an antipattern in other frameworks but does has a performance impact
|
||||||
|
/// in Dioxus apps. Fragments don't mount a physical element to the dom immediately, so Dioxus must recurse into its
|
||||||
|
/// children to find a physical dom node. This process is called "normalization". Other frameworks perform an agressive
|
||||||
|
/// mutative normalization while Dioxus keeps your VNodes immutable. This means that deepely nested fragments make Dioxus
|
||||||
|
/// perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true dom element.
|
||||||
|
///
|
||||||
|
/// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing
|
||||||
|
/// an API for registering shared state without the ContextProvider pattern.
|
||||||
|
static Blah: FC<()> = |cx| {
|
||||||
|
// Try to avoid
|
||||||
|
rsx!(in cx,
|
||||||
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
div { "Finally have a real node!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
-143
@@ -1,143 +0,0 @@
|
|||||||
#![allow(unused)]
|
|
||||||
/*
|
|
||||||
This example shows how to encapsulate sate in dioxus components with the reducer pattern.
|
|
||||||
This pattern is very useful when a single component can handle many types of input that can
|
|
||||||
be represented by an enum. This particular pattern is very powerful in rust where ADTs can simplify
|
|
||||||
much of the traditional reducer boilerplate.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
enum Actions {
|
|
||||||
Pause,
|
|
||||||
Play,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SomeState {
|
|
||||||
is_playing: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SomeState {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self { is_playing: false }
|
|
||||||
}
|
|
||||||
fn reduce(&mut self, action: Actions) {
|
|
||||||
match action {
|
|
||||||
Actions::Pause => self.is_playing = false,
|
|
||||||
Actions::Play => self.is_playing = true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn is_playing(&self) -> &'static str {
|
|
||||||
match self.is_playing {
|
|
||||||
true => "currently playing!",
|
|
||||||
false => "not currently playing",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static ExampleReducer: FC<()> = |ctx| {
|
|
||||||
let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
|
|
||||||
|
|
||||||
let is_playing = state.is_playing();
|
|
||||||
|
|
||||||
ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
h1 {"Select an option"}
|
|
||||||
h3 {"The radio is... {is_playing}!"}
|
|
||||||
button {
|
|
||||||
"Pause"
|
|
||||||
onclick: move |_| reduce(Actions::Pause)
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
"Play"
|
|
||||||
onclick: move |_| reduce(Actions::Play)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct AppContext {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KindaState {
|
|
||||||
Ready,
|
|
||||||
Complete,
|
|
||||||
Erred,
|
|
||||||
}
|
|
||||||
|
|
||||||
static EnumReducer: FC<()> = |ctx| {
|
|
||||||
let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
|
|
||||||
|
|
||||||
let status = match state {
|
|
||||||
KindaState::Ready => "Ready",
|
|
||||||
KindaState::Complete => "Complete",
|
|
||||||
KindaState::Erred => "Erred",
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
h1 {"{status}"}
|
|
||||||
button {
|
|
||||||
"Set Ready"
|
|
||||||
onclick: move |_| reduce(KindaState::Ready)
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
"Set Complete"
|
|
||||||
onclick: move |_| reduce(KindaState::Complete)
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
"Set Erred"
|
|
||||||
onclick: move |_| reduce(KindaState::Erred)
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
{(0..10).map(|f| {
|
|
||||||
|
|
||||||
rsx!{
|
|
||||||
li {
|
|
||||||
"hello there!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Demonstrate how the DebugRenderer can be used to unit test components without needing a browser
|
|
||||||
/// These tests can run locally.
|
|
||||||
/// They use the "compare" method of the debug renderer to do partial tree compares for succint
|
|
||||||
#[test]
|
|
||||||
fn ensure_it_works_properly() -> dioxus::error::Result<()> {
|
|
||||||
let mut test = DebugRenderer::new(EnumReducer);
|
|
||||||
test.compare(rsx! { div { h1 {"Ready"} } })?;
|
|
||||||
|
|
||||||
test.trigger_listener(1)?;
|
|
||||||
test.compare(rsx! { div { h1 {"Ready"} } })?;
|
|
||||||
|
|
||||||
test.trigger_listener(2)?;
|
|
||||||
test.compare(rsx! { div { h1 {"Complete"} } })?;
|
|
||||||
|
|
||||||
test.trigger_listener(3)?;
|
|
||||||
test.compare(rsx! { div { h1 {"Erred"} } })?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//! Example: Reducer Pattern
|
||||||
|
//! -----------------
|
||||||
|
//! This example shows how to encapsulate sate in dioxus components with the reducer pattern.
|
||||||
|
//! This pattern is very useful when a single component can handle many types of input that can
|
||||||
|
//! be represented by an enum.
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
pub static ExampleReducer: FC<()> = |ctx| {
|
||||||
|
let (state, reduce) = use_reducer(&ctx, PlayerState::new, PlayerState::reduce);
|
||||||
|
|
||||||
|
let is_playing = state.is_playing();
|
||||||
|
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div {
|
||||||
|
h1 {"Select an option"}
|
||||||
|
h3 {"The radio is... {is_playing}!"}
|
||||||
|
button {
|
||||||
|
"Pause"
|
||||||
|
onclick: move |_| reduce(PlayerAction::Pause)
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
"Play"
|
||||||
|
onclick: move |_| reduce(PlayerAction::Play)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PlayerAction {
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlayerState {
|
||||||
|
is_playing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { is_playing: false }
|
||||||
|
}
|
||||||
|
fn reduce(&mut self, action: PlayerAction) {
|
||||||
|
match action {
|
||||||
|
PlayerAction::Pause => self.is_playing = false,
|
||||||
|
PlayerAction::Play => self.is_playing = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_playing(&self) -> &'static str {
|
||||||
|
match self.is_playing {
|
||||||
|
true => "currently playing!",
|
||||||
|
false => "not currently playing",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,18 +2,21 @@ fn main() {}
|
|||||||
|
|
||||||
pub mod dioxus {
|
pub mod dioxus {
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub unsafe trait Properties {
|
pub trait Properties {
|
||||||
type Builder;
|
type Builder;
|
||||||
const CAN_BE_MEMOIZED: bool;
|
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(dioxus_core_macro::Props)]
|
|
||||||
|
/// This implementation should require a "PartialEq" because it memoizes (no external references)
|
||||||
|
#[derive(PartialEq, dioxus_core_macro::Props)]
|
||||||
struct SomeProps {
|
struct SomeProps {
|
||||||
a: String,
|
a: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This implementation does not require a "PartialEq" because it does not memoize
|
||||||
#[derive(dioxus_core_macro::Props)]
|
#[derive(dioxus_core_macro::Props)]
|
||||||
struct SomePropsTwo<'a> {
|
struct SomePropsTwo<'a> {
|
||||||
a: &'a str,
|
a: &'a str,
|
||||||
|
|||||||
@@ -44,11 +44,21 @@ pub enum VirtualEvent {
|
|||||||
MouseEvent(Rc<dyn on::MouseEvent>),
|
MouseEvent(Rc<dyn on::MouseEvent>),
|
||||||
PointerEvent(Rc<dyn on::PointerEvent>),
|
PointerEvent(Rc<dyn on::PointerEvent>),
|
||||||
|
|
||||||
|
// image event has conflicting method types
|
||||||
// ImageEvent(event_data::ImageEvent),
|
// ImageEvent(event_data::ImageEvent),
|
||||||
OtherEvent,
|
OtherEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod on {
|
pub mod on {
|
||||||
|
//! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer
|
||||||
|
//! will implement the same events and same behavior (bubbling, cancelation, etc).
|
||||||
|
//!
|
||||||
|
//! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
|
||||||
|
//! Arc allocation through "get_mut"
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use std::{fmt::Debug, ops::Deref, rc::Rc};
|
use std::{fmt::Debug, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
@@ -178,8 +188,8 @@ pub mod on {
|
|||||||
|
|
||||||
pub trait MouseEvent: Debug {
|
pub trait MouseEvent: Debug {
|
||||||
fn alt_key(&self) -> bool;
|
fn alt_key(&self) -> bool;
|
||||||
fn button(&self) -> i32;
|
fn button(&self) -> i16;
|
||||||
fn buttons(&self) -> i32;
|
fn buttons(&self) -> u16;
|
||||||
fn client_x(&self) -> i32;
|
fn client_x(&self) -> i32;
|
||||||
fn client_y(&self) -> i32;
|
fn client_y(&self) -> i32;
|
||||||
fn ctrl_key(&self) -> bool;
|
fn ctrl_key(&self) -> bool;
|
||||||
@@ -189,7 +199,7 @@ pub mod on {
|
|||||||
fn screen_x(&self) -> i32;
|
fn screen_x(&self) -> i32;
|
||||||
fn screen_y(&self) -> i32;
|
fn screen_y(&self) -> i32;
|
||||||
fn shift_key(&self) -> bool;
|
fn shift_key(&self) -> bool;
|
||||||
fn get_modifier_state(&self, key_code: usize) -> bool;
|
fn get_modifier_state(&self, key_code: &str) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PointerEvent: Debug {
|
pub trait PointerEvent: Debug {
|
||||||
|
|||||||
@@ -504,9 +504,9 @@ where
|
|||||||
let child = item.into_vnode(&self.ctx);
|
let child = item.into_vnode(&self.ctx);
|
||||||
self.children.push(child);
|
self.children.push(child);
|
||||||
}
|
}
|
||||||
if self.children.len() > len_before + 1 {
|
if cfg!(debug_assertions) {
|
||||||
if self.children.last().unwrap().key().is_none() {
|
if self.children.len() > len_before + 1 {
|
||||||
if cfg!(debug_assertions) {
|
if self.children.last().unwrap().key().is_none() {
|
||||||
log::error!(
|
log::error!(
|
||||||
r#"
|
r#"
|
||||||
Warning: Each child in an array or iterator should have a unique "key" prop.
|
Warning: Each child in an array or iterator should have a unique "key" prop.
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! POC: Planning the layout of a single element type
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! The ultimate goal with a dedicated namespace is three-fold:
|
||||||
|
//! - compile-time correct templates preventing misuse of elemnents
|
||||||
|
//! - deep integration of DSL with IDE
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
struct NodeCtx {}
|
||||||
|
|
||||||
|
struct div<'a>(&NodeCtx);
|
||||||
|
impl<'a> div<'a> {
|
||||||
|
fn new(cx: &NodeCtx) -> Self {
|
||||||
|
div(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn factory(
|
||||||
|
// this is your mom
|
||||||
|
cx: &NodeCtx,
|
||||||
|
) {
|
||||||
|
div::new(cx);
|
||||||
|
rsx! {
|
||||||
|
div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-16
@@ -480,43 +480,46 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|||||||
pub struct CustomMouseEvent(web_sys::MouseEvent);
|
pub struct CustomMouseEvent(web_sys::MouseEvent);
|
||||||
impl dioxus_core::events::on::MouseEvent for CustomMouseEvent {
|
impl dioxus_core::events::on::MouseEvent for CustomMouseEvent {
|
||||||
fn alt_key(&self) -> bool {
|
fn alt_key(&self) -> bool {
|
||||||
todo!()
|
self.0.alt_key()
|
||||||
}
|
}
|
||||||
fn button(&self) -> i32 {
|
fn button(&self) -> i16 {
|
||||||
todo!()
|
self.0.button()
|
||||||
}
|
}
|
||||||
fn buttons(&self) -> i32 {
|
fn buttons(&self) -> u16 {
|
||||||
todo!()
|
self.0.buttons()
|
||||||
}
|
}
|
||||||
fn client_x(&self) -> i32 {
|
fn client_x(&self) -> i32 {
|
||||||
todo!()
|
self.0.client_x()
|
||||||
}
|
}
|
||||||
fn client_y(&self) -> i32 {
|
fn client_y(&self) -> i32 {
|
||||||
todo!()
|
self.0.client_y()
|
||||||
}
|
}
|
||||||
fn ctrl_key(&self) -> bool {
|
fn ctrl_key(&self) -> bool {
|
||||||
todo!()
|
self.0.ctrl_key()
|
||||||
}
|
}
|
||||||
fn meta_key(&self) -> bool {
|
fn meta_key(&self) -> bool {
|
||||||
todo!()
|
self.0.meta_key()
|
||||||
}
|
}
|
||||||
fn page_x(&self) -> i32 {
|
fn page_x(&self) -> i32 {
|
||||||
todo!()
|
self.0.page_x()
|
||||||
}
|
}
|
||||||
fn page_y(&self) -> i32 {
|
fn page_y(&self) -> i32 {
|
||||||
todo!()
|
self.0.page_y()
|
||||||
}
|
}
|
||||||
fn screen_x(&self) -> i32 {
|
fn screen_x(&self) -> i32 {
|
||||||
todo!()
|
self.0.screen_x()
|
||||||
}
|
}
|
||||||
fn screen_y(&self) -> i32 {
|
fn screen_y(&self) -> i32 {
|
||||||
todo!()
|
self.0.screen_y()
|
||||||
}
|
}
|
||||||
fn shift_key(&self) -> bool {
|
fn shift_key(&self) -> bool {
|
||||||
todo!()
|
self.0.shift_key()
|
||||||
}
|
}
|
||||||
fn get_modifier_state(&self, key_code: usize) -> bool {
|
|
||||||
todo!()
|
// yikes
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||||
|
fn get_modifier_state(&self, key_code: &str) -> bool {
|
||||||
|
self.0.get_modifier_state(key_code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VirtualEvent::MouseEvent(Rc::new(CustomMouseEvent(evt)))
|
VirtualEvent::MouseEvent(Rc::new(CustomMouseEvent(evt)))
|
||||||
|
|||||||
+8
-6
@@ -182,12 +182,14 @@ pub mod prelude {
|
|||||||
pub use dioxus_core::prelude::*;
|
pub use dioxus_core::prelude::*;
|
||||||
pub use dioxus_core_macro::fc;
|
pub use dioxus_core_macro::fc;
|
||||||
}
|
}
|
||||||
pub mod builder {
|
// pub mod builder {
|
||||||
// pub use dioxus_core::builder::*;
|
// // pub use dioxus_core::builder::*;
|
||||||
}
|
// }
|
||||||
pub mod events {
|
pub use dioxus_core::builder;
|
||||||
// pub use dioxus_core::events::*;
|
pub use dioxus_core::events;
|
||||||
}
|
// pub mod events {
|
||||||
|
// // pub use dioxus_core::events::*;
|
||||||
|
// }
|
||||||
// Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
|
// Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
|
||||||
// together and exports their namespaces to something predicatble.
|
// together and exports their namespaces to something predicatble.
|
||||||
#[cfg(feature = "core")]
|
#[cfg(feature = "core")]
|
||||||
|
|||||||
Reference in new issue
Block a user