docs: working on memo docs

This commit is contained in:
Greg Johnston 2024-06-28 15:19:55 -04:00
parent 44eae4c2ed
commit a2d8fde8cf
1 changed files with 83 additions and 0 deletions

View File

@ -7,6 +7,64 @@ use crate::{
};
use std::{fmt::Debug, hash::Hash, panic::Location};
/// A memo is an efficient derived reactive value based on other reactive values.
///
/// Unlike a "derived signal," a memo comes with two guarantees:
/// 1. The memo will only run *once* per change, no matter how many times you
/// access its value.
/// 2. The memo will only notify its dependents if the value of the computation changes.
///
/// This makes a memo the perfect tool for expensive computations.
///
/// Memos have a certain overhead compared to derived signals. In most cases, you should
/// create a derived signal. But if the derivation calculation is expensive, you should
/// create a memo.
///
/// Memos are lazy: they do not run at all until they are read for the first time, and they will
/// not re-run the calculation when a source signal changes until they are read again.
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # fn really_expensive_computation(value: i32) -> i32 { value };
/// let (value, set_value) = signal(0);
///
/// // 🆗 we could create a derived signal with a simple function
/// let double_value = move || value.get() * 2;
/// set_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
/// println!("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
/// println!("memoized = {}", memoized.get());
/// });
/// Effect::new(move |_| {
/// // ✅ reads the current value **without re-running the calculation**
/// let value = memoized.get();
/// // do something else...
/// });
/// # });
/// ```
pub struct Memo<T> {
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
@ -36,6 +94,25 @@ impl<T: Send + Sync + 'static> Memo<T> {
feature = "tracing",
tracing::instrument(level = "debug", skip_all,)
)]
/// Creates a new memoized, computed reactive value.
///
/// As with an [`Effect`](crate::effect::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.
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # fn really_expensive_computation(value: i32) -> i32 { value };
/// let (value, set_value) = signal(0);
///
/// // the memo will reactively update whenever `value` changes
/// let memoized =
/// Memo::new(move |_| really_expensive_computation(value.get()));
/// # });
/// ```
pub fn new(fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static) -> Self
where
T: PartialEq,
@ -52,6 +129,12 @@ impl<T: Send + Sync + 'static> Memo<T> {
feature = "tracing",
tracing::instrument(level = "trace", skip_all,)
)]
/// Creates a new memo with a custom comparison function. By default, memos simply use
/// [`PartialEq`] to compare the previous value to the new value. Passing a custom comparator
/// allows you to compare the old and new values using any criteria.
///
/// `changed` should be a function that returns `true` if the new value is different from the
/// old value.
pub fn new_with_compare(
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
changed: fn(Option<&T>, Option<&T>) -> bool,