mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
feat: Trigger
primitive and reactive-system cleanups (#838)
This commit is contained in:
parent
764192af36
commit
493c805993
20 changed files with 438 additions and 152 deletions
|
@ -3,8 +3,8 @@
|
|||
use crate::{runtime::with_runtime, Scope};
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
/// Provides a context value of type `T` to the current reactive [Scope](crate::Scope)
|
||||
/// and all of its descendants. This can be consumed using [use_context](crate::use_context).
|
||||
/// Provides a context value of type `T` to the current reactive [`Scope`](crate::Scope)
|
||||
/// and all of its descendants. This can be consumed using [`use_context`](crate::use_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
|
@ -61,9 +61,9 @@ where
|
|||
}
|
||||
|
||||
/// Extracts a context value of type `T` from the reactive system by traversing
|
||||
/// it upwards, beginning from the current [Scope](crate::Scope) and iterating
|
||||
/// it upwards, beginning from the current [`Scope`](crate::Scope) and iterating
|
||||
/// through its parents, if any. The context value should have been provided elsewhere
|
||||
/// using [provide_context](crate::provide_context).
|
||||
/// using [`provide_context`](crate::provide_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
// The point of these diagnostics is to give useful error messages when someone
|
||||
// tries to access a reactive variable outside the reactive scope. They track when
|
||||
// you create a signal/memo, and where you access it non-reactively.
|
||||
|
@ -23,7 +21,7 @@ pub(crate) struct AccessDiagnostics {}
|
|||
#[doc(hidden)]
|
||||
pub struct SpecialNonReactiveZone {}
|
||||
|
||||
cfg_if! {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
use std::cell::Cell;
|
||||
|
||||
|
@ -66,7 +64,7 @@ impl SpecialNonReactiveZone {
|
|||
#[macro_export]
|
||||
macro_rules! diagnostics {
|
||||
($this:ident) => {{
|
||||
cfg_if! {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
AccessDiagnostics {
|
||||
defined_at: $this.defined_at,
|
||||
|
|
|
@ -11,14 +11,14 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
|
|||
/// Effects are intended to run *side-effects* of the system, not to synchronize state
|
||||
/// *within* the system. In other words: don't write to signals within effects.
|
||||
/// (If you need to define a signal that depends on the value of other signals, use a
|
||||
/// derived signal or [create_memo](crate::create_memo)).
|
||||
/// derived signal or [`create_memo`](crate::create_memo)).
|
||||
///
|
||||
/// The effect function is called with an argument containing whatever value it returned
|
||||
/// the last time it ran. On the initial run, this is `None`.
|
||||
///
|
||||
/// By default, effects **do not run on the server**. This means you can call browser-specific
|
||||
/// APIs within the effect function without causing issues. If you need an effect to run on
|
||||
/// the server, use [create_isomorphic_effect].
|
||||
/// the server, use [`create_isomorphic_effect`].
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
|
@ -76,7 +76,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates an effect; unlike effects created by [create_effect], isomorphic effects will run on
|
||||
/// Creates an effect; unlike effects created by [`create_effect`], isomorphic effects will run on
|
||||
/// the server as well as the client.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
|
|
|
@ -17,18 +17,19 @@
|
|||
//! Here are the most commonly-used functions and types you'll need to build a reactive system:
|
||||
//!
|
||||
//! ### Signals
|
||||
//! 1. *Signals:* [create_signal](crate::create_signal), which returns a ([ReadSignal](crate::ReadSignal),
|
||||
//! [WriteSignal](crate::WriteSignal)) tuple, or [create_rw_signal](crate::create_rw_signal), which returns
|
||||
//! a signal [RwSignal](crate::RwSignal) without this read-write segregation.
|
||||
//! 1. *Signals:* [`create_signal`](crate::create_signal), which returns a ([`ReadSignal`](crate::ReadSignal),
|
||||
//! [`WriteSignal`](crate::WriteSignal)) tuple, or [`create_rw_signal`](crate::create_rw_signal), which returns
|
||||
//! a signal [`RwSignal`](crate::RwSignal) without this read-write segregation.
|
||||
//! 2. *Derived Signals:* any function that relies on another signal.
|
||||
//! 3. *Memos:* [create_memo](crate::create_memo), which returns a [Memo](crate::Memo).
|
||||
//! 4. *Resources:* [create_resource], which converts an `async` [std::future::Future] into a
|
||||
//! synchronous [Resource](crate::Resource) signal.
|
||||
//! 3. *Memos:* [`create_memo`], which returns a [`Memo`](crate::Memo).
|
||||
//! 4. *Resources:* [`create_resource`], which converts an `async` [`Future`](std::future::Future) into a
|
||||
//! synchronous [`Resource`](crate::Resource) signal.
|
||||
//! 5. *Triggers:* [`create_trigger`], creates a purely reactive [`Trigger`] primitive without any associated state.
|
||||
//!
|
||||
//! ### Effects
|
||||
//! 1. Use [create_effect](crate::create_effect) when you need to synchronize the reactive system
|
||||
//! 1. Use [`create_effect`](crate::create_effect) when you need to synchronize the reactive system
|
||||
//! with something outside it (for example: logging to the console, writing to a file or local storage)
|
||||
//! 2. The Leptos DOM renderer wraps any [Fn] in your template with [create_effect](crate::create_effect), so
|
||||
//! 2. The Leptos DOM renderer wraps any [`Fn`] in your template with [`create_effect`](crate::create_effect), so
|
||||
//! components you write do *not* need explicit effects to synchronize with the DOM.
|
||||
//!
|
||||
//! ### Example
|
||||
|
@ -90,6 +91,7 @@ mod spawn;
|
|||
mod spawn_microtask;
|
||||
mod stored_value;
|
||||
pub mod suspense;
|
||||
mod trigger;
|
||||
|
||||
pub use context::*;
|
||||
pub use diagnostics::SpecialNonReactiveZone;
|
||||
|
@ -109,6 +111,7 @@ pub use spawn::*;
|
|||
pub use spawn_microtask::*;
|
||||
pub use stored_value::*;
|
||||
pub use suspense::SuspenseContext;
|
||||
pub use trigger::*;
|
||||
|
||||
mod macros {
|
||||
macro_rules! debug_warn {
|
||||
|
|
|
@ -5,7 +5,6 @@ use crate::{
|
|||
SignalDispose, SignalGet, SignalGetUntracked, SignalStream, SignalWith,
|
||||
SignalWithUntracked,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// Creates an efficient derived reactive value based on other reactive values.
|
||||
|
@ -21,7 +20,7 @@ use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
|||
/// create a derived signal. But if the derivation calculation is expensive, you should
|
||||
/// create a memo.
|
||||
///
|
||||
/// As with [create_effect](crate::create_effect), the argument to the memo function is the previous value,
|
||||
/// As with [`create_effect`](crate::create_effect), the argument to the memo function is the previous value,
|
||||
/// i.e., the current value of the memo, which will be `None` for the initial calculation.
|
||||
///
|
||||
/// ```
|
||||
|
@ -99,7 +98,7 @@ where
|
|||
/// create a derived signal. But if the derivation calculation is expensive, you should
|
||||
/// create a memo.
|
||||
///
|
||||
/// As with [create_effect](crate::create_effect), the argument to the memo function is the previous value,
|
||||
/// As with [`create_effect`](crate::create_effect), the argument to the memo function is the previous value,
|
||||
/// i.e., the current value of the memo, which will be `None` for the initial calculation.
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
|
|
|
@ -8,13 +8,22 @@ slotmap::new_key_type! {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ReactiveNode {
|
||||
pub value: Rc<RefCell<dyn Any>>,
|
||||
pub value: Option<Rc<RefCell<dyn Any>>>,
|
||||
pub state: ReactiveNodeState,
|
||||
pub node_type: ReactiveNodeType,
|
||||
}
|
||||
|
||||
impl ReactiveNode {
|
||||
pub fn value(&self) -> Rc<RefCell<dyn Any>> {
|
||||
self.value
|
||||
.clone()
|
||||
.expect("ReactiveNode.value to have a value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ReactiveNodeType {
|
||||
Trigger,
|
||||
Signal,
|
||||
Memo { f: Rc<dyn AnyComputation> },
|
||||
Effect { f: Rc<dyn AnyComputation> },
|
||||
|
|
|
@ -20,19 +20,19 @@ use std::{
|
|||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Creates [Resource](crate::Resource), which is a signal that reflects the
|
||||
/// Creates a [`Resource`](crate::Resource), which is a signal that reflects the
|
||||
/// current state of an asynchronous task, allowing you to integrate `async`
|
||||
/// [Future]s into the synchronous reactive system.
|
||||
/// [`Future`]s into the synchronous reactive system.
|
||||
///
|
||||
/// Takes a `fetcher` function that generates a [Future] when called and a
|
||||
/// Takes a `fetcher` function that generates a [`Future`] when called and a
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever the
|
||||
/// value of the `source` changes, a new [Future] will be created and run.
|
||||
/// value of the `source` changes, a new [`Future`] will be created and run.
|
||||
///
|
||||
/// When server-side rendering is used, the server will handle running the
|
||||
/// [Future] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [Serializable]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [Future] runs locally, use
|
||||
/// [create_local_resource()].
|
||||
/// [`Future`] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [`Serializable`]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [`Future`] runs locally, use
|
||||
/// [`create_local_resource()`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
|
@ -79,14 +79,14 @@ where
|
|||
create_resource_with_initial_value(cx, source, fetcher, initial_value)
|
||||
}
|
||||
|
||||
/// Creates a [Resource](crate::Resource) with the given initial value, which
|
||||
/// will only generate and run a [Future] using the `fetcher` when the `source` changes.
|
||||
/// Creates a [`Resource`](crate::Resource) with the given initial value, which
|
||||
/// will only generate and run a [`Future`] using the `fetcher` when the `source` changes.
|
||||
///
|
||||
/// When server-side rendering is used, the server will handle running the
|
||||
/// [Future] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [Serializable]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [Future] runs locally, use
|
||||
/// [create_local_resource_with_initial_value()].
|
||||
/// [`Future`] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [`Serializable`]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [`Future`] runs locally, use
|
||||
/// [`create_local_resource_with_initial_value()`].
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
|
@ -120,7 +120,7 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// Creates a “blocking” [Resource](crate::Resource). When server-side rendering is used,
|
||||
/// Creates a “blocking” [`Resource`](crate::Resource). When server-side rendering is used,
|
||||
/// this resource will cause any `<Suspense/>` you read it under to block the initial
|
||||
/// chunk of HTML from being sent to the client. This means that if you set things like
|
||||
/// HTTP headers or `<head>` metadata in that `<Suspense/>`, that header material will
|
||||
|
@ -228,16 +228,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a _local_ [Resource](crate::Resource), which is a signal that
|
||||
/// Creates a _local_ [`Resource`](crate::Resource), which is a signal that
|
||||
/// reflects the current state of an asynchronous task, allowing you to
|
||||
/// integrate `async` [Future]s into the synchronous reactive system.
|
||||
/// integrate `async` [`Future`]s into the synchronous reactive system.
|
||||
///
|
||||
/// Takes a `fetcher` function that generates a [Future] when called and a
|
||||
/// Takes a `fetcher` function that generates a [`Future`] when called and a
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever the
|
||||
/// value of the `source` changes, a new [Future] will be created and run.
|
||||
/// value of the `source` changes, a new [`Future`] will be created and run.
|
||||
///
|
||||
/// Unlike [create_resource()], this [Future] is always run on the local system
|
||||
/// and therefore it's result type does not need to be [Serializable].
|
||||
/// Unlike [`create_resource()`], this [`Future`] is always run on the local system
|
||||
/// and therefore it's result type does not need to be [`Serializable`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
|
@ -273,13 +273,13 @@ where
|
|||
create_local_resource_with_initial_value(cx, source, fetcher, initial_value)
|
||||
}
|
||||
|
||||
/// Creates a _local_ [Resource](crate::Resource) with the given initial value,
|
||||
/// which will only generate and run a [Future] using the `fetcher` when the
|
||||
/// Creates a _local_ [`Resource`](crate::Resource) with the given initial value,
|
||||
/// which will only generate and run a [`Future`] using the `fetcher` when the
|
||||
/// `source` changes.
|
||||
///
|
||||
/// Unlike [create_resource_with_initial_value()], this [Future] will always run
|
||||
/// Unlike [`create_resource_with_initial_value()`], this [`Future`] will always run
|
||||
/// on the local system and therefore its output type does not need to be
|
||||
/// [Serializable].
|
||||
/// [`Serializable`].
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
|
@ -443,7 +443,7 @@ where
|
|||
/// resource is still pending). Also subscribes the running effect to this
|
||||
/// resource.
|
||||
///
|
||||
/// If you want to get the value without cloning it, use [Resource::with].
|
||||
/// If you want to get the value without cloning it, use [`Resource::with`].
|
||||
/// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.)
|
||||
#[track_caller]
|
||||
pub fn read(&self, cx: Scope) -> Option<T>
|
||||
|
@ -463,10 +463,10 @@ where
|
|||
/// Applies a function to the current value of the resource, and subscribes
|
||||
/// the running effect to this resource. If the resource hasn't yet
|
||||
/// resolved, the function won't be called and this will return
|
||||
/// [Option::None].
|
||||
/// [`Option::None`].
|
||||
///
|
||||
/// If you want to get the value by cloning it, you can use
|
||||
/// [Resource::read].
|
||||
/// [`Resource::read`].
|
||||
#[track_caller]
|
||||
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
let location = std::panic::Location::caller();
|
||||
|
@ -501,8 +501,8 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
/// Returns a [std::future::Future] that will resolve when the resource has loaded,
|
||||
/// yield its [ResourceId] and a JSON string.
|
||||
/// Returns a [`Future`] that will resolve when the resource has loaded,
|
||||
/// yield its [`ResourceId`] and a JSON string.
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
pub async fn to_serialization_resolver(
|
||||
&self,
|
||||
|
@ -526,17 +526,17 @@ where
|
|||
|
||||
/// A signal that reflects the
|
||||
/// current state of an asynchronous task, allowing you to integrate `async`
|
||||
/// [Future]s into the synchronous reactive system.
|
||||
/// [`Future`]s into the synchronous reactive system.
|
||||
///
|
||||
/// Takes a `fetcher` function that generates a [Future] when called and a
|
||||
/// Takes a `fetcher` function that generates a [`Future`] when called and a
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever the
|
||||
/// value of the `source` changes, a new [Future] will be created and run.
|
||||
/// value of the `source` changes, a new [`Future`] will be created and run.
|
||||
///
|
||||
/// When server-side rendering is used, the server will handle running the
|
||||
/// [Future] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [Serializable]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [Future] runs locally, use
|
||||
/// [create_local_resource()].
|
||||
/// [`Future`] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [`Serializable`]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [`Future`] runs locally, use
|
||||
/// [`create_local_resource()`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
|
@ -583,7 +583,7 @@ where
|
|||
|
||||
// Resources
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [Resource](crate::Resource).
|
||||
/// Unique ID assigned to a [`Resource`](crate::Resource).
|
||||
pub struct ResourceId;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
|||
node::{NodeId, ReactiveNode, ReactiveNodeState, ReactiveNodeType},
|
||||
AnyComputation, AnyResource, Effect, Memo, MemoState, ReadSignal,
|
||||
ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
ScopeProperty, SerializableResource, StoredValueId, UnserializableResource,
|
||||
WriteSignal,
|
||||
ScopeProperty, SerializableResource, StoredValueId, Trigger,
|
||||
UnserializableResource, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use core::hash::BuildHasherDefault;
|
||||
|
@ -117,15 +117,16 @@ impl Runtime {
|
|||
// memos and effects rerun
|
||||
// signals simply have their value
|
||||
let changed = match node.node_type {
|
||||
ReactiveNodeType::Signal => true,
|
||||
ReactiveNodeType::Memo { f }
|
||||
| ReactiveNodeType::Effect { f } => {
|
||||
ReactiveNodeType::Signal | ReactiveNodeType::Trigger => true,
|
||||
ReactiveNodeType::Memo { ref f }
|
||||
| ReactiveNodeType::Effect { ref f } => {
|
||||
let value = node.value();
|
||||
// set this node as the observer
|
||||
self.with_observer(node_id, move || {
|
||||
// clean up sources of this memo/effect
|
||||
self.cleanup(node_id);
|
||||
|
||||
f.run(Rc::clone(&node.value))
|
||||
f.run(value)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
@ -191,12 +192,17 @@ impl Runtime {
|
|||
pub(crate) fn mark_dirty(&self, node: NodeId) {
|
||||
//crate::macros::debug_warn!("marking {node:?} dirty");
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
let mut pending_effects = self.pending_effects.borrow_mut();
|
||||
let subscribers = self.node_subscribers.borrow();
|
||||
let current_observer = self.observer.get();
|
||||
|
||||
// mark self dirty
|
||||
if let Some(current_node) = nodes.get_mut(node) {
|
||||
if current_node.state == ReactiveNodeState::DirtyMarked {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut pending_effects = self.pending_effects.borrow_mut();
|
||||
let subscribers = self.node_subscribers.borrow();
|
||||
let current_observer = self.observer.get();
|
||||
|
||||
// mark self dirty
|
||||
Runtime::mark(
|
||||
node,
|
||||
current_node,
|
||||
|
@ -325,13 +331,7 @@ impl Runtime {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_effects(runtime_id: RuntimeId) {
|
||||
_ = with_runtime(runtime_id, |runtime| {
|
||||
runtime.run_your_effects();
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn run_your_effects(&self) {
|
||||
pub(crate) fn run_effects(&self) {
|
||||
if !self.batching.get() {
|
||||
let effects = self.pending_effects.take();
|
||||
for effect_id in effects {
|
||||
|
@ -384,7 +384,7 @@ pub(crate) fn with_runtime<T>(
|
|||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Runtime will leak memory if Runtime::dispose() is never called."]
|
||||
/// Creates a new reactive [Runtime]. This should almost always be handled by the framework.
|
||||
/// Creates a new reactive [`Runtime`]. This should almost always be handled by the framework.
|
||||
pub fn create_runtime() -> RuntimeId {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
|
@ -397,17 +397,17 @@ pub fn create_runtime() -> RuntimeId {
|
|||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [Runtime](crate::Runtime).
|
||||
/// Unique ID assigned to a Runtime.
|
||||
pub struct RuntimeId;
|
||||
}
|
||||
|
||||
/// Unique ID assigned to a [Runtime](crate::Runtime).
|
||||
/// Unique ID assigned to a Runtime.
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct RuntimeId;
|
||||
|
||||
impl RuntimeId {
|
||||
/// Removes the runtime, disposing all its child [Scope](crate::Scope)s.
|
||||
/// Removes the runtime, disposing all its child [`Scope`](crate::Scope)s.
|
||||
pub fn dispose(self) {
|
||||
cfg_if! {
|
||||
if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
|
@ -468,13 +468,35 @@ impl RuntimeId {
|
|||
ret
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)] // only because it's placed here to fit in with the other create methods
|
||||
pub(crate) fn create_trigger(self) -> Trigger {
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: None,
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
})
|
||||
})
|
||||
.expect(
|
||||
"tried to create a trigger in a runtime that has been disposed",
|
||||
);
|
||||
|
||||
Trigger {
|
||||
id,
|
||||
runtime: self,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_concrete_signal(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
) -> NodeId {
|
||||
with_runtime(self, |runtime| {
|
||||
runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value,
|
||||
value: Some(value),
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Signal,
|
||||
})
|
||||
|
@ -539,7 +561,7 @@ impl RuntimeId {
|
|||
values
|
||||
.map(|value| {
|
||||
signals.insert(ReactiveNode {
|
||||
value: Rc::new(RefCell::new(value)),
|
||||
value: Some(Rc::new(RefCell::new(value))),
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Signal,
|
||||
})
|
||||
|
@ -598,7 +620,7 @@ impl RuntimeId {
|
|||
) -> NodeId {
|
||||
with_runtime(self, |runtime| {
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: Rc::clone(&value),
|
||||
value: Some(Rc::clone(&value)),
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Effect {
|
||||
f: Rc::clone(&effect),
|
||||
|
@ -625,7 +647,7 @@ impl RuntimeId {
|
|||
) -> NodeId {
|
||||
with_runtime(self, |runtime| {
|
||||
runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value,
|
||||
value: Some(value),
|
||||
// memos are lazy, so are dirty when created
|
||||
// will be run the first time we ask for it
|
||||
state: ReactiveNodeState::Dirty,
|
||||
|
@ -775,12 +797,13 @@ impl Runtime {
|
|||
f
|
||||
}
|
||||
|
||||
/// Do not call on triggers
|
||||
pub(crate) fn get_value(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Option<Rc<RefCell<dyn Any>>> {
|
||||
let signals = self.nodes.borrow();
|
||||
signals.get(node_id).map(|node| Rc::clone(&node.value))
|
||||
signals.get(node_id).map(|node| node.value())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ pub fn run_scope_undisposed<T>(
|
|||
/// when it is removed from the list.
|
||||
///
|
||||
/// Every other function in this crate takes a `Scope` as its first argument. Since `Scope`
|
||||
/// is [Copy] and `'static` this does not add much overhead or lifetime complexity.
|
||||
/// is [`Copy`] and `'static` this does not add much overhead or lifetime complexity.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Scope {
|
||||
#[doc(hidden)]
|
||||
|
@ -226,7 +226,7 @@ impl Scope {
|
|||
///
|
||||
/// This will
|
||||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
pub fn dispose(self) {
|
||||
_ = with_runtime(self.runtime, |runtime| {
|
||||
|
@ -264,7 +264,8 @@ impl Scope {
|
|||
if let Some(owned) = owned {
|
||||
for property in owned {
|
||||
match property {
|
||||
ScopeProperty::Signal(id) => {
|
||||
ScopeProperty::Signal(id)
|
||||
| ScopeProperty::Trigger(id) => {
|
||||
// remove the signal
|
||||
runtime.nodes.borrow_mut().remove(id);
|
||||
let subs = runtime
|
||||
|
@ -339,7 +340,7 @@ fn push_cleanup(cx: Scope, cleanup_fn: Box<dyn FnOnce()>) {
|
|||
});
|
||||
}
|
||||
|
||||
/// Creates a cleanup function, which will be run when a [Scope] is disposed.
|
||||
/// Creates a cleanup function, which will be run when a [`Scope`] is disposed.
|
||||
///
|
||||
/// It runs after child scopes have been disposed, but before signals, effects, and resources
|
||||
/// are invalidated.
|
||||
|
@ -349,34 +350,35 @@ pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
|
|||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [Scope](crate::Scope).
|
||||
/// Unique ID assigned to a [`Scope`](crate::Scope).
|
||||
pub struct ScopeId;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ScopeProperty {
|
||||
Trigger(NodeId),
|
||||
Signal(NodeId),
|
||||
Effect(NodeId),
|
||||
Resource(ResourceId),
|
||||
StoredValue(StoredValueId),
|
||||
}
|
||||
|
||||
/// Creating a [Scope](crate::Scope) gives you a disposer, which can be called
|
||||
/// Creating a [`Scope`](crate::Scope) gives you a disposer, which can be called
|
||||
/// to dispose of that reactive scope.
|
||||
///
|
||||
/// This will
|
||||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
#[repr(transparent)]
|
||||
pub struct ScopeDisposer(pub(crate) Scope);
|
||||
|
||||
impl ScopeDisposer {
|
||||
/// Disposes of a reactive [Scope](crate::Scope).
|
||||
/// Disposes of a reactive [`Scope`](crate::Scope).
|
||||
///
|
||||
/// This will
|
||||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
#[inline(always)]
|
||||
pub fn dispose(self) {
|
||||
|
@ -385,20 +387,20 @@ impl ScopeDisposer {
|
|||
}
|
||||
|
||||
impl Scope {
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
|
||||
pub fn all_resources(&self) -> Vec<ResourceId> {
|
||||
with_runtime(self.runtime, |runtime| runtime.all_resources())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope that are
|
||||
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope that are
|
||||
/// pending from the server.
|
||||
pub fn pending_resources(&self) -> Vec<ResourceId> {
|
||||
with_runtime(self.runtime, |runtime| runtime.pending_resources())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
|
||||
pub fn serialization_resolvers(
|
||||
&self,
|
||||
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
|
@ -408,7 +410,7 @@ impl Scope {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
|
||||
/// Registers the given [`SuspenseContext`](crate::SuspenseContext) with the current scope,
|
||||
/// calling the `resolver` when its resources are all resolved.
|
||||
pub fn register_suspense(
|
||||
&self,
|
||||
|
@ -522,7 +524,7 @@ impl Scope {
|
|||
runtime.batching.set(batching.1);
|
||||
std::mem::forget(batching);
|
||||
|
||||
runtime.run_your_effects();
|
||||
runtime.run_effects();
|
||||
val
|
||||
})
|
||||
.expect(
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
|
||||
/// Creates a conditional signal that only notifies subscribers when a change
|
||||
/// in the source signal’s value changes whether it is equal to the key value
|
||||
/// (as determined by [PartialEq].)
|
||||
/// (as determined by [`PartialEq`].)
|
||||
///
|
||||
/// **You probably don’t need this,** but it can be a very useful optimization
|
||||
/// in certain situations (e.g., “set the class `selected` if `selected() == this_row_index`)
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::rc::Rc;
|
|||
use thiserror::Error;
|
||||
|
||||
/// Describes errors that can occur while serializing and deserializing data,
|
||||
/// typically during the process of streaming [Resource](crate::Resource)s from
|
||||
/// typically during the process of streaming [`Resource`](crate::Resource)s from
|
||||
/// the server to the client.
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum SerializationError {
|
||||
|
@ -19,7 +19,7 @@ pub enum SerializationError {
|
|||
/// Describes an object that can be serialized to or from a supported format
|
||||
/// Currently those are JSON and Cbor
|
||||
///
|
||||
/// This is primarily used for serializing and deserializing [Resource](crate::Resource)s
|
||||
/// This is primarily used for serializing and deserializing [`Resource`](crate::Resource)s
|
||||
/// so they can begin on the server and be resolved on the client, but can be used
|
||||
/// for any data that needs to be serialized/deserialized.
|
||||
///
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::{
|
|||
runtime::{with_runtime, RuntimeId},
|
||||
Runtime, Scope, ScopeProperty,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::Stream;
|
||||
use std::{
|
||||
any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, pin::Pin, rc::Rc,
|
||||
|
@ -110,7 +109,7 @@ pub trait SignalGet<T> {
|
|||
/// the running effect to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that was created in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed.
|
||||
#[track_caller]
|
||||
fn get(&self) -> T;
|
||||
|
||||
|
@ -126,7 +125,7 @@ pub trait SignalWith<T> {
|
|||
/// the running effect to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that was created in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed.
|
||||
#[track_caller]
|
||||
fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O;
|
||||
|
||||
|
@ -198,7 +197,7 @@ pub trait SignalGetUntracked<T> {
|
|||
/// current scope.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that was created in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed.
|
||||
#[track_caller]
|
||||
fn get_untracked(&self) -> T;
|
||||
|
||||
|
@ -215,7 +214,7 @@ pub trait SignalWithUntracked<T> {
|
|||
/// value without creating a dependency on the current scope.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that was created in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed.
|
||||
#[track_caller]
|
||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O;
|
||||
|
||||
|
@ -276,7 +275,7 @@ pub trait SignalStream<T> {
|
|||
/// whenever it changes.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that was created in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed.
|
||||
// We're returning an opaque type until impl trait in trait
|
||||
// positions are stabilized, and also so any underlying
|
||||
// changes are non-breaking
|
||||
|
@ -284,7 +283,7 @@ pub trait SignalStream<T> {
|
|||
fn to_stream(&self, cx: Scope) -> Pin<Box<dyn Stream<Item = T>>>;
|
||||
}
|
||||
|
||||
/// This trait allows disposing a signal before its [Scope] has been disposed.
|
||||
/// This trait allows disposing a signal before its [`Scope`] has been disposed.
|
||||
pub trait SignalDispose {
|
||||
/// Disposes of the signal. This:
|
||||
/// 1. Detaches the signal from the reactive graph, preventing it from triggering
|
||||
|
@ -300,8 +299,8 @@ pub trait SignalDispose {
|
|||
/// and notifies other code when it has changed. This is the
|
||||
/// core primitive of Leptos’s reactive system.
|
||||
///
|
||||
/// Takes a reactive [Scope] and the initial value as arguments,
|
||||
/// and returns a tuple containing a [ReadSignal] and a [WriteSignal],
|
||||
/// Takes a reactive [`Scope`] and the initial value as arguments,
|
||||
/// and returns a tuple containing a [`ReadSignal`] and a [`WriteSignal`],
|
||||
/// each of which can be called as a function.
|
||||
///
|
||||
/// ```
|
||||
|
@ -353,7 +352,7 @@ pub fn create_signal<T>(
|
|||
s
|
||||
}
|
||||
|
||||
/// Works exactly as [create_signal], but creates multiple signals at once.
|
||||
/// Works exactly as [`create_signal`], but creates multiple signals at once.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
|
@ -373,7 +372,7 @@ pub fn create_many_signals<T>(
|
|||
cx.runtime.create_many_signals_with_map(cx, values, |x| x)
|
||||
}
|
||||
|
||||
/// Works exactly as [create_many_signals], but applies the map function to each signal pair.
|
||||
/// Works exactly as [`create_many_signals`], but applies the map function to each signal pair.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
|
@ -398,7 +397,7 @@ where
|
|||
}
|
||||
|
||||
/// Creates a signal that always contains the most recent value emitted by a
|
||||
/// [Stream](futures::stream::Stream).
|
||||
/// [`Stream`](futures::stream::Stream).
|
||||
/// If the stream has not yet emitted a value since the signal was created, the signal's
|
||||
/// value will be `None`.
|
||||
///
|
||||
|
@ -419,7 +418,7 @@ pub fn create_signal_from_stream<T>(
|
|||
#[allow(unused_mut)] // allowed because needed for SSR
|
||||
mut stream: impl Stream<Item = T> + Unpin + 'static,
|
||||
) -> ReadSignal<Option<T>> {
|
||||
cfg_if! {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
_ = stream;
|
||||
let (read, _) = create_signal(cx, None);
|
||||
|
@ -445,7 +444,7 @@ pub fn create_signal_from_stream<T>(
|
|||
/// and notifies other code when it has changed. This is the
|
||||
/// core primitive of Leptos’s reactive system.
|
||||
///
|
||||
/// `ReadSignal` is also [Copy] and `'static`, so it can very easily moved into closures
|
||||
/// `ReadSignal` is also [`Copy`] and `'static`, so it can very easily moved into closures
|
||||
/// or copied structs.
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
|
@ -638,7 +637,7 @@ impl<T> SignalWith<T> for ReadSignal<T> {
|
|||
match with_runtime(self.runtime, |runtime| {
|
||||
self.id.try_with(runtime, f, diagnostics)
|
||||
})
|
||||
.expect("runtime to be alive ")
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(o) => o,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
|
@ -816,13 +815,13 @@ impl<T> Copy for ReadSignal<T> {}
|
|||
/// and notifies other code when it has changed. This is the
|
||||
/// core primitive of Leptos’s reactive system.
|
||||
///
|
||||
/// Calling [WriteSignal::update] will mutate the signal’s value in place,
|
||||
/// Calling [`WriteSignal::update`] will mutate the signal’s value in place,
|
||||
/// and notify all subscribers that the signal’s value has changed.
|
||||
///
|
||||
/// `WriteSignal` implements [Fn], such that `set_value(new_value)` is equivalent to
|
||||
/// `WriteSignal` implements [`Fn`], such that `set_value(new_value)` is equivalent to
|
||||
/// `set_value.update(|value| *value = new_value)`.
|
||||
///
|
||||
/// `WriteSignal` is [Copy] and `'static`, so it can very easily moved into closures
|
||||
/// `WriteSignal` is [`Copy`] and `'static`, so it can very easily moved into closures
|
||||
/// or copied structs.
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
|
@ -1131,7 +1130,7 @@ pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
|
|||
}
|
||||
|
||||
/// A signal that combines the getter and setter into one value, rather than
|
||||
/// separating them into a [ReadSignal] and a [WriteSignal]. You may prefer this
|
||||
/// separating them into a [`ReadSignal`] and a [`WriteSignal`]. You may prefer this
|
||||
/// its style, or it may be easier to pass around in a context or as a function argument.
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
|
@ -1724,7 +1723,7 @@ impl<T> RwSignal<T> {
|
|||
/// Returns a write-only handle to the signal.
|
||||
///
|
||||
/// Useful if you're trying to give write access to another component, or split an
|
||||
/// `RwSignal` into a [ReadSignal] and a [WriteSignal].
|
||||
/// [`RwSignal`] into a [`ReadSignal`] and a [`WriteSignal`].
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
|
@ -1873,7 +1872,7 @@ impl NodeId {
|
|||
runtime.update_if_necessary(*self);
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(*self).ok_or(SignalError::Disposed)?;
|
||||
Ok(Rc::clone(&node.value))
|
||||
Ok(node.value())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -1997,13 +1996,14 @@ impl NodeId {
|
|||
None
|
||||
};
|
||||
|
||||
// mark descendants dirty
|
||||
runtime.mark_dirty(*self);
|
||||
|
||||
// notify subscribers
|
||||
if updated.is_some() && !runtime.batching.get() {
|
||||
Runtime::run_effects(runtime_id);
|
||||
};
|
||||
if updated.is_some() {
|
||||
// mark descendants dirty
|
||||
runtime.mark_dirty(*self);
|
||||
|
||||
runtime.run_effects();
|
||||
}
|
||||
|
||||
updated
|
||||
})
|
||||
.unwrap_or_default()
|
||||
|
|
|
@ -21,8 +21,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of readable reactive signal: a [ReadSignal](crate::ReadSignal),
|
||||
/// [Memo](crate::Memo), [RwSignal](crate::RwSignal), or derived signal closure.
|
||||
/// A wrapper for any kind of readable reactive signal: a [`ReadSignal`](crate::ReadSignal),
|
||||
/// [`Memo`](crate::Memo), [`RwSignal`](crate::RwSignal), or derived signal closure.
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `Signal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
|
|
|
@ -18,8 +18,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of settable reactive signal: a [WriteSignal](crate::WriteSignal),
|
||||
/// [RwSignal](crate::RwSignal), or closure that receives a value and sets a signal depending
|
||||
/// A wrapper for any kind of settable reactive signal: a [`WriteSignal`](crate::WriteSignal),
|
||||
/// [`RwSignal`](crate::RwSignal), or closure that receives a value and sets a signal depending
|
||||
/// on it.
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `SignalSetter<T>` as an argument,
|
||||
|
|
|
@ -3,9 +3,9 @@ use crate::{
|
|||
SignalUpdate, SignalWith,
|
||||
};
|
||||
|
||||
/// Derives a reactive slice of an [RwSignal](crate::RwSignal).
|
||||
/// Derives a reactive slice of an [`RwSignal`](crate::RwSignal).
|
||||
///
|
||||
/// Slices have the same guarantees as [Memos](crate::Memo):
|
||||
/// Slices have the same guarantees as [`Memo`s](crate::Memo):
|
||||
/// they only emit their value when it has actually been changed.
|
||||
///
|
||||
/// Slices need a getter and a setter, and you must make sure that
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use cfg_if::cfg_if;
|
||||
use std::future::Future;
|
||||
|
||||
/// Spawns and runs a thread-local [std::future::Future] in a platform-independent way.
|
||||
/// Spawns and runs a thread-local [`Future`] in a platform-independent way.
|
||||
///
|
||||
/// This can be used to interface with any `async` code.
|
||||
pub fn spawn_local<F>(fut: F)
|
||||
|
|
|
@ -7,7 +7,7 @@ use cfg_if::cfg_if;
|
|||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// Exposes the [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
microtask(wasm_bindgen::closure::Closure::once_into_js(task));
|
||||
|
@ -21,7 +21,7 @@ cfg_if! {
|
|||
fn microtask(task: wasm_bindgen::JsValue);
|
||||
}
|
||||
} else {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// Exposes the [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
task();
|
||||
|
|
|
@ -3,17 +3,17 @@ use crate::{with_runtime, RuntimeId, Scope, ScopeProperty};
|
|||
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [StoredValue].
|
||||
/// Unique ID assigned to a [`StoredValue`].
|
||||
pub(crate) struct StoredValueId;
|
||||
}
|
||||
|
||||
/// A **non-reactive** wrapper for any value, which can be created with [store_value].
|
||||
/// A **non-reactive** wrapper for any value, which can be created with [`store_value`].
|
||||
///
|
||||
/// If you want a reactive wrapper, use [create_signal](crate::create_signal).
|
||||
/// If you want a reactive wrapper, use [`create_signal`](crate::create_signal).
|
||||
///
|
||||
/// This allows you to create a stable reference for any value by storing it within
|
||||
/// the reactive system. Like the signal types (e.g., [ReadSignal](crate::ReadSignal)
|
||||
/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// the reactive system. Like the signal types (e.g., [`ReadSignal`](crate::ReadSignal)
|
||||
/// and [`RwSignal`](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -43,7 +43,7 @@ impl<T> StoredValue<T> {
|
|||
/// to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -77,7 +77,7 @@ impl<T> StoredValue<T> {
|
|||
/// to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -128,7 +128,7 @@ impl<T> StoredValue<T> {
|
|||
/// Applies a function to the current stored value.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -155,7 +155,7 @@ impl<T> StoredValue<T> {
|
|||
/// Applies a function to the current stored value.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -375,8 +375,8 @@ impl<T> StoredValue<T> {
|
|||
/// Creates a **non-reactive** wrapper for any value by storing it within
|
||||
/// the reactive system.
|
||||
///
|
||||
/// Like the signal types (e.g., [ReadSignal](crate::ReadSignal)
|
||||
/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// Like the signal types (e.g., [`ReadSignal`](crate::ReadSignal)
|
||||
/// and [`RwSignal`](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
/// ```compile_fail
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
use futures::Future;
|
||||
use std::{borrow::Cow, collections::VecDeque, pin::Pin};
|
||||
|
||||
/// Tracks [Resource](crate::Resource)s that are read under a suspense context,
|
||||
/// Tracks [`Resource`](crate::Resource)s that are read under a suspense context,
|
||||
/// i.e., within a [`Suspense`](https://docs.rs/leptos_core/latest/leptos_core/fn.Suspense.html) component.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SuspenseContext {
|
||||
|
|
252
leptos_reactive/src/trigger.rs
Normal file
252
leptos_reactive/src/trigger.rs
Normal file
|
@ -0,0 +1,252 @@
|
|||
#![forbid(unsafe_code)]
|
||||
|
||||
use crate::{
|
||||
diagnostics,
|
||||
diagnostics::*,
|
||||
node::NodeId,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
Scope, ScopeProperty, SignalGet, SignalSet, SignalUpdate,
|
||||
};
|
||||
|
||||
/// Reactive Trigger, notifies reactive code to rerun.
|
||||
///
|
||||
/// See [`create_trigger`] for more.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Trigger {
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: NodeId,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
/// Notifies any reactive code where this trigger is tracked to rerun.
|
||||
pub fn notify(&self) {
|
||||
assert!(self.try_notify(), "Trigger::notify(): runtime not alive")
|
||||
}
|
||||
|
||||
/// Attempts to notify any reactive code where this trigger is tracked to rerun.
|
||||
///
|
||||
/// Returns `None` if the runtime has been disposed.
|
||||
pub fn try_notify(&self) -> bool {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.mark_dirty(self.id);
|
||||
runtime.run_effects();
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Subscribes the running effect to this trigger.
|
||||
pub fn track(&self) {
|
||||
assert!(self.try_track(), "Trigger::track(): runtime not alive")
|
||||
}
|
||||
|
||||
/// Attempts to subscribe the running effect to this trigger, returning
|
||||
/// `None` if the runtime has been disposed.
|
||||
pub fn try_track(&self) -> bool {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
self.id.subscribe(runtime, diagnostics);
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Trigger`], a kind of reactive primitive.
|
||||
///
|
||||
/// A trigger is a data-less signal with the sole purpose
|
||||
/// of notifying other reactive code of a change. This can be useful
|
||||
/// for when using external data not stored in signals, for example.
|
||||
///
|
||||
/// Take a reactive [`Scope`] and returns the [`Trigger`] handle, which
|
||||
/// can be called as a function to track the trigger in the current
|
||||
/// reactive context.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// use std::{cell::RefCell, fmt::Write, rc::Rc};
|
||||
///
|
||||
/// let external_data = Rc::new(RefCell::new(1));
|
||||
/// let output = Rc::new(RefCell::new(String::new()));
|
||||
///
|
||||
/// let rerun_on_data = create_trigger(cx);
|
||||
///
|
||||
/// let o = output.clone();
|
||||
/// let e = external_data.clone();
|
||||
/// create_effect(cx, move |_| {
|
||||
/// rerun_on_data(); // or rerun_on_data.track();
|
||||
/// write!(o.borrow_mut(), "{}", *e.borrow());
|
||||
/// *e.borrow_mut() += 1;
|
||||
/// });
|
||||
/// # if !cfg!(feature = "ssr") {
|
||||
/// assert_eq!(*output.borrow(), "1");
|
||||
///
|
||||
/// rerun_on_data.notify(); // reruns the above effect
|
||||
///
|
||||
/// assert_eq!(*output.borrow(), "12");
|
||||
/// # }
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(scope = ?cx.id)
|
||||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
pub fn create_trigger(cx: Scope) -> Trigger {
|
||||
let t = cx.runtime.create_trigger();
|
||||
cx.push_scope_property(ScopeProperty::Trigger(t.id));
|
||||
t
|
||||
}
|
||||
|
||||
impl SignalGet<()> for Trigger {
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "Trigger::get()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn get(&self) {
|
||||
self.track()
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "Trigger::try_get()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_get(&self) -> Option<()> {
|
||||
self.try_track().then_some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SignalUpdate<()> for Trigger {
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
name = "Trigger::update()",
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update(&self, f: impl FnOnce(&mut ())) {
|
||||
self.try_update(f).expect("runtime to be alive")
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
name = "Trigger::try_update()",
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_update<O>(&self, f: impl FnOnce(&mut ()) -> O) -> Option<O> {
|
||||
// run callback with runtime before dirtying the trigger,
|
||||
// consistent with signals.
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let res = f(&mut ());
|
||||
|
||||
runtime.mark_dirty(self.id);
|
||||
runtime.run_effects();
|
||||
|
||||
Some(res)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl SignalSet<()> for Trigger {
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "Trigger::set()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn set(&self, _: ()) {
|
||||
self.notify();
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "Trigger::try_set()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_set(&self, _: ()) -> Option<()> {
|
||||
self.try_notify().then_some(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FnOnce<()> for Trigger {
|
||||
type Output = ();
|
||||
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
self.track()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FnMut<()> for Trigger {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.track()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl Fn<()> for Trigger {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.track()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue