فهرست منبع

implement suspense using simpler approach

Jonathan Kelley 1 سال پیش
والد
کامیت
f0128a3150
5فایلهای تغییر یافته به همراه27 افزوده شده و 83 حذف شده
  1. 1 0
      packages/core/Cargo.toml
  2. 2 0
      packages/core/src/scope_arena.rs
  3. 1 1
      packages/core/src/scopes.rs
  4. 3 1
      packages/core/src/virtual_dom.rs
  5. 20 81
      packages/core/tests/suspense.rs

+ 1 - 0
packages/core/Cargo.toml

@@ -40,6 +40,7 @@ tokio = { workspace = true, features = ["full"] }
 dioxus = { workspace = true }
 pretty_assertions = "1.3.0"
 rand = "0.8.5"
+dioxus-ssr = { workspace = true }
 
 [features]
 default = []

+ 2 - 0
packages/core/src/scope_arena.rs

@@ -85,6 +85,8 @@ impl VirtualDom {
 
         if scope.suspended.get() {
             self.suspended_scopes.insert(scope.id);
+        } else if !self.suspended_scopes.is_empty() {
+            _ = self.suspended_scopes.remove(&scope.id);
         }
 
         // rebind the lifetime now that its stored internally

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

@@ -655,7 +655,7 @@ impl<'src> ScopeState {
     }
 
     /// Mark this component as suspended and then return None
-    pub fn suspend(&self) -> Option<()> {
+    pub fn suspend(&self) -> Option<Element> {
         self.suspended.set(true);
         None
     }

+ 3 - 1
packages/core/src/virtual_dom.rs

@@ -572,10 +572,12 @@ impl VirtualDom {
     pub async fn wait_for_suspense(&mut self) {
         loop {
             if self.suspended_scopes.is_empty() {
-                break;
+                return;
             }
 
             self.wait_for_work().await;
+
+            _ = self.render_immediate();
         }
     }
 

+ 20 - 81
packages/core/tests/suspense.rs

@@ -1,103 +1,42 @@
-use dioxus::core::ElementId;
 use dioxus::core::Mutation::*;
 use dioxus::prelude::*;
 use std::future::IntoFuture;
 use std::rc::Rc;
 use std::time::Duration;
 
-#[test]
-fn it_works() {
+#[tokio::test]
+async fn it_works() {
     // wait just a moment, not enough time for the boundary to resolve
 
-    tokio::runtime::Builder::new_current_thread()
-        .enable_time()
-        .build()
-        .unwrap()
-        .block_on(async {
-            let mut dom = VirtualDom::new(app);
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+    dom.wait_for_suspense().await;
+    let out = dioxus_ssr::pre_render(&dom);
 
-            {
-                let mutations = dom.rebuild().santize();
+    assert_eq!(out, "<div>Waiting for... child</div>");
 
-                // We should at least get the top-level template in before pausing for the children
-                // note: we dont test template edits anymore
-                // assert_eq!(
-                //     mutations.templates,
-                //     [
-                //         CreateElement { name: "div" },
-                //         CreateStaticText { value: "Waiting for child..." },
-                //         CreateStaticPlaceholder,
-                //         AppendChildren { m: 2 },
-                //         SaveTemplate { name: "template", m: 1 }
-                //     ]
-                // );
-
-                // And we should load it in and assign the placeholder properly
-                assert_eq!(
-                    mutations.edits,
-                    [
-                        LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-                        // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
-                        // can we even?
-                        AssignId { path: &[1], id: ElementId(3) },
-                        AppendChildren { m: 1, id: ElementId(0) },
-                    ]
-                );
-            }
-
-            dom.wait_for_work().await;
-        });
+    dbg!(out);
 }
 
 fn app(cx: Scope) -> Element {
     cx.render(rsx!(
         div {
-            "Waiting for child..."
-            suspense_boundary {}
+            "Waiting for... "
+            suspended_child {}
         }
     ))
 }
 
-fn suspense_boundary(cx: Scope) -> Element {
-    todo!()
-    // cx.use_hook(|| {
-    //     cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id())));
-    // });
-
-    // // Ensure the right types are found
-    // cx.has_context::<Rc<SuspenseContext>>().unwrap();
-
-    // cx.render(rsx!(async_child {}))
-}
-
-fn async_child(cx: Scope<'_>) -> Element {
-    todo!()
-    // use_future!(cx, || tokio::time::sleep(Duration::from_millis(10))).await;
-    // cx.render(rsx!(async_text {}))
-}
-
-fn async_text(cx: Scope<'_>) -> Element {
-    todo!()
-    // let username = use_future!(cx, || async {
-    //     tokio::time::sleep(std::time::Duration::from_secs(1)).await;
-    //     "async child 1"
-    // });
+fn suspended_child(cx: Scope) -> Element {
+    let mut val = use_state(cx, || 0);
 
-    // let age = use_future!(cx, || async {
-    //     tokio::time::sleep(std::time::Duration::from_secs(2)).await;
-    //     1234
-    // });
-
-    // let (_user, _age) = use_future!(cx, || async {
-    //     tokio::join!(
-    //         tokio::time::sleep(std::time::Duration::from_secs(1)),
-    //         tokio::time::sleep(std::time::Duration::from_secs(2))
-    //     );
-    //     ("async child 1", 1234)
-    // })
-    // .await;
-
-    // let (username, age) = tokio::join!(username.into_future(), age.into_future());
+    if **val < 3 {
+        let mut val = val.clone();
+        cx.spawn(async move {
+            val += 1;
+        });
+        return cx.suspend()?;
+    }
 
-    // cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } ))
+    render!("child")
 }