From f5683a23464992ecace463a61414795b5a2c58c8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 23 Jun 2021 22:32:54 -0400 Subject: [PATCH] wip: docs Worked a bit on adding more examples. Trying out a new "antipattern" example to show how *not* to use Dioxus. --- examples/antipatterns.rs | 62 ++++++++++ examples/app.rs | 143 ---------------------- examples/reducer.rs | 56 +++++++++ packages/core-macro/examples/prop_test.rs | 9 +- packages/core/src/events.rs | 16 ++- packages/core/src/nodebuilder.rs | 6 +- packages/html-namespace/examples/poc.rs | 30 +++++ packages/web/src/new.rs | 35 +++--- src/lib.rs | 14 ++- 9 files changed, 197 insertions(+), 174 deletions(-) create mode 100644 examples/antipatterns.rs delete mode 100644 examples/app.rs create mode 100644 examples/reducer.rs create mode 100644 packages/html-namespace/examples/poc.rs diff --git a/examples/antipatterns.rs b/examples/antipatterns.rs new file mode 100644 index 000000000..69562bbde --- /dev/null +++ b/examples/antipatterns.rs @@ -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, +} +static AntipatternNoKeys: FC = |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!" } + } + } + } + } + } + ) +}; diff --git a/examples/app.rs b/examples/app.rs deleted file mode 100644 index 7c4bb63f6..000000000 --- a/examples/app.rs +++ /dev/null @@ -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(()) -} diff --git a/examples/reducer.rs b/examples/reducer.rs new file mode 100644 index 000000000..beafdfe19 --- /dev/null +++ b/examples/reducer.rs @@ -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", + } + } +} diff --git a/packages/core-macro/examples/prop_test.rs b/packages/core-macro/examples/prop_test.rs index da4d7e961..9a38fd604 100644 --- a/packages/core-macro/examples/prop_test.rs +++ b/packages/core-macro/examples/prop_test.rs @@ -2,18 +2,21 @@ fn main() {} pub mod dioxus { pub mod prelude { - pub unsafe trait Properties { + pub trait Properties { type Builder; - const CAN_BE_MEMOIZED: bool; 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 { a: String, } +/// This implementation does not require a "PartialEq" because it does not memoize #[derive(dioxus_core_macro::Props)] struct SomePropsTwo<'a> { a: &'a str, diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 1f400c41b..f1f0ed4b4 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -44,11 +44,21 @@ pub enum VirtualEvent { MouseEvent(Rc), PointerEvent(Rc), + // image event has conflicting method types // ImageEvent(event_data::ImageEvent), OtherEvent, } 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)] use std::{fmt::Debug, ops::Deref, rc::Rc}; @@ -178,8 +188,8 @@ pub mod on { pub trait MouseEvent: Debug { fn alt_key(&self) -> bool; - fn button(&self) -> i32; - fn buttons(&self) -> i32; + fn button(&self) -> i16; + fn buttons(&self) -> u16; fn client_x(&self) -> i32; fn client_y(&self) -> i32; fn ctrl_key(&self) -> bool; @@ -189,7 +199,7 @@ pub mod on { fn screen_x(&self) -> i32; fn screen_y(&self) -> i32; 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 { diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index c3ea6e93a..6b526f37b 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -504,9 +504,9 @@ where let child = item.into_vnode(&self.ctx); self.children.push(child); } - if self.children.len() > len_before + 1 { - if self.children.last().unwrap().key().is_none() { - if cfg!(debug_assertions) { + if cfg!(debug_assertions) { + if self.children.len() > len_before + 1 { + if self.children.last().unwrap().key().is_none() { log::error!( r#" Warning: Each child in an array or iterator should have a unique "key" prop. diff --git a/packages/html-namespace/examples/poc.rs b/packages/html-namespace/examples/poc.rs new file mode 100644 index 000000000..acd882e1d --- /dev/null +++ b/packages/html-namespace/examples/poc.rs @@ -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 {} + } +} diff --git a/packages/web/src/new.rs b/packages/web/src/new.rs index 5c79dea25..b4c874002 100644 --- a/packages/web/src/new.rs +++ b/packages/web/src/new.rs @@ -480,43 +480,46 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { pub struct CustomMouseEvent(web_sys::MouseEvent); impl dioxus_core::events::on::MouseEvent for CustomMouseEvent { fn alt_key(&self) -> bool { - todo!() + self.0.alt_key() } - fn button(&self) -> i32 { - todo!() + fn button(&self) -> i16 { + self.0.button() } - fn buttons(&self) -> i32 { - todo!() + fn buttons(&self) -> u16 { + self.0.buttons() } fn client_x(&self) -> i32 { - todo!() + self.0.client_x() } fn client_y(&self) -> i32 { - todo!() + self.0.client_y() } fn ctrl_key(&self) -> bool { - todo!() + self.0.ctrl_key() } fn meta_key(&self) -> bool { - todo!() + self.0.meta_key() } fn page_x(&self) -> i32 { - todo!() + self.0.page_x() } fn page_y(&self) -> i32 { - todo!() + self.0.page_y() } fn screen_x(&self) -> i32 { - todo!() + self.0.screen_x() } fn screen_y(&self) -> i32 { - todo!() + self.0.screen_y() } 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))) diff --git a/src/lib.rs b/src/lib.rs index 439aace83..242d71720 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,12 +182,14 @@ pub mod prelude { pub use dioxus_core::prelude::*; pub use dioxus_core_macro::fc; } -pub mod builder { - // pub use dioxus_core::builder::*; -} -pub mod events { - // pub use dioxus_core::events::*; -} +// pub mod builder { +// // pub use dioxus_core::builder::*; +// } +pub use dioxus_core::builder; +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 // together and exports their namespaces to something predicatble. #[cfg(feature = "core")]