feat: add Scope::batch() (#711)

This commit is contained in:
Greg Johnston 2023-03-20 08:29:18 -04:00 committed by GitHub
parent 9d142758ec
commit 42a58855a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 7 deletions

View file

@ -62,6 +62,7 @@ pub(crate) struct Runtime {
RefCell<SecondaryMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
pub pending_effects: RefCell<Vec<NodeId>>,
pub resources: RefCell<SlotMap<ResourceId, AnyResource>>,
pub batching: Cell<bool>,
}
// This core Runtime impl block handles all the work of marking and updating
@ -248,13 +249,17 @@ impl Runtime {
pub(crate) fn run_effects(runtime_id: RuntimeId) {
_ = with_runtime(runtime_id, |runtime| {
let effects = runtime.pending_effects.take();
for effect_id in effects {
runtime.update_if_necessary(effect_id);
}
runtime.run_your_effects();
});
}
pub(crate) fn run_your_effects(&self) {
let effects = self.pending_effects.take();
for effect_id in effects {
self.update_if_necessary(effect_id);
}
}
pub(crate) fn dispose_node(&self, node: NodeId) {
self.node_sources.borrow_mut().remove(node);
self.node_subscribers.borrow_mut().remove(node);

View file

@ -442,6 +442,25 @@ impl Scope {
.ok()
.flatten()
}
/// Batches any reactive updates, preventing effects from running until the whole
/// function has run. This allows you to prevent rerunning effects if multiple
/// signal updates might cause the same effect to run.
///
/// # Panics
/// Panics if the runtime this scope belongs to has already been disposed.
pub fn batch<T>(&self, f: impl FnOnce() -> T) -> T {
with_runtime(self.runtime, move |runtime| {
runtime.batching.set(true);
let val = f();
runtime.batching.set(false);
runtime.run_your_effects();
val
})
.expect(
"tried to run a batched update in a runtime that has been disposed",
)
}
}
impl fmt::Debug for ScopeDisposer {

View file

@ -1912,7 +1912,7 @@ impl NodeId {
runtime.mark_dirty(*self);
// notify subscribers
if updated.is_some() {
if updated.is_some() && !runtime.batching.get() {
Runtime::run_effects(runtime_id);
};
updated

View file

@ -1,7 +1,7 @@
#[cfg(not(feature = "stable"))]
use leptos_reactive::{
create_isomorphic_effect, create_memo, create_runtime, create_scope,
create_signal,
create_isomorphic_effect, create_memo, create_runtime, create_rw_signal,
create_scope, create_signal, SignalSet,
};
#[cfg(not(feature = "stable"))]
@ -91,3 +91,45 @@ fn untrack_mutes_effect() {
})
.dispose()
}
#[cfg(not(feature = "stable"))]
#[test]
fn batching_actually_batches() {
use std::{cell::Cell, rc::Rc};
create_scope(create_runtime(), |cx| {
let first_name = create_rw_signal(cx, "Greg".to_string());
let last_name = create_rw_signal(cx, "Johnston".to_string());
// simulate an arbitrary side effect
let count = Rc::new(Cell::new(0));
create_isomorphic_effect(cx, {
let count = count.clone();
move |_| {
_ = first_name();
_ = last_name();
count.set(count.get() + 1);
}
});
// runs once initially
assert_eq!(count.get(), 1);
// individual updates run effect once each
first_name.set("Alice".to_string());
assert_eq!(count.get(), 2);
last_name.set("Smith".to_string());
assert_eq!(count.get(), 3);
// batched effect only runs twice
cx.batch(move || {
first_name.set("Bob".to_string());
last_name.set("Williams".to_string());
});
assert_eq!(count.get(), 4);
})
.dispose()
}