comparer.rs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. use std::hash::Hash;
  2. use dioxus_core::prelude::*;
  3. use crate::{CopyValue, Effect, ReadOnlySignal, Signal};
  4. use rustc_hash::FxHashMap;
  5. /// An object that can efficiently compare a value to a set of values.
  6. #[derive(Debug)]
  7. pub struct Comparer<R: 'static> {
  8. subscribers: CopyValue<FxHashMap<R, Signal<bool>>>,
  9. }
  10. impl<R: Eq + Hash> Comparer<R> {
  11. /// Returns a signal which is true when the value is equal to the value passed to this function.
  12. pub fn equal(&self, value: R) -> ReadOnlySignal<bool> {
  13. let subscribers = self.subscribers.read();
  14. match subscribers.get(&value) {
  15. Some(&signal) => signal.into(),
  16. None => {
  17. drop(subscribers);
  18. let mut subscribers = self.subscribers.write();
  19. let signal = Signal::new(false);
  20. subscribers.insert(value, signal);
  21. signal.into()
  22. }
  23. }
  24. }
  25. }
  26. impl<R> Clone for Comparer<R> {
  27. fn clone(&self) -> Self {
  28. *self
  29. }
  30. }
  31. impl<R> Copy for Comparer<R> {}
  32. /// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values.
  33. ///
  34. /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_selector`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
  35. ///
  36. /// ```rust
  37. /// use dioxus::prelude::*;
  38. /// use dioxus_signals::*;
  39. ///
  40. /// fn App(cx: Scope) -> Element {
  41. /// let mut count = use_signal(cx, || 0);
  42. /// let comparer = use_comparer(cx, move || count.value());
  43. ///
  44. /// render! {
  45. /// for i in 0..10 {
  46. /// // Child will only re-render when i == count
  47. /// Child { active: comparer.equal(i) }
  48. /// }
  49. /// button {
  50. /// // This will only rerender the child with the old and new value of i == count
  51. /// // Because we are using a comparer, this will be O(1) instead of the O(n) performance of a selector
  52. /// onclick: move |_| count += 1,
  53. /// "Increment"
  54. /// }
  55. /// }
  56. /// }
  57. ///
  58. /// #[component]
  59. /// fn Child(cx: Scope, active: ReadOnlySignal<bool>) -> Element {
  60. /// if *active() {
  61. /// render! { "Active" }
  62. /// } else {
  63. /// render! { "Inactive" }
  64. /// }
  65. /// }
  66. /// ```
  67. #[must_use]
  68. pub fn use_comparer<R: Eq + Hash>(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Comparer<R> {
  69. *cx.use_hook(move || comparer(f))
  70. }
  71. /// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values.
  72. ///
  73. /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_selector`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
  74. pub fn comparer<R: Eq + Hash>(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
  75. let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> = CopyValue::new(FxHashMap::default());
  76. let previous = CopyValue::new(None);
  77. Effect::new(move || {
  78. let subscribers = subscribers.read();
  79. let mut previous = previous.write();
  80. if let Some(previous) = previous.take() {
  81. if let Some(value) = subscribers.get(&previous) {
  82. value.set(false);
  83. }
  84. }
  85. let current = f();
  86. if let Some(value) = subscribers.get(&current) {
  87. value.set(true);
  88. }
  89. *previous = Some(current);
  90. });
  91. Comparer { subscribers }
  92. }