Deduplicate reactive scope updates/Reset subscriptions on reruns/fix use memo double update (#2506)
* deduplicate effect updates * only subscribe to signals read in the current run of reactive scopes * subscribe to memo reads after recomputing the value
This commit is contained in:
1 parent
24d247dc85
commit
b6dc2a2230
18 files changed
+459
-250
No files matched your search
@@ -1,5 +1,6 @@
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_signals::ReactiveContext;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
use crate::use_callback;
|
||||
@@ -15,12 +16,24 @@ pub fn use_effect(callback: impl FnMut() + 'static) -> Effect {
|
||||
use_hook(|| {
|
||||
// Inside the effect, we track any reads so that we can rerun the effect if a value the effect reads changes
|
||||
let (rc, mut changed) = ReactiveContext::new_with_origin(location);
|
||||
|
||||
// Deduplicate queued effects
|
||||
let effect_queued = Rc::new(Cell::new(false));
|
||||
|
||||
// Spawn a task that will run the effect when:
|
||||
// 1) The component is first run
|
||||
// 2) The effect is rerun due to an async read at any time
|
||||
// 3) The effect is rerun in the same tick that the component is rerun: we need to wait for the component to rerun before we can run the effect again
|
||||
let queue_effect_for_next_render = move || {
|
||||
queue_effect(move || rc.run_in(&*callback));
|
||||
if effect_queued.get() {
|
||||
return;
|
||||
}
|
||||
effect_queued.set(true);
|
||||
let effect_queued = effect_queued.clone();
|
||||
queue_effect(move || {
|
||||
rc.reset_and_run_in(&*callback);
|
||||
effect_queued.set(false);
|
||||
});
|
||||
};
|
||||
|
||||
queue_effect_for_next_render();
|
||||
|
||||
@@ -30,7 +30,7 @@ where
|
||||
|
||||
let cb = use_callback(move || {
|
||||
// Create the user's task
|
||||
let fut = rc.run_in(&mut future);
|
||||
let fut = rc.reset_and_run_in(&mut future);
|
||||
|
||||
// Spawn a wrapper task that polls the inner future and watch its dependencies
|
||||
spawn(async move {
|
||||
|
||||
@@ -50,21 +50,73 @@ async fn memo_updates() {
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let race = async move {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
dom.rebuild_in_place();
|
||||
let mut signal = VEC_SIGNAL.with(|cell| (*cell.borrow()).unwrap());
|
||||
// Wait for the signal to update
|
||||
for _ in 0..2 {
|
||||
dom.wait_for_work().await;
|
||||
dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);
|
||||
dom.rebuild_in_place();
|
||||
let mut signal = VEC_SIGNAL.with(|cell| (*cell.borrow()).unwrap());
|
||||
// Wait for the signal to update
|
||||
for _ in 0..2 {
|
||||
dom.wait_for_work().await;
|
||||
dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);
|
||||
}
|
||||
assert_eq!(signal(), vec![0, 1, 2, 3, 4, 5]);
|
||||
// Remove each element from the vec
|
||||
for _ in 0..6 {
|
||||
signal.pop();
|
||||
dom.wait_for_work().await;
|
||||
dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);
|
||||
println!("Signal: {signal:?}");
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = race => {},
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(1000)) => panic!("timed out")
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn use_memo_only_triggers_one_update() {
|
||||
use dioxus::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static VEC_SIGNAL: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
assert_eq!(signal(), vec![0, 1, 2, 3, 4, 5]);
|
||||
// Remove each element from the vec
|
||||
for _ in 0..6 {
|
||||
signal.pop();
|
||||
dom.wait_for_work().await;
|
||||
dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);
|
||||
println!("Signal: {signal:?}");
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
let memorized = use_memo(move || dbg!(count() * 2));
|
||||
|
||||
use_memo(move || {
|
||||
println!("reading doubled");
|
||||
let doubled = memorized();
|
||||
VEC_SIGNAL.with_borrow_mut(|v| v.push(doubled))
|
||||
});
|
||||
|
||||
// Writing to count many times in a row should not cause the memo to update other subscribers multiple times
|
||||
use_hook(move || {
|
||||
for _ in 0..10 {
|
||||
count += 1;
|
||||
// Reading the memo each time will trigger the memo to rerun immediately, but the VEC_SIGNAL should still only rerun once
|
||||
println!("doubled {memorized}");
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild_in_place();
|
||||
|
||||
tokio::select! {
|
||||
_ = dom.wait_for_work() => {},
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {}
|
||||
};
|
||||
|
||||
dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);
|
||||
|
||||
assert_eq!(VEC_SIGNAL.with(|v| v.borrow().clone()), vec![0, 20]);
|
||||
}
|
||||
Reference in new issue
Block a user