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:
Jonathan Kelley committed 2021-06-23 22:32:54 -04:00
1 parent 9d7ee79826
commit f5683a2346
9 files changed
+197 -174

No files matched your search

+62
View File
@@ -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
View File
@@ -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(())
}
+56
View File
@@ -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",
}
}
}
+6 -3
View File
@@ -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,
+13 -3
View File
@@ -44,11 +44,21 @@ pub enum VirtualEvent {
MouseEvent(Rc<dyn on::MouseEvent>),
PointerEvent(Rc<dyn on::PointerEvent>),
// 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 {
+3 -3
View File
@@ -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.
+30
View File
@@ -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
View File
@@ -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)))
+8 -6
View File
@@ -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")]