fix: memo with_untracked (#1213)

This commit is contained in:
Lukas Potthast 2023-06-21 16:13:18 +02:00 committed by GitHub
parent a9cbcce8b2
commit bbc7799b7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 13 deletions

View file

@ -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 whats happening.** This is by far - **Enhance ergonomics without obfuscating whats happening.** This is by far
the hardest to achieve. Its often the case that adding additional layers to the hardest to achieve. Its 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

View file

@ -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

View file

@ -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()

View file

@ -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() {