Просмотр исходного кода

Merge branch 'master' into jk/documet-everything

Jonathan Kelley 3 лет назад
Родитель
Сommit
e71069883d
1 измененных файлов с 191 добавлено и 21 удалено
  1. 191 21
      packages/hooks/src/useref.rs

+ 191 - 21
packages/hooks/src/useref.rs

@@ -1,57 +1,227 @@
+use dioxus_core::ScopeState;
 use std::{
     cell::{Ref, RefCell, RefMut},
     rc::Rc,
 };
 
-use dioxus_core::ScopeState;
-
-pub fn use_ref<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> &'a UseRef<T> {
+/// `use_ref` is a key foundational hook for storing state in Dioxus.
+///
+/// It is different that `use_state` in that the value stored is not "immutable".
+/// Instead, UseRef is designed to store larger values that will be mutated at will.
+///
+/// ## Writing Values
+///
+/// Generally, `use_ref` is just a wrapper around a RefCell that tracks mutable
+/// writes through the `write` method. Whenever `write` is called, the component
+/// that initialized the hook will be marked as "dirty".
+///
+/// ```rust
+/// let val = use_ref(|| HashMap::<u32, String>::new());
+///
+/// // using `write` will give us a `RefMut` to the inner value, which we can call methods on
+/// // This marks the component as "dirty"
+/// val.write().insert(1, "hello".to_string());
+/// ```
+///
+/// You can avoid this defualt behavior with `write_silent`
+///
+/// ```
+/// // with `write_silent`, the component will not be re-rendered
+/// val.write_silent().insert(2, "goodbye".to_string());
+/// ```
+///
+/// ## Reading Values
+///
+/// To read values out of the refcell, you can use the `read` method which will retrun a `Ref`.
+///
+/// ```rust
+/// let map: Ref<_> = val.read();
+///
+/// let item = map.get(&1);
+/// ```
+///
+/// To get an &T out of the RefCell, you need to "reborrow" through the Ref:
+///
+/// ```rust
+/// let read = val.read();
+/// let map = &*read;
+/// ```
+///
+/// ## Collections and iteration
+///
+/// A common usecase for `use_ref` is to store a large amount of data in a component.
+/// Typically this will be a collection like a HashMap or a Vec. To create new
+/// elements from the collection, we can use `read()` directly in our rsx!.
+///
+/// ```rust
+/// rsx!{
+///     val.read().iter().map(|(k, v)| {
+///         rsx!{ key: "{k}", value: "{v}" }
+///     })
+/// }
+/// ```
+///
+/// If you are generating elements outside of `rsx!` then you might need to call
+/// "render" inside the iterator. For some cases you might need to collect into
+/// a temporary Vec.
+///
+/// ```rust
+/// let items = val.read().iter().map(|(k, v)| {
+///     cx.render(rsx!{ key: "{k}", value: "{v}" })
+/// });
+///
+/// // collect into a Vec
+///
+/// let items: Vec<Element> = items.collect();
+/// ```
+///
+/// ## Use in Async
+///
+/// To access values from a `UseRef` in an async context, you need to detach it
+/// from the current scope's lifetime, making it a `'static` value. This is done
+/// by simply calling `ToOnwed` or `Clone`.
+///
+/// ```rust
+/// let val = use_ref(|| HashMap::<u32, String>::new());
+///
+/// cx.spawn({
+///     let val = val.clone();
+///     async move {
+///         some_work().await;
+///         val.write().insert(1, "hello".to_string());
+///     }
+/// })
+/// ```
+///
+/// If you're working with lots of values like UseState and UseRef, you can use the
+/// `clone!` macro to make it easier to write the above code.
+///
+/// ```rust
+/// let val1 = use_ref(|| HashMap::<u32, String>::new());
+/// let val2 = use_ref(|| HashMap::<u32, String>::new());
+/// let val3 = use_ref(|| HashMap::<u32, String>::new());
+///
+/// cx.spawn({
+///     clone![val1, val2, val3];
+///     async move {
+///         some_work().await;
+///         val.write().insert(1, "hello".to_string());
+///     }
+/// })
+/// ```
+pub fn use_ref<'a, T: 'static>(
+    cx: &'a ScopeState,
+    initialize_refcell: impl FnOnce() -> T,
+) -> &'a UseRef<T> {
     cx.use_hook(|_| UseRef {
-        update_callback: cx.schedule_update(),
-        value: Rc::new(RefCell::new(f())),
+        update: cx.schedule_update(),
+        value: Rc::new(RefCell::new(initialize_refcell())),
     })
 }
 
+/// A type created by the [`use_ref`] hook. See its documentation for more details.
 pub struct UseRef<T> {
-    update_callback: Rc<dyn Fn()>,
+    update: Rc<dyn Fn()>,
     value: Rc<RefCell<T>>,
 }
 
+impl<T> Clone for UseRef<T> {
+    fn clone(&self) -> Self {
+        Self {
+            update: self.update.clone(),
+            value: self.value.clone(),
+        }
+    }
+}
+
 impl<T> UseRef<T> {
+    /// Read the value in the RefCell into a `Ref`. If this method is called
+    /// while other values are still being `read` or `write`, then your app will crash.
+    ///
+    /// Be very careful when working with this method. If you can, consider using
+    /// the `with` and `with_mut` methods instead, choosing to render Elements
+    /// during the read calls.
     pub fn read(&self) -> Ref<'_, T> {
         self.value.borrow()
     }
 
+    /// Set the curernt value to `new_value`. This will mark the component as "dirty"
+    ///
+    /// This change will propogate immediately, so any other contexts that are
+    /// using this RefCell will also be affected. If called during an async context,
+    /// the component will not be re-rendered until the next `.await` call.
     pub fn set(&self, new: T) {
         *self.value.borrow_mut() = new;
         self.needs_update();
     }
 
-    pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
-        (self.read(), self)
-    }
-
-    /// Calling "write" will force the component to re-render
+    /// Mutably unlock the value in the RefCell. This will mark the component as "dirty"
+    ///
+    /// Uses to `write` should be as short as possible.
+    ///
+    /// Be very careful when working with this method. If you can, consider using
+    /// the `with` and `with_mut` methods instead, choosing to render Elements
+    /// during the read and write calls.
     pub fn write(&self) -> RefMut<'_, T> {
         self.needs_update();
         self.value.borrow_mut()
     }
 
-    /// Allows the ability to write the value without forcing a re-render
+    /// Mutably unlock the value in the RefCell. This will not mark the component as dirty.
+    /// This is useful if you want to do some work without causing the component to re-render.
+    ///
+    /// Uses to `write` should be as short as possible.
+    ///
+    /// Be very careful when working with this method. If you can, consider using
+    /// the `with` and `with_mut` methods instead, choosing to render Elements
     pub fn write_silent(&self) -> RefMut<'_, T> {
         self.value.borrow_mut()
     }
 
-    pub fn needs_update(&self) {
-        (self.update_callback)();
+    /// Take a reference to the inner value termporarily and produce a new value
+    ///
+    /// Note: You can always "reborrow" the value through the RefCell.
+    /// This method just does it for you automatically.
+    ///
+    /// ```rust
+    /// let val = use_ref(|| HashMap::<u32, String>::new());
+    ///
+    ///
+    /// // use reborrowing
+    /// let inner = &*val.read();
+    ///
+    /// // or, be safer and use `with`
+    /// val.with(|i| println!("{:?}", i));
+    /// ```
+    pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
+        immutable_callback(&*self.read())
     }
-}
 
-impl<T> Clone for UseRef<T> {
-    fn clone(&self) -> Self {
-        Self {
-            update_callback: self.update_callback.clone(),
-            value: self.value.clone(),
-        }
+    /// Take a reference to the inner value termporarily and produce a new value,
+    /// modifying the original in place.
+    ///
+    /// Note: You can always "reborrow" the value through the RefCell.
+    /// This method just does it for you automatically.
+    ///
+    /// ```rust
+    /// let val = use_ref(|| HashMap::<u32, String>::new());
+    ///
+    ///
+    /// // use reborrowing
+    /// let inner = &mut *val.write();
+    ///
+    /// // or, be safer and use `with`
+    /// val.with_mut(|i| i.insert(1, "hi"));
+    /// ```
+    pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
+        mutable_callback(&mut *self.write())
+    }
+
+    /// Call the inner callback to mark the originator component as dirty.
+    ///
+    /// This will cause the component to be re-rendered after the current scope
+    /// has ended or the current async task has been yielded through await.
+    pub fn needs_update(&self) {
+        (self.update)();
     }
 }