mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Merge pull request #1579 from leptos-rs/rusty
feat: start adding some Rustier interfaces for reactive types
This commit is contained in:
commit
70e1ad41e2
7 changed files with 433 additions and 17 deletions
|
@ -3,7 +3,7 @@ use leptos::{ev, html::*, *};
|
|||
/// A simple counter view.
|
||||
// A component is really just a function call: it runs once to create the DOM and reactive system
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
let (count, set_count) = create_signal(Count::new(initial_value, step));
|
||||
let count = RwSignal::new(Count::new(initial_value, step));
|
||||
|
||||
// the function name is the same as the HTML tag name
|
||||
div()
|
||||
|
@ -17,18 +17,14 @@ pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
|||
// typed events found in leptos::ev
|
||||
// 1) prevent typos in event names
|
||||
// 2) allow for correct type inference in callbacks
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.on(ev::click, move |_| count.update(Count::clear))
|
||||
.child("Clear"),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.decrease())
|
||||
})
|
||||
.on(ev::click, move |_| count.update(Count::decrease))
|
||||
.child("-1"),
|
||||
span().child(("Value: ", move || count.get().value(), "!")),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.increase())
|
||||
})
|
||||
.on(ev::click, move |_| count.update(Count::increase))
|
||||
.child("+1"),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
|
|||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static) -> Effect
|
||||
pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static) -> Effect<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
|
@ -69,15 +69,127 @@ where
|
|||
_ = with_runtime( |runtime| {
|
||||
runtime.update_if_necessary(id);
|
||||
});
|
||||
Effect { id }
|
||||
Effect { id, ty: PhantomData }
|
||||
} else {
|
||||
// clear warnings
|
||||
_ = f;
|
||||
Effect::default()
|
||||
Effect { id: Default::default(), ty: PhantomData }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Effect<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Effects run a certain chunk of code whenever the signals they depend on change.
|
||||
/// `create_effect` immediately runs the given function once, tracks its dependence
|
||||
/// on any signal values read within it, and reruns the function whenever the value
|
||||
/// of a dependency changes.
|
||||
///
|
||||
/// 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)).
|
||||
///
|
||||
/// 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`].
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
/// // ✅ use effects to interact between reactive state and the outside world
|
||||
/// Effect::new(move |_| {
|
||||
/// // immediately prints "Value: 0" and subscribes to `a`
|
||||
/// log::debug!("Value: {}", a.get());
|
||||
/// });
|
||||
///
|
||||
/// a.set(1);
|
||||
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
|
||||
///
|
||||
/// // ❌ don't use effects to synchronize state within the reactive system
|
||||
/// Effect::new(move |_| {
|
||||
/// // this technically works but can cause unnecessary re-renders
|
||||
/// // and easily lead to problems like infinite loops
|
||||
/// b.set(a.get() + 1);
|
||||
/// });
|
||||
/// # if !cfg!(feature = "ssr") {
|
||||
/// # assert_eq!(b.get(), 2);
|
||||
/// # }
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn new(f: impl Fn(Option<T>) -> T + 'static) -> Self {
|
||||
create_effect(f)
|
||||
}
|
||||
|
||||
/// Creates an effect; unlike effects created by [`create_effect`], isomorphic effects will run on
|
||||
/// the server as well as the client.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
/// // ✅ use effects to interact between reactive state and the outside world
|
||||
/// Effect::new_isomorphic(move |_| {
|
||||
/// // immediately prints "Value: 0" and subscribes to `a`
|
||||
/// log::debug!("Value: {}", a.get());
|
||||
/// });
|
||||
///
|
||||
/// a.set(1);
|
||||
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
|
||||
///
|
||||
/// // ❌ don't use effects to synchronize state within the reactive system
|
||||
/// Effect::new_isomorphic(move |_| {
|
||||
/// // this technically works but can cause unnecessary re-renders
|
||||
/// // and easily lead to problems like infinite loops
|
||||
/// b.set(a.get() + 1);
|
||||
/// });
|
||||
/// # assert_eq!(b.get(), 2);
|
||||
/// # runtime.dispose();
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn new_isomorphic(f: impl Fn(Option<T>) -> T + 'static) -> Self {
|
||||
create_isomorphic_effect(f)
|
||||
}
|
||||
|
||||
/// Applies the given closure to the most recent value of the effect.
|
||||
///
|
||||
/// Because effect functions can return values, each time an effect runs it
|
||||
/// consumes its previous value. This allows an effect to store additional state
|
||||
/// (like a DOM node, a timeout handle, or a type that implements `Drop`) and
|
||||
/// keep it alive across multiple runs.
|
||||
///
|
||||
/// This method allows access to the effect’s value outside the effect function.
|
||||
/// The next time a signal change causes the effect to run, it will receive the
|
||||
/// mutated value.
|
||||
pub fn with_value_mut<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut Option<T>) -> U,
|
||||
) -> Option<U> {
|
||||
with_runtime(|runtime| {
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(self.id)?;
|
||||
let value = node.value.clone()?;
|
||||
let mut value = value.borrow_mut();
|
||||
let value = value.downcast_mut()?;
|
||||
Some(f(value))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an effect; unlike effects created by [`create_effect`], isomorphic effects will run on
|
||||
/// the server as well as the client.
|
||||
/// ```
|
||||
|
@ -118,7 +230,7 @@ where
|
|||
#[inline(always)]
|
||||
pub fn create_isomorphic_effect<T>(
|
||||
f: impl Fn(Option<T>) -> T + 'static,
|
||||
) -> Effect
|
||||
) -> Effect<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
|
@ -128,7 +240,10 @@ where
|
|||
_ = with_runtime(|runtime| {
|
||||
runtime.update_if_necessary(id);
|
||||
});
|
||||
Effect { id }
|
||||
Effect {
|
||||
id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -152,17 +267,18 @@ where
|
|||
|
||||
/// A handle to an effect, can be used to explicitly dispose of the effect.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Effect {
|
||||
pub struct Effect<T> {
|
||||
pub(crate) id: NodeId,
|
||||
ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl From<Effect> for Disposer {
|
||||
fn from(effect: Effect) -> Self {
|
||||
impl<T> From<Effect<T>> for Disposer {
|
||||
fn from(effect: Effect<T>) -> Self {
|
||||
Disposer(effect.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignalDispose for Effect {
|
||||
impl<T> SignalDispose for Effect<T> {
|
||||
fn dispose(self) {
|
||||
drop(Disposer::from(self));
|
||||
}
|
||||
|
|
|
@ -165,6 +165,58 @@ where
|
|||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl<T> Memo<T> {
|
||||
/// Creates a new memo from the given function.
|
||||
///
|
||||
/// This is identical to [`create_memo`].
|
||||
/// /// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # let runtime = create_runtime();
|
||||
/// let value = RwSignal::new(0);
|
||||
///
|
||||
/// // 🆗 we could create a derived signal with a simple function
|
||||
/// let double_value = move || value.get() * 2;
|
||||
/// value.set(2);
|
||||
/// assert_eq!(double_value(), 4);
|
||||
///
|
||||
/// // but imagine the computation is really expensive
|
||||
/// let expensive = move || really_expensive_computation(value.get()); // lazy: doesn't run until called
|
||||
/// Effect::new(move |_| {
|
||||
/// // 🆗 run #1: calls `really_expensive_computation` the first time
|
||||
/// log::debug!("expensive = {}", expensive());
|
||||
/// });
|
||||
/// Effect::new(move |_| {
|
||||
/// // ❌ run #2: this calls `really_expensive_computation` a second time!
|
||||
/// let value = expensive();
|
||||
/// // do something else...
|
||||
/// });
|
||||
///
|
||||
/// // instead, we create a memo
|
||||
/// // 🆗 run #1: the calculation runs once immediately
|
||||
/// let memoized = Memo::new(move |_| really_expensive_computation(value.get()));
|
||||
/// Effect::new(move |_| {
|
||||
/// // 🆗 reads the current value of the memo
|
||||
/// // can be `memoized()` on nightly
|
||||
/// log::debug!("memoized = {}", memoized.get());
|
||||
/// });
|
||||
/// Effect::new(move |_| {
|
||||
/// // ✅ reads the current value **without re-running the calculation**
|
||||
/// let value = memoized.get();
|
||||
/// // do something else...
|
||||
/// });
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new(f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
{
|
||||
create_memo(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Memo<T>
|
||||
where
|
||||
T: 'static,
|
||||
|
|
|
@ -904,6 +904,139 @@ where
|
|||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl<S, T> Resource<S, T>
|
||||
where
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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()`].
|
||||
///
|
||||
/// This is identical with [`create_resource`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn fetch_cat_picture_urls(how_many: i32) -> Vec<String> {
|
||||
/// // pretend we're fetching cat pics
|
||||
/// vec![how_many.to_string()]
|
||||
/// }
|
||||
///
|
||||
/// // a signal that controls how many cat pics we want
|
||||
/// let (how_many_cats, set_how_many_cats) = create_signal(1);
|
||||
///
|
||||
/// // create a resource that will refetch whenever `how_many_cats` changes
|
||||
/// # // `csr`, `hydrate`, and `ssr` all have issues here
|
||||
/// # // because we're not running in a browser or in Tokio. Let's just ignore it.
|
||||
/// # if false {
|
||||
/// let cats = Resource::new(move || how_many_cats.get(), fetch_cat_picture_urls);
|
||||
///
|
||||
/// // when we read the signal, it contains either
|
||||
/// // 1) None (if the Future isn't ready yet) or
|
||||
/// // 2) Some(T) (if the future's already resolved)
|
||||
/// assert_eq!(cats.read(), Some(vec!["1".to_string()]));
|
||||
///
|
||||
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
|
||||
/// set_how_many_cats.set(2);
|
||||
/// assert_eq!(cats.read(), Some(vec!["2".to_string()]));
|
||||
/// # }
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new<Fu>(
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Clone + 'static,
|
||||
T: Serializable + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
create_resource(source, fetcher)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Unlike [`create_resource()`], this [`Future`] is always run on the local system
|
||||
/// and therefore it's result type does not need to be [`Serializable`].
|
||||
///
|
||||
/// This is identical with [`create_local_resource`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// #[derive(Debug, Clone)] // doesn't implement Serialize, Deserialize
|
||||
/// struct ComplicatedUnserializableStruct {
|
||||
/// // something here that can't be serialized
|
||||
/// }
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn setup_complicated_struct() -> ComplicatedUnserializableStruct {
|
||||
/// // do some work
|
||||
/// ComplicatedUnserializableStruct {}
|
||||
/// }
|
||||
///
|
||||
/// // create the resource; it will run but not be serialized
|
||||
/// # if cfg!(not(any(feature = "csr", feature = "hydrate"))) {
|
||||
/// let result =
|
||||
/// create_local_resource(move || (), |_| setup_complicated_struct());
|
||||
/// # }
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn local<Fu>(
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Clone + 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let initial_value = None;
|
||||
create_local_resource_with_initial_value(source, fetcher, initial_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Resource<(), T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Creates a resource that will only load once, and will not respond
|
||||
/// to any reactive changes, including changes in any reactive variables
|
||||
/// read in its fetcher.
|
||||
///
|
||||
/// This identical to `create_resource(|| (), move |_| fetcher())`.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn once<Fu>(fetcher: impl Fn() -> Fu + 'static) -> Resource<(), T>
|
||||
where
|
||||
T: Serializable + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
create_resource(|| (), move |_| fetcher())
|
||||
}
|
||||
}
|
||||
|
||||
// Resources
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [`Resource`](crate::Resource).
|
||||
|
|
|
@ -124,6 +124,50 @@ impl<T> Selector<T>
|
|||
where
|
||||
T: PartialEq + Eq + Clone + Hash + 'static,
|
||||
{
|
||||
/// 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`].)
|
||||
///
|
||||
/// **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`)
|
||||
/// because it reduces them from `O(n)` to `O(1)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use std::rc::Rc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # let runtime = create_runtime();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let is_selected = Selector::new(move || a.get());
|
||||
/// let total_notifications = Rc::new(RefCell::new(0));
|
||||
/// let not = Rc::clone(&total_notifications);
|
||||
/// create_isomorphic_effect({
|
||||
/// let is_selected = is_selected.clone();
|
||||
/// move |_| {
|
||||
/// if is_selected.selected(5) {
|
||||
/// *not.borrow_mut() += 1;
|
||||
/// }
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(is_selected.selected(5), false);
|
||||
/// assert_eq!(*total_notifications.borrow(), 0);
|
||||
/// a.set(5);
|
||||
/// assert_eq!(is_selected.selected(5), true);
|
||||
/// assert_eq!(*total_notifications.borrow(), 1);
|
||||
/// a.set(5);
|
||||
/// assert_eq!(is_selected.selected(5), true);
|
||||
/// assert_eq!(*total_notifications.borrow(), 1);
|
||||
/// a.set(4);
|
||||
/// assert_eq!(is_selected.selected(5), false);
|
||||
/// # runtime.dispose()
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new(source: impl Fn() -> T + Clone + 'static) -> Self {
|
||||
create_selector_with_fn(source, PartialEq::eq)
|
||||
}
|
||||
|
||||
/// Reactively checks whether the given key is selected.
|
||||
pub fn selected(&self, key: T) -> bool {
|
||||
let owner = self.owner;
|
||||
|
|
|
@ -1790,6 +1790,35 @@ impl<T> SignalDispose for RwSignal<T> {
|
|||
}
|
||||
|
||||
impl<T> RwSignal<T> {
|
||||
/// Creates a reactive signal with the getter and setter unified in one value.
|
||||
/// You may prefer this style, or it may be easier to pass around in a context
|
||||
/// or as a function argument.
|
||||
///
|
||||
/// This is identical to [`create_rw_signal`].
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// let count = RwSignal::new(0);
|
||||
///
|
||||
/// // ✅ set the value
|
||||
/// count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you can call the getter within the setter
|
||||
/// // count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
|
||||
/// count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
/// # runtime.dispose();
|
||||
/// #
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
create_rw_signal(value)
|
||||
}
|
||||
|
||||
/// Returns a read-only handle to the signal.
|
||||
///
|
||||
/// Useful if you're trying to give read access to another component but ensure that it can't write
|
||||
|
|
|
@ -308,4 +308,50 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
/// ```compile_fail
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // this structure is neither `Copy` nor `Clone`
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
///
|
||||
/// // ❌ this won't compile, as it can't be cloned or copied into the closures
|
||||
/// let data = MyUncloneableData { value: "a".into() };
|
||||
/// let callback_a = move || data.value == "a";
|
||||
/// let callback_b = move || data.value == "b";
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // this structure is neither `Copy` nor `Clone`
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
///
|
||||
/// // ✅ you can move the `StoredValue` and access it with .with_value()
|
||||
/// let data = StoredValue::new(MyUncloneableData { value: "a".into() });
|
||||
/// let callback_a = move || data.with_value(|data| data.value == "a");
|
||||
/// let callback_b = move || data.with_value(|data| data.value == "b");
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
///
|
||||
/// ## Panics
|
||||
/// Panics if there is no current reactive runtime.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
store_value(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl_get_fn_traits!(StoredValue(get_value));
|
||||
|
|
Loading…
Reference in a new issue