add optional dependency variants of selectors and effects
This commit is contained in:
1 parent
e2c08a79ae
commit
d6089bbd35
6 files changed
+174
-1
No files matched your search
@@ -15,3 +15,4 @@ simple_logger = "4.2.0"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
dioxus-desktop = { workspace = true }
|
dioxus-desktop = { workspace = true }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_signals::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let signal = use_signal(cx, || 0);
|
||||||
|
|
||||||
|
use_future!(cx, || async move {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
*signal.write() += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let local_state = use_state(cx, || 0);
|
||||||
|
let computed =
|
||||||
|
use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| {
|
||||||
|
local_state * 2 + signal.value()
|
||||||
|
});
|
||||||
|
println!("Running app");
|
||||||
|
|
||||||
|
render! {
|
||||||
|
button {
|
||||||
|
onclick: move |_| {
|
||||||
|
local_state.set(local_state.get() + 1);
|
||||||
|
},
|
||||||
|
"Add one"
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
"{computed}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||||
|
pub trait Dependency: Sized + Clone {
|
||||||
|
/// The output of the dependency
|
||||||
|
type Out: Clone + PartialEq;
|
||||||
|
/// Returns the output of the dependency.
|
||||||
|
fn out(&self) -> Self::Out;
|
||||||
|
/// Returns true if the dependency has changed.
|
||||||
|
fn changed(&self, other: &Self::Out) -> bool {
|
||||||
|
self.out() != *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dependency for () {
|
||||||
|
type Out = ();
|
||||||
|
fn out(&self) -> Self::Out {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||||
|
pub trait Dep: 'static + PartialEq + Clone {}
|
||||||
|
impl<T> Dep for T where T: 'static + PartialEq + Clone {}
|
||||||
|
|
||||||
|
impl<A: Dep> Dependency for &A {
|
||||||
|
type Out = A;
|
||||||
|
fn out(&self) -> Self::Out {
|
||||||
|
(*self).clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_dep {
|
||||||
|
(
|
||||||
|
$($el:ident=$name:ident $other:ident,)*
|
||||||
|
) => {
|
||||||
|
impl< $($el),* > Dependency for ($(&$el,)*)
|
||||||
|
where
|
||||||
|
$(
|
||||||
|
$el: Dep
|
||||||
|
),*
|
||||||
|
{
|
||||||
|
type Out = ($($el,)*);
|
||||||
|
|
||||||
|
fn out(&self) -> Self::Out {
|
||||||
|
let ($($name,)*) = self;
|
||||||
|
($((*$name).clone(),)*)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&self, other: &Self::Out) -> bool {
|
||||||
|
let ($($name,)*) = self;
|
||||||
|
let ($($other,)*) = other;
|
||||||
|
$(
|
||||||
|
if *$name != $other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_dep!(A = a1 a2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,);
|
||||||
|
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,);
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
use core::{self, fmt::Debug};
|
use core::{self, fmt::Debug};
|
||||||
use std::fmt::{self, Formatter};
|
use std::fmt::{self, Formatter};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
use crate::CopyValue;
|
use crate::dependency::Dep;
|
||||||
|
use crate::{dependency::Dependency, CopyValue};
|
||||||
|
use crate::{use_signal, Signal};
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Default, Clone, Copy)]
|
||||||
pub(crate) struct EffectStack {
|
pub(crate) struct EffectStack {
|
||||||
@@ -26,6 +29,28 @@ pub fn use_effect(cx: &ScopeState, callback: impl FnMut() + 'static) {
|
|||||||
cx.use_hook(|| Effect::new(callback));
|
cx.use_hook(|| Effect::new(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
|
||||||
|
/// The signal will be owned by the current component and will be dropped when the component is dropped.
|
||||||
|
pub fn use_effect_with_dependencies<D: Dependency>(
|
||||||
|
cx: &ScopeState,
|
||||||
|
dependencies: D,
|
||||||
|
mut callback: impl FnMut(D::Out) + 'static,
|
||||||
|
) where
|
||||||
|
D::Out: 'static,
|
||||||
|
{
|
||||||
|
let dependencies_signal = use_signal(cx, || dependencies.out());
|
||||||
|
cx.use_hook(|| {
|
||||||
|
Effect::new(move || {
|
||||||
|
let deref = &*dependencies_signal.read();
|
||||||
|
callback(deref.clone());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let changed = { dependencies.changed(&*dependencies_signal.read()) };
|
||||||
|
if changed {
|
||||||
|
dependencies_signal.set(dependencies.out());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Effects allow you to run code when a signal changes. Effects are run immediately and whenever any signal it reads changes.
|
/// Effects allow you to run code when a signal changes. Effects are run immediately and whenever any signal it reads changes.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub struct Effect {
|
pub struct Effect {
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ mod selector;
|
|||||||
pub use selector::*;
|
pub use selector::*;
|
||||||
pub(crate) mod signal;
|
pub(crate) mod signal;
|
||||||
pub use signal::*;
|
pub use signal::*;
|
||||||
|
mod dependency;
|
||||||
|
pub use dependency::*;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
|
use crate::dependency::Dependency;
|
||||||
|
use crate::use_signal;
|
||||||
use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal};
|
use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal};
|
||||||
|
|
||||||
/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
|
/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
|
||||||
@@ -26,6 +28,44 @@ pub fn use_selector<R: PartialEq>(
|
|||||||
*cx.use_hook(|| selector(f))
|
*cx.use_hook(|| selector(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
|
||||||
|
///
|
||||||
|
/// Selectors can be used to efficiently compute derived data from signals.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use dioxus::prelude::*;
|
||||||
|
/// use dioxus_signals::*;
|
||||||
|
///
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// let mut local_state = use_state(cx, || 0);
|
||||||
|
/// let double = use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2);
|
||||||
|
/// local_state.set(1);
|
||||||
|
///
|
||||||
|
/// render! { "{double}" }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn use_selector_with_dependencies<R: PartialEq, D: Dependency>(
|
||||||
|
cx: &ScopeState,
|
||||||
|
dependencies: D,
|
||||||
|
mut f: impl FnMut(D::Out) -> R + 'static,
|
||||||
|
) -> ReadOnlySignal<R>
|
||||||
|
where
|
||||||
|
D::Out: 'static,
|
||||||
|
{
|
||||||
|
let dependencies_signal = use_signal(cx, || dependencies.out());
|
||||||
|
let selector = *cx.use_hook(|| {
|
||||||
|
selector(move || {
|
||||||
|
let deref = &*dependencies_signal.read();
|
||||||
|
f(deref.clone())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let changed = { dependencies.changed(&*dependencies_signal.read()) };
|
||||||
|
if changed {
|
||||||
|
dependencies_signal.set(dependencies.out());
|
||||||
|
}
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
|
/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes.
|
||||||
///
|
///
|
||||||
/// Selectors can be used to efficiently compute derived data from signals.
|
/// Selectors can be used to efficiently compute derived data from signals.
|
||||||
|
|||||||
Reference in new issue
Block a user