mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
fix: memo with_untracked (#1213)
This commit is contained in:
parent
a9cbcce8b2
commit
bbc7799b7c
5 changed files with 59 additions and 13 deletions
|
@ -42,7 +42,7 @@ Leptos, as a framework, reflects certain technical values:
|
||||||
- **Embrace Rust semantics.** Especially in things like UI templating, use Rust
|
- **Embrace Rust semantics.** Especially in things like UI templating, use Rust
|
||||||
semantics or extend them in a predictable way with control-flow components
|
semantics or extend them in a predictable way with control-flow components
|
||||||
rather than overloading the meaning of Rust terms like `if` or `for` in a
|
rather than overloading the meaning of Rust terms like `if` or `for` in a
|
||||||
framework-speciic way.
|
framework-specific way.
|
||||||
- **Enhance ergonomics without obfuscating what’s happening.** This is by far
|
- **Enhance ergonomics without obfuscating what’s happening.** This is by far
|
||||||
the hardest to achieve. It’s often the case that adding additional layers to
|
the hardest to achieve. It’s often the case that adding additional layers to
|
||||||
improve DX (like a custom build tool and starter templates) comes across as
|
improve DX (like a custom build tool and starter templates) comes across as
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
[![Discord](https://img.shields.io/discord/1031524867910148188?color=%237289DA&label=discord)](https://discord.gg/YdRAhS7eQB)
|
[![Discord](https://img.shields.io/discord/1031524867910148188?color=%237289DA&label=discord)](https://discord.gg/YdRAhS7eQB)
|
||||||
[![Matrix](https://img.shields.io/badge/Matrix-leptos-grey?logo=matrix&labelColor=white&logoColor=black)](https://matrix.to/#/#leptos:matrix.org)
|
[![Matrix](https://img.shields.io/badge/Matrix-leptos-grey?logo=matrix&labelColor=white&logoColor=black)](https://matrix.to/#/#leptos:matrix.org)
|
||||||
|
|
||||||
|
|
||||||
[Website](https://leptos.dev) | [Book](https://leptos-rs.github.io/leptos/) | [Docs.rs](https://docs.rs/leptos/latest/leptos/) | [Playground](https://codesandbox.io/p/sandbox/leptos-rtfggt?file=%2Fsrc%2Fmain.rs%3A1%2C1) | [Discord](https://discord.gg/YdRAhS7eQB)
|
[Website](https://leptos.dev) | [Book](https://leptos-rs.github.io/leptos/) | [Docs.rs](https://docs.rs/leptos/latest/leptos/) | [Playground](https://codesandbox.io/p/sandbox/leptos-rtfggt?file=%2Fsrc%2Fmain.rs%3A1%2C1) | [Discord](https://discord.gg/YdRAhS7eQB)
|
||||||
|
|
||||||
# Leptos
|
# Leptos
|
||||||
|
|
|
@ -7,6 +7,16 @@ use crate::{
|
||||||
};
|
};
|
||||||
use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
|
use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
|
// IMPLEMENTATION NOTE:
|
||||||
|
// Memos are implemented "lazily," i.e., the inner computation is not run
|
||||||
|
// when the memo is created or when its value is marked as stale, but on demand
|
||||||
|
// when it is accessed, if the value is stale. This means that the value is stored
|
||||||
|
// internally as Option<T>, even though it can always be accessed by the user as T.
|
||||||
|
// This means the inner value can be unwrapped in circumstances in which we know
|
||||||
|
// `Runtime::update_if_necessary()` has already been called, e.g., in the
|
||||||
|
// `.try_with_no_subscription()` calls below that are unwrapped with
|
||||||
|
// `.expect("invariant: must have already been initialized")`.
|
||||||
|
|
||||||
/// Creates an efficient derived reactive value based on other reactive values.
|
/// Creates an efficient derived reactive value based on other reactive values.
|
||||||
///
|
///
|
||||||
/// Unlike a "derived signal," a memo comes with two guarantees:
|
/// Unlike a "derived signal," a memo comes with two guarantees:
|
||||||
|
@ -199,6 +209,17 @@ impl<T> PartialEq for Memo<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn forward_ref_to<T, O, F: FnOnce(&T) -> O>(
|
||||||
|
f: F,
|
||||||
|
) -> impl FnOnce(&Option<T>) -> O {
|
||||||
|
|maybe_value: &Option<T>| {
|
||||||
|
let ref_t = maybe_value
|
||||||
|
.as_ref()
|
||||||
|
.expect("invariant: must have already been initialized");
|
||||||
|
f(ref_t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
|
impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
any(debug_assertions, feature = "ssr"),
|
any(debug_assertions, feature = "ssr"),
|
||||||
|
@ -215,7 +236,11 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
|
||||||
)]
|
)]
|
||||||
fn get_untracked(&self) -> T {
|
fn get_untracked(&self) -> T {
|
||||||
with_runtime(self.runtime, move |runtime| {
|
with_runtime(self.runtime, move |runtime| {
|
||||||
let f = move |maybe_value: &Option<T>| maybe_value.clone().unwrap();
|
let f = |maybe_value: &Option<T>| {
|
||||||
|
maybe_value
|
||||||
|
.clone()
|
||||||
|
.expect("invariant: must have already been initialized")
|
||||||
|
};
|
||||||
match self.id.try_with_no_subscription(runtime, f) {
|
match self.id.try_with_no_subscription(runtime, f) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => panic_getting_dead_memo(
|
Err(_) => panic_getting_dead_memo(
|
||||||
|
@ -261,10 +286,8 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||||
// Unwrapping here is fine for the same reasons as <Memo as
|
|
||||||
// UntrackedSignal>::get_untracked
|
|
||||||
with_runtime(self.runtime, |runtime| {
|
with_runtime(self.runtime, |runtime| {
|
||||||
match self.id.try_with_no_subscription(runtime, |v: &T| f(v)) {
|
match self.id.try_with_no_subscription(runtime, forward_ref_to(f)) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => panic_getting_dead_memo(
|
Err(_) => panic_getting_dead_memo(
|
||||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||||
|
@ -394,15 +417,13 @@ impl<T> SignalWith<T> for Memo<T> {
|
||||||
)]
|
)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||||
// memo is stored as Option<T>, but will always have T available
|
|
||||||
// after latest_value() called, so we can unwrap safely
|
|
||||||
let f = move |maybe_value: &Option<T>| f(maybe_value.as_ref().unwrap());
|
|
||||||
|
|
||||||
let diagnostics = diagnostics!(self);
|
let diagnostics = diagnostics!(self);
|
||||||
|
|
||||||
with_runtime(self.runtime, |runtime| {
|
with_runtime(self.runtime, |runtime| {
|
||||||
self.id.subscribe(runtime, diagnostics);
|
self.id.subscribe(runtime, diagnostics);
|
||||||
self.id.try_with_no_subscription(runtime, f).ok()
|
self.id
|
||||||
|
.try_with_no_subscription(runtime, forward_ref_to(f))
|
||||||
|
.ok()
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
|
@ -13,6 +13,32 @@ fn basic_memo() {
|
||||||
.dispose()
|
.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "stable"))]
|
||||||
|
#[test]
|
||||||
|
fn signal_with_untracked() {
|
||||||
|
use leptos_reactive::SignalWithUntracked;
|
||||||
|
|
||||||
|
create_scope(create_runtime(), |cx| {
|
||||||
|
let m = create_memo(cx, move |_| 5);
|
||||||
|
let copied_out = m.with_untracked(|value| *value);
|
||||||
|
assert_eq!(copied_out, 5);
|
||||||
|
})
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "stable"))]
|
||||||
|
#[test]
|
||||||
|
fn signal_get_untracked() {
|
||||||
|
use leptos_reactive::SignalGetUntracked;
|
||||||
|
|
||||||
|
create_scope(create_runtime(), |cx| {
|
||||||
|
let m = create_memo(cx, move |_| "memo".to_owned());
|
||||||
|
let cloned_out = m.get_untracked();
|
||||||
|
assert_eq!(cloned_out, "memo".to_owned());
|
||||||
|
})
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "stable"))]
|
#[cfg(not(feature = "stable"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn memo_with_computed_value() {
|
fn memo_with_computed_value() {
|
||||||
|
|
Loading…
Reference in a new issue