瀏覽代碼

add optional dependency variants of selectors and effects

Evan Almloff 1 年之前
父節點
當前提交
d6089bbd35

+ 1 - 0
packages/signals/Cargo.toml

@@ -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"] }

+ 38 - 0
packages/signals/examples/dependancies.rs

@@ -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}"
+        }
+    }
+}

+ 67 - 0
packages/signals/src/dependency.rs

@@ -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,);

+ 26 - 1
packages/signals/src/effect.rs

@@ -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 {

+ 2 - 0
packages/signals/src/lib.rs

@@ -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::*;

+ 40 - 0
packages/signals/src/selector.rs

@@ -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.