Преглед изворни кода

chore: finish tweaking context api

Jonathan Kelley пре 2 година
родитељ
комит
df4a1b6c7a

+ 2 - 1
packages/core/src/create.rs

@@ -1,4 +1,5 @@
 use std::cell::Cell;
+use std::rc::Rc;
 
 use crate::innerlude::{VComponent, VText};
 use crate::mutations::Mutation;
@@ -340,7 +341,7 @@ impl<'b> VirtualDom {
         }
 
         // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
-        let boundary = match self.scopes[scope.0].has_context::<SuspenseContext>() {
+        let boundary = match self.scopes[scope.0].has_context::<Rc<SuspenseContext>>() {
             Some(boundary) => boundary,
             _ => return created,
         };

+ 2 - 2
packages/core/src/scheduler/wait.rs

@@ -34,9 +34,9 @@ impl VirtualDom {
         }
     }
 
-    pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> Rc<SuspenseContext> {
+    pub(crate) fn acquire_suspense_boundary<'a>(&'a self, id: ScopeId) -> Rc<SuspenseContext> {
         self.scopes[id.0]
-            .consume_context::<SuspenseContext>()
+            .consume_context::<Rc<SuspenseContext>>()
             .unwrap()
     }
 

+ 16 - 16
packages/core/src/scopes.rs

@@ -81,7 +81,7 @@ pub struct ScopeState {
     pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
     pub(crate) hook_idx: Cell<usize>,
 
-    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
+    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
 
     pub(crate) tasks: Rc<Scheduler>,
     pub(crate) spawned_tasks: HashSet<TaskId>,
@@ -250,17 +250,18 @@ impl<'src> ScopeState {
     }
 
     /// Return any context of type T if it exists on this scope
-    pub fn has_context<T: 'static>(&self) -> Option<Rc<T>> {
+    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
         self.shared_contexts
             .borrow()
             .get(&TypeId::of::<T>())?
-            .clone()
-            .downcast()
-            .ok()
+            .downcast_ref::<T>()
+            .cloned()
     }
 
     /// Try to retrieve a shared state with type `T` from any parent scope.
-    pub fn consume_context<T: 'static>(&self) -> Option<Rc<T>> {
+    ///
+    /// Clones the state if it exists.
+    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
         if let Some(this_ctx) = self.has_context() {
             return Some(this_ctx);
         }
@@ -270,7 +271,7 @@ impl<'src> ScopeState {
             // safety: all parent pointers are valid thanks to the bump arena
             let parent = unsafe { &*parent_ptr };
             if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
-                return shared.clone().downcast().ok();
+                return shared.downcast_ref::<T>().cloned();
             }
             search_parent = parent.parent;
         }
@@ -303,15 +304,14 @@ impl<'src> ScopeState {
     ///     render!(div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_context<T: 'static>(&self, value: T) -> Rc<T> {
-        let mut contexts = self.shared_contexts.borrow_mut();
-        contexts.insert(TypeId::of::<T>(), Rc::new(value));
-        contexts
-            .get(&TypeId::of::<T>())
-            .unwrap()
-            .clone()
-            .downcast()
-            .unwrap()
+    pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
+        let value2 = value.clone();
+
+        self.shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), Box::new(value));
+
+        value2
     }
 
     /// Pushes the future onto the poll queue to be polled after the component renders.

+ 5 - 5
packages/core/src/virtual_dom.rs

@@ -249,10 +249,10 @@ impl VirtualDom {
         // This could be unexpected, so we might rethink this behavior later
         //
         // We *could* just panic if the suspense boundary is not found
-        root.provide_context(SuspenseContext::new(ScopeId(0)));
+        root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
-        root.provide_context(ErrorBoundary::new(ScopeId(0)));
+        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         dom.elements.insert(ElementRef::null());
@@ -296,7 +296,7 @@ impl VirtualDom {
     /// currently suspended.
     pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
         !self.scopes[id.0]
-            .consume_context::<SuspenseContext>()
+            .consume_context::<Rc<SuspenseContext>>()
             .unwrap()
             .waiting_on
             .borrow()
@@ -527,7 +527,7 @@ impl VirtualDom {
             // first, unload any complete suspense trees
             for finished_fiber in self.finished_fibers.drain(..) {
                 let scope = &mut self.scopes[finished_fiber.0];
-                let context = scope.has_context::<SuspenseContext>().unwrap();
+                let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
 
                 self.mutations
                     .templates
@@ -565,7 +565,7 @@ impl VirtualDom {
                 // No placeholder necessary since this is a diff
                 if !self.collected_leaves.is_empty() {
                     let mut boundary = self.scopes[dirty.id.0]
-                        .consume_context::<SuspenseContext>()
+                        .consume_context::<Rc<SuspenseContext>>()
                         .unwrap();
 
                     let boundary_mut = boundary.borrow_mut();

+ 2 - 1
packages/fermi/src/hooks/atom_root.rs

@@ -1,6 +1,7 @@
+use std::rc::Rc;
+
 use crate::AtomRoot;
 use dioxus_core::ScopeState;
-use std::rc::Rc;
 
 // Returns the atom root, initiaizing it at the root of the app if it does not exist.
 pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {

+ 10 - 5
packages/hooks/src/usecontext.rs

@@ -1,13 +1,18 @@
 use dioxus_core::ScopeState;
+use std::rc::Rc;
 
-/// Consume some context in the tree
-pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<&T> {
-    cx.use_hook(|| cx.consume_context::<T>()).as_deref()
+/// Consume some context in the tree, providing a sharable handle to the value
+pub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {
+    cx.use_hook(|| cx.consume_context::<T>()).as_ref()
 }
 
 /// Provide some context via the tree and return a reference to it
 ///
 /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.
-pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
-    cx.use_hook(|| cx.provide_context(f()))
+pub fn use_context_provider<T: 'static + Clone>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
+    cx.use_hook(|| {
+        let val = f();
+        cx.provide_context(val.clone());
+        val
+    })
 }

+ 2 - 4
packages/router/src/components/link.rs

@@ -1,6 +1,4 @@
-use std::sync::Arc;
-
-use crate::{use_route, RouterCore};
+use crate::{use_route, RouterContext};
 use dioxus::prelude::*;
 
 /// Props for the [`Link`](struct.Link.html) component.
@@ -77,7 +75,7 @@ pub struct LinkProps<'a> {
 /// }
 /// ```
 pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| cx.consume_context::<Arc<RouterCore>>());
+    let svc = use_context::<RouterContext>(cx);
 
     let LinkProps {
         to,

+ 6 - 10
packages/router/src/components/route.rs

@@ -1,7 +1,5 @@
+use crate::{RouteContext, RouterContext};
 use dioxus::prelude::*;
-use std::sync::Arc;
-
-use crate::{RouteContext, RouterCore};
 
 /// Props for the [`Route`](struct.Route.html) component.
 #[derive(Props)]
@@ -27,15 +25,13 @@ pub struct RouteProps<'a> {
 /// )
 /// ```
 pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
-    let router_root = cx
-        .use_hook(|| cx.consume_context::<Arc<RouterCore>>())
-        .as_ref()
-        .unwrap();
+    let router_root = use_context::<RouterContext>(cx).unwrap();
+    let root_context = use_context::<RouteContext>(cx);
 
     cx.use_hook(|| {
         // create a bigger, better, longer route if one above us exists
-        let total_route = match cx.consume_context::<RouteContext>() {
-            Some(ctx) => ctx.total_route,
+        let total_route = match root_context {
+            Some(ctx) => ctx.total_route.clone(),
             None => cx.props.to.to_string(),
         };
 
@@ -46,7 +42,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
         });
 
         // submit our rout
-        router_root.register_total_route(route_context.total_route, cx.scope_id());
+        router_root.register_total_route(route_context.total_route.clone(), cx.scope_id());
     });
 
     log::trace!("Checking Route: {:?}", cx.props.to);

+ 5 - 6
packages/router/src/components/router.rs

@@ -1,6 +1,5 @@
-use crate::{cfg::RouterCfg, RouterCore};
+use crate::{cfg::RouterCfg, RouterContext, RouterService};
 use dioxus::prelude::*;
-use std::sync::Arc;
 
 /// The props for the [`Router`](fn.Router.html) component.
 #[derive(Props)]
@@ -21,7 +20,7 @@ pub struct RouterProps<'a> {
     ///
     /// This lets you easily implement redirects
     #[props(default)]
-    pub onchange: EventHandler<'a, Arc<RouterCore>>,
+    pub onchange: EventHandler<'a, RouterContext>,
 
     /// Set the active class of all Link components contained in this router.
     ///
@@ -40,15 +39,15 @@ pub struct RouterProps<'a> {
 /// Will fallback to HashRouter is BrowserRouter is not available, or through configuration.
 #[allow(non_snake_case)]
 pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
-    let svc = cx.use_hook(|| {
-        cx.provide_context(RouterCore::new(
+    let svc = use_context_provider(cx, || {
+        RouterService::new(
             &cx,
             RouterCfg {
                 base_url: cx.props.base_url.map(|s| s.to_string()),
                 active_class: cx.props.active_class.map(|s| s.to_string()),
                 initial_url: cx.props.initial_url.clone(),
             },
-        ))
+        )
     });
 
     // next time we run the rout_found will be filled

+ 6 - 6
packages/router/src/hooks/use_route.rs

@@ -1,4 +1,4 @@
-use crate::{ParsedRoute, RouteContext, RouterCore, RouterService};
+use crate::{ParsedRoute, RouteContext, RouterContext};
 use dioxus::core::{ScopeId, ScopeState};
 use std::{borrow::Cow, str::FromStr, sync::Arc};
 use url::Url;
@@ -7,11 +7,11 @@ use url::Url;
 /// context of a [`Router`]. If this function is called outside of a `Router`
 /// component it will panic.
 pub fn use_route(cx: &ScopeState) -> &UseRoute {
-    let handle = cx.use_hook(|| {
-        let router = cx
-            .consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component");
+    let router = cx
+        .consume_context::<RouterContext>()
+        .expect("Cannot call use_route outside the scope of a Router component");
 
+    let handle = cx.use_hook(|| {
         let route_context = cx.consume_context::<RouteContext>();
 
         router.subscribe_onchange(cx.scope_id());
@@ -115,7 +115,7 @@ impl UseRoute {
 // and reveal our cached version of UseRoute to the component.
 struct UseRouteListener {
     state: UseRoute,
-    router: Arc<RouterCore>,
+    router: RouterContext,
     scope: ScopeId,
 }
 

+ 5 - 7
packages/router/src/hooks/use_router.rs

@@ -1,10 +1,8 @@
-use crate::RouterService;
-use dioxus::core::ScopeState;
+use crate::RouterContext;
+use dioxus::{core::ScopeState, prelude::use_context};
 
 /// This hook provides access to the `RouterService` for the app.
-pub fn use_router(cx: &ScopeState) -> &RouterService {
-    cx.use_hook(|| {
-        cx.consume_context::<RouterService>()
-            .expect("Cannot call use_route outside the scope of a Router component")
-    })
+pub fn use_router(cx: &ScopeState) -> &RouterContext {
+    use_context::<RouterContext>(cx)
+        .expect("Cannot call use_route outside the scope of a Router component")
 }

+ 15 - 18
packages/router/src/service.rs

@@ -4,7 +4,7 @@
 use crate::cfg::RouterCfg;
 use dioxus::core::{ScopeId, ScopeState, VirtualDom};
 use std::any::Any;
-use std::sync::Weak;
+use std::rc::Weak;
 use std::{
     cell::{Cell, RefCell},
     collections::{HashMap, HashSet},
@@ -14,6 +14,9 @@ use std::{
 };
 use url::Url;
 
+/// A clonable handle to the router
+pub type RouterContext = Rc<RouterService>;
+
 /// An abstraction over the platform's history API.
 ///
 /// The history is denoted using web-like semantics, with forward slashes delmitiing
@@ -41,7 +44,7 @@ use url::Url;
 /// - On the web, this is a [`BrowserHistory`](https://docs.rs/gloo/0.3.0/gloo/history/struct.BrowserHistory.html).
 /// - On desktop, mobile, and SSR, this is just a Vec of Strings. Currently on
 ///   desktop, there is no way to tap into forward/back for the app unless explicitly set.
-pub struct RouterCore {
+pub struct RouterService {
     pub(crate) route_found: Cell<Option<ScopeId>>,
 
     pub(crate) stack: RefCell<Vec<Arc<ParsedRoute>>>,
@@ -61,9 +64,6 @@ pub struct RouterCore {
     pub(crate) cfg: RouterCfg,
 }
 
-/// A shared type for the RouterCore.
-pub type RouterService = Arc<RouterCore>;
-
 /// A route is a combination of window title, saved state, and a URL.
 #[derive(Debug, Clone)]
 pub struct ParsedRoute {
@@ -77,8 +77,8 @@ pub struct ParsedRoute {
     pub serialized_state: Option<String>,
 }
 
-impl RouterCore {
-    pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc<Self> {
+impl RouterService {
+    pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> RouterContext {
         #[cfg(feature = "web")]
         let history = Box::new(web::new());
 
@@ -99,7 +99,7 @@ impl RouterCore {
             None => Arc::new(history.init_location()),
         };
 
-        let svc = Arc::new(Self {
+        let svc = Rc::new(Self {
             cfg,
             regen_any_route: cx.schedule_update_any(),
             router_id: cx.scope_id(),
@@ -111,7 +111,7 @@ impl RouterCore {
             history,
         });
 
-        svc.history.attach_listeners(Arc::downgrade(&svc));
+        svc.history.attach_listeners(Rc::downgrade(&svc));
 
         svc
     }
@@ -247,12 +247,9 @@ impl RouterCore {
 /// that owns the router.
 ///
 /// This might change in the future.
-pub fn get_router_from_vdom(
-    dom: &VirtualDom,
-    target_scope: Option<ScopeId>,
-) -> Option<Arc<RouterCore>> {
-    dom.get_scope(target_scope.unwrap_or(ScopeId(0)))
-        .and_then(|scope| scope.consume_context::<Arc<RouterCore>>())
+pub fn get_router_from_vdom(dom: &VirtualDom, target_scope: ScopeId) -> Option<RouterContext> {
+    dom.get_scope(target_scope)
+        .and_then(|scope| scope.consume_context::<RouterContext>())
 }
 
 fn clean_route(route: String) -> String {
@@ -319,7 +316,7 @@ pub(crate) trait RouterProvider {
     fn replace(&self, route: &ParsedRoute);
     fn native_location(&self) -> Box<dyn Any>;
     fn init_location(&self) -> ParsedRoute;
-    fn attach_listeners(&self, svc: Weak<RouterCore>);
+    fn attach_listeners(&self, svc: Weak<RouterService>);
 }
 
 #[cfg(not(feature = "web"))]
@@ -350,7 +347,7 @@ mod hash {
 
         fn replace(&self, _route: &ParsedRoute) {}
 
-        fn attach_listeners(&self, _svc: Weak<RouterCore>) {}
+        fn attach_listeners(&self, _svc: Weak<RouterService>) {}
     }
 }
 
@@ -418,7 +415,7 @@ mod web {
             }
         }
 
-        fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterCore>) {
+        fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterService>) {
             self._listener.set(Some(EventListener::new(
                 &web_sys::window().unwrap(),
                 "popstate",