From dfd795951b58276d83af5dacfff0023e082b1b44 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 3 Sep 2024 09:55:40 +0800 Subject: [PATCH 1/5] add blog 23 24 --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 7eee2a4..0786952 100644 --- a/readme.md +++ b/readme.md @@ -51,3 +51,7 @@ [从零实现 React v18,但 WASM 版 - [21] 性能优化支持 Context](https://www.paradeto.com/2024/07/26/big-react-wasm-21/) [从零实现 React v18,但 WASM 版 - [22] 实现 memo](https://www.paradeto.com/2024/08/01/big-react-wasm-22/) + +[从零实现 React v18,但 WASM 版 - [23] 实现 Fragment](https://www.paradeto.com/2024/08/01/big-react-wasm-23/) + +[从零实现 React v18,但 WASM 版 - [24] 实现 Suspense(一):渲染 Fallback](https://www.paradeto.com/2024/08/01/big-react-wasm-24/) From 7050d983fe2e3a2931c6e3585bca81f7eb759d1e Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 5 Sep 2024 10:02:21 +0800 Subject: [PATCH 2/5] add use --- examples/hello-world/src/suspense/index.tsx | 7 +++-- packages/react-reconciler/src/fiber_hooks.rs | 30 +++++++++++++++----- packages/react-reconciler/src/thenable.rs | 25 +++++++++++++--- packages/react/src/current_dispatcher.rs | 5 ++++ packages/react/src/lib.rs | 6 ++++ 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/examples/hello-world/src/suspense/index.tsx b/examples/hello-world/src/suspense/index.tsx index a4f5393..e71d23a 100644 --- a/examples/hello-world/src/suspense/index.tsx +++ b/examples/hello-world/src/suspense/index.tsx @@ -9,6 +9,9 @@ export default function App() { } function Child() { - debugger - throw new Promise((resolve) => setTimeout(resolve, 1000)) + try { + throw new Promise((resolve) => setTimeout(resolve, 1000)) + } catch (error) { + debugger + } } diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 0557bcb..66e0fc6 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,17 +1,19 @@ use std::cell::RefCell; +use std::error::Error; use std::rc::Rc; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; -use shared::{derive_from_js_value, is_dev, log}; +use shared::{derive_from_js_value, is_dev, log, type_of}; use crate::begin_work::mark_wip_received_update; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_context::read_context as read_context_origin; use crate::fiber_flags::Flags; use crate::fiber_lanes::{merge_lanes, remove_lanes, request_update_lane, Lane}; +use crate::thenable::{track_used_thenable, SuspenseException}; use crate::update_queue::{ create_update, create_update_queue, enqueue_update, process_update_queue, ReturnOfProcessUpdateQueue, Update, UpdateQueue, @@ -136,25 +138,31 @@ fn update_hooks_to_dispatcher(is_update: bool) { use_memo_closure.forget(); // use_callback - let use_callback_clusure = Closure::wrap(Box::new(if is_update { + let use_callback_closure = Closure::wrap(Box::new(if is_update { update_callback } else { mount_callback }) as Box JsValue>); - let use_callback = use_callback_clusure + let use_callback = use_callback_closure .as_ref() .unchecked_ref::() .clone(); - use_callback_clusure.forget(); + use_callback_closure.forget(); // use_context - let use_context_clusure = + let use_context_closure = Closure::wrap(Box::new(read_context) as Box JsValue>); - let use_context = use_context_clusure + let use_context = use_context_closure .as_ref() .unchecked_ref::() .clone(); - use_context_clusure.forget(); + use_context_closure.forget(); + + // use + let use_closure = + Closure::wrap(Box::new(_use) as Box Result>); + let use_fn = use_closure.as_ref().unchecked_ref::().clone(); + use_closure.forget(); Reflect::set(&object, &"use_state".into(), &use_state).expect("TODO: panic set use_state"); Reflect::set(&object, &"use_effect".into(), &use_effect).expect("TODO: panic set use_effect"); @@ -164,6 +172,7 @@ fn update_hooks_to_dispatcher(is_update: bool) { .expect("TODO: panic set use_callback"); Reflect::set(&object, &"use_context".into(), &use_context) .expect("TODO: panic set use_context"); + Reflect::set(&object, &"use".into(), &use_fn).expect("TODO: panic set use"); updateDispatcher(&object.into()); } @@ -747,3 +756,10 @@ fn read_context(context: JsValue) -> JsValue { let consumer = unsafe { CURRENTLY_RENDERING_FIBER.clone() }; read_context_origin(consumer, context) } + +fn _use(usable: &JsValue) -> Result { + track_used_thenable(usable) + // if !usable.is_null() && type_of(usable, "object") { + // if derive_from_js_value(usable, "then").is_function() {} + // } +} diff --git a/packages/react-reconciler/src/thenable.rs b/packages/react-reconciler/src/thenable.rs index fbfdd14..2c854ef 100644 --- a/packages/react-reconciler/src/thenable.rs +++ b/packages/react-reconciler/src/thenable.rs @@ -1,6 +1,23 @@ -use wasm_bindgen::prelude::*; +use std::{ + error::Error, + fmt::{self, Display, Formatter}, +}; -#[wasm_bindgen] -extern "C" { - type SuspenseException; +use wasm_bindgen::JsValue; + +#[derive(Debug, Clone)] +pub struct SuspenseException; + +impl Error for SuspenseException {} + +impl Display for SuspenseException { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out") + } +} + +pub static SUSPENSE_EXCEPTION: JsValue = JsValue::from_str("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); + +pub fn track_used_thenable(usable: &JsValue) -> Result { + Err(&SUSPENSE_EXCEPTION) } diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index a62e1d6..c41bd75 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -10,6 +10,7 @@ pub struct Dispatcher { pub use_memo: Function, pub use_callback: Function, pub use_context: Function, + pub _use: Function, } unsafe impl Send for Dispatcher {} @@ -22,6 +23,7 @@ impl Dispatcher { use_memo: Function, use_callback: Function, use_context: Function, + _use: Function, ) -> Self { Dispatcher { use_state, @@ -30,6 +32,7 @@ impl Dispatcher { use_memo, use_callback, use_context, + _use, } } } @@ -55,6 +58,7 @@ pub unsafe fn update_dispatcher(args: &JsValue) { let use_memo = derive_function_from_js_value(args, "use_memo"); let use_callback = derive_function_from_js_value(args, "use_callback"); let use_context = derive_function_from_js_value(args, "use_context"); + let _use = derive_function_from_js_value(args, "use"); CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( use_state, use_effect, @@ -62,5 +66,6 @@ pub unsafe fn update_dispatcher(args: &JsValue) { use_memo, use_callback, use_context, + _use, ))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 4a30bdf..387816b 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -150,6 +150,12 @@ pub unsafe fn use_context(context: &JsValue) -> Result { use_context.call1(&JsValue::null(), context) } +#[wasm_bindgen(js_name = use)] +pub unsafe fn _use(usable: &JsValue) -> Result { + let _use = &CURRENT_DISPATCHER.current.as_ref().unwrap()._use; + _use.call1(&JsValue::null(), usable) +} + #[wasm_bindgen(js_name = createContext)] pub unsafe fn create_context(default_value: &JsValue) -> JsValue { let context = Object::new(); From 919f5c76564aca311bb1df9c2d43ae0e69f37115 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 5 Sep 2024 10:56:04 +0800 Subject: [PATCH 3/5] SUSPENSE_EXCEPTION --- examples/hello-world/src/suspense/index.tsx | 4 ++-- packages/react-reconciler/src/fiber_hooks.rs | 3 +-- packages/react-reconciler/src/thenable.rs | 24 +++++--------------- scripts/build.js | 24 ++++++++++---------- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/examples/hello-world/src/suspense/index.tsx b/examples/hello-world/src/suspense/index.tsx index e71d23a..6108298 100644 --- a/examples/hello-world/src/suspense/index.tsx +++ b/examples/hello-world/src/suspense/index.tsx @@ -1,4 +1,4 @@ -import {Suspense} from 'react' +import {Suspense, use} from 'react' export default function App() { return ( @@ -10,7 +10,7 @@ export default function App() { function Child() { try { - throw new Promise((resolve) => setTimeout(resolve, 1000)) + use(new Promise((resolve) => setTimeout(resolve, 1000))) } catch (error) { debugger } diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 66e0fc6..5ce4a84 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::error::Error; use std::rc::Rc; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; @@ -13,7 +12,7 @@ use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_context::read_context as read_context_origin; use crate::fiber_flags::Flags; use crate::fiber_lanes::{merge_lanes, remove_lanes, request_update_lane, Lane}; -use crate::thenable::{track_used_thenable, SuspenseException}; +use crate::thenable::track_used_thenable; use crate::update_queue::{ create_update, create_update_queue, enqueue_update, process_update_queue, ReturnOfProcessUpdateQueue, Update, UpdateQueue, diff --git a/packages/react-reconciler/src/thenable.rs b/packages/react-reconciler/src/thenable.rs index 2c854ef..d34dadb 100644 --- a/packages/react-reconciler/src/thenable.rs +++ b/packages/react-reconciler/src/thenable.rs @@ -1,23 +1,11 @@ -use std::{ - error::Error, - fmt::{self, Display, Formatter}, -}; - use wasm_bindgen::JsValue; +use web_sys::js_sys::wasm_bindgen::prelude::*; -#[derive(Debug, Clone)] -pub struct SuspenseException; - -impl Error for SuspenseException {} - -impl Display for SuspenseException { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out") - } +#[wasm_bindgen] +extern "C" { + pub static SuspenseException: JsValue; } -pub static SUSPENSE_EXCEPTION: JsValue = JsValue::from_str("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); - -pub fn track_used_thenable(usable: &JsValue) -> Result { - Err(&SUSPENSE_EXCEPTION) +pub fn track_used_thenable(usable: &JsValue) -> Result { + Err(SuspenseException.__inner.with(JsValue::clone)) } diff --git a/scripts/build.js b/scripts/build.js index fa31a30..bad3355 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -45,18 +45,23 @@ packageJson.files.push( ) fs.writeFileSync(packageJsonFilename, JSON.stringify(packageJson)) +const code1 = isTest + ? ` +const {updateDispatcher} = require("react"); +const SUSPENSE_EXCEPTION = new Error("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); +` + : ` +import {updateDispatcher} from "react"; +const SUSPENSE_EXCEPTION = new Error("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); +` + if (isTest) { // modify react-noop/index_bg.js const reactNoopIndexFilename = isTest ? `${cwd}/dist/react-noop/index.js` : `${cwd}/dist/react-noop/index_bg.js` const reactNoopIndexBgData = fs.readFileSync(reactNoopIndexFilename) - fs.writeFileSync( - reactNoopIndexFilename, - (isTest - ? 'const {updateDispatcher} = require("react");\n' - : 'import {updateDispatcher} from "react";\n') + reactNoopIndexBgData - ) + fs.writeFileSync(reactNoopIndexFilename, code1 + reactNoopIndexBgData) } // modify react-dom/index_bg.js @@ -64,12 +69,7 @@ const reactDomIndexFilename = isTest ? `${cwd}/dist/react-dom/index.js` : `${cwd}/dist/react-dom/index_bg.js` const reactDomIndexBgData = fs.readFileSync(reactDomIndexFilename) -fs.writeFileSync( - reactDomIndexFilename, - (isTest - ? 'const {updateDispatcher} = require("react");\n' - : 'import {updateDispatcher} from "react";\n') + reactDomIndexBgData -) +fs.writeFileSync(reactDomIndexFilename, code1 + reactDomIndexBgData) // add Suspense + Fragment const reactIndexFilename = `${cwd}/dist/react/index.js` From a610d989d0297b8cb881f471b3c3d0e904c9d7ed Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 5 Sep 2024 17:57:31 +0800 Subject: [PATCH 4/5] build successfully --- examples/hello-world/src/suspense/index.tsx | 7 +- packages/react-reconciler/src/child_fiber.rs | 51 ++++---------- packages/react-reconciler/src/fiber.rs | 16 ++++- packages/react-reconciler/src/fiber_hooks.rs | 26 +++++-- packages/react-reconciler/src/fiber_lanes.rs | 8 ++- packages/react-reconciler/src/fiber_throw.rs | 65 ++++++++++++++---- packages/react-reconciler/src/lib.rs | 26 ++++++- packages/react-reconciler/src/thenable.rs | 72 ++++++++++++++++++-- packages/react-reconciler/src/work_loop.rs | 20 +++++- 9 files changed, 220 insertions(+), 71 deletions(-) diff --git a/examples/hello-world/src/suspense/index.tsx b/examples/hello-world/src/suspense/index.tsx index 6108298..2c794c7 100644 --- a/examples/hello-world/src/suspense/index.tsx +++ b/examples/hello-world/src/suspense/index.tsx @@ -9,9 +9,6 @@ export default function App() { } function Child() { - try { - use(new Promise((resolve) => setTimeout(resolve, 1000))) - } catch (error) { - debugger - } + const a = use(new Promise((resolve) => setTimeout(() => resolve(1), 1000))) + return {a} } diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index c90cfb9..be41696 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::collections::HashMap; -use std::hash::{Hash, Hasher}; use std::rc::Rc; use wasm_bindgen::{JsCast, JsValue}; @@ -12,6 +11,7 @@ use crate::fiber::FiberNode; use crate::fiber_flags::Flags; use crate::work_tags::WorkTag; use crate::work_tags::WorkTag::HostText; +use crate::JsValueKey; fn use_fiber(fiber: Rc>, pending_props: JsValue) -> Rc> { let clone = FiberNode::create_work_in_progress(fiber, pending_props); @@ -133,7 +133,7 @@ fn reconcile_single_element( } } - let mut fiber ; + let mut fiber; if derive_from_js_value(&element, "type") == REACT_FRAGMENT_TYPE { let props = derive_from_js_value(&element, "props"); let children = derive_from_js_value(&props, "children"); @@ -141,7 +141,7 @@ fn reconcile_single_element( } else { fiber = FiberNode::create_fiber_from_element(element); } - + fiber._return = Some(return_fiber.clone()); Rc::new(RefCell::new(fiber)) } @@ -190,35 +190,12 @@ fn reconcile_single_text_node( Rc::new(RefCell::new(created)) } -#[derive(Clone, Debug)] -struct Key(JsValue); - -impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - Object::is(&self.0, &other.0) - } -} - -impl Eq for Key {} - -impl Hash for Key { - fn hash(&self, state: &mut H) { - if self.0.is_string() { - self.0.as_string().unwrap().hash(state) - } else if let Some(n) = self.0.as_f64() { - n.to_bits().hash(state) - } else if self.0.is_null() { - "null".hash(state) - } - } -} - fn update_fragment( return_fiber: Rc>, current: Option>>, elements: JsValue, - key: Key, - existing_children: &mut HashMap>>, + key: JsValueKey, + existing_children: &mut HashMap>>, ) -> Rc> { let fiber; if current.is_none() || current.clone().unwrap().borrow().tag != WorkTag::Fragment { @@ -235,7 +212,7 @@ fn update_fragment( fn update_from_map( return_fiber: Rc>, - existing_children: &mut HashMap>>, + existing_children: &mut HashMap>>, index: u32, element: &JsValue, should_track_effects: bool, @@ -255,13 +232,15 @@ fn update_from_map( false => key.clone(), } } - let before = existing_children.get(&Key(key_to_use.clone())).clone(); + let before = existing_children + .get(&JsValueKey(key_to_use.clone())) + .clone(); if type_of(element, "null") || type_of(element, "string") || type_of(element, "number") { let props = create_props_with_content(element.clone()); // log!("update_from_map {:?}", props); if before.is_some() { let before = (*before.clone().unwrap()).clone(); - existing_children.remove(&Key(key_to_use.clone())); + existing_children.remove(&JsValueKey(key_to_use.clone())); if before.borrow().tag == HostText { return Some(use_fiber(before.clone(), props.clone())); } else { @@ -287,7 +266,7 @@ fn update_from_map( return_fiber, before, (*element).clone(), - Key(key_to_use.clone()), + JsValueKey(key_to_use.clone()), existing_children, )); } else if type_of(element, "object") && !element.is_null() { @@ -301,14 +280,14 @@ fn update_from_map( return_fiber, before, (*element).clone(), - Key(key_to_use.clone()), + JsValueKey(key_to_use.clone()), existing_children, )); } if before.is_some() { let before = (*before.clone().unwrap()).clone(); - existing_children.remove(&Key(key_to_use.clone())); + existing_children.remove(&JsValueKey(key_to_use.clone())); if Object::is( &before.borrow()._type, &derive_from_js_value(&(*element).clone(), "type"), @@ -346,7 +325,7 @@ fn reconcile_children_array( // 创建的第一个fiber let mut first_new_fiber: Option>> = None; - let mut existing_children: HashMap>> = HashMap::new(); + let mut existing_children: HashMap>> = HashMap::new(); let mut current = current_first_child; while current.is_some() { let current_rc = current.unwrap(); @@ -354,7 +333,7 @@ fn reconcile_children_array( true => JsValue::from(current_rc.borrow().index), false => current_rc.borrow().key.clone(), }; - existing_children.insert(Key(key_to_use), current_rc.clone()); + existing_children.insert(JsValueKey(key_to_use), current_rc.clone()); current = current_rc.borrow().sibling.clone(); } // log!("existing_children {:?}", existing_children.keys()); diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 415cb56..09a72c5 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::cell::RefCell; -use std::collections::VecDeque; +use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::{Debug, Formatter}; use std::ops::Deref; use std::rc::Rc; @@ -19,6 +19,7 @@ use crate::fiber_hooks::{Effect, Hook}; use crate::fiber_lanes::{get_highest_priority, merge_lanes, Lane}; use crate::update_queue::{Update, UpdateQueue}; use crate::work_tags::WorkTag; +use crate::JsValueKey; #[derive(Debug)] pub enum StateNode { @@ -294,10 +295,11 @@ pub struct FiberRootNode { pub pending_lanes: Lane, pub finished_lanes: Lane, pub suspended_lanes: Lane, - pub pinged_lanes: Lane, + pub pinged_lanes: Lane, // Records the processed suspended lanes, comes from suspended lanes pub callback_node: Option, pub callback_priority: Lane, pub pending_passive_effects: Rc>, + pub ping_cache: Option>>>>, } impl FiberRootNode { @@ -316,6 +318,7 @@ impl FiberRootNode { callback_priority: Lane::NoLane, pinged_lanes: Lane::NoLane, suspended_lanes: Lane::NoLane, + ping_cache: None, } } @@ -327,6 +330,15 @@ impl FiberRootNode { self.pending_lanes = merge_lanes(self.pending_lanes.clone(), lane) } + pub fn mark_root_pinged(&mut self, pinged_lane: Lane) { + self.pinged_lanes |= self.suspended_lanes.clone() & pinged_lane; + } + + pub fn mark_root_suspended(&mut self, suspended_lane: Lane) { + self.suspended_lanes |= suspended_lane.clone(); + self.pinged_lanes -= suspended_lane; + } + pub fn get_next_lanes(&self) -> Lane { let pending_lanes = self.pending_lanes.clone(); if pending_lanes == Lane::NoLane { diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 5ce4a84..b489b3f 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; -use shared::{derive_from_js_value, is_dev, log, type_of}; +use shared::{derive_from_js_value, is_dev, log, type_of, REACT_CONTEXT_TYPE}; use crate::begin_work::mark_wip_received_update; use crate::fiber::{FiberNode, MemoizedState}; @@ -159,7 +159,7 @@ fn update_hooks_to_dispatcher(is_update: bool) { // use let use_closure = - Closure::wrap(Box::new(_use) as Box Result>); + Closure::wrap(Box::new(_use) as Box Result>); let use_fn = use_closure.as_ref().unchecked_ref::().clone(); use_closure.forget(); @@ -756,9 +756,21 @@ fn read_context(context: JsValue) -> JsValue { read_context_origin(consumer, context) } -fn _use(usable: &JsValue) -> Result { - track_used_thenable(usable) - // if !usable.is_null() && type_of(usable, "object") { - // if derive_from_js_value(usable, "then").is_function() {} - // } +fn _use(usable: JsValue) -> Result { + if !usable.is_null() && type_of(&usable, "object") { + if derive_from_js_value(&usable, "then").is_function() { + return track_used_thenable(usable); + } else if derive_from_js_value(&usable, "$$typeof") == REACT_CONTEXT_TYPE { + return Ok(read_context(usable)); + } + } + Err(JsValue::from_str("Not supported use arguments")) +} + +pub fn reset_hooks_on_unwind(wip: Rc>) { + unsafe { + CURRENTLY_RENDERING_FIBER = None; + CURRENT_HOOK = None; + WORK_IN_PROGRESS_HOOK = None; + } } diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index d9f46be..1a511bc 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,7 +1,7 @@ use bitflags::bitflags; use scheduler::{unstable_get_current_priority_level, Priority}; use std::cell::RefCell; -use std::pin; +use std::hash::{Hash, Hasher}; use std::rc::Rc; use crate::fiber::FiberRootNode; @@ -25,6 +25,12 @@ impl PartialEq for Lane { impl Eq for Lane {} +impl Hash for Lane { + fn hash(&self, state: &mut H) { + self.0.bits().hash(state); + } +} + pub fn get_highest_priority(lanes: Lane) -> Lane { let lanes = lanes.bits(); let highest_priority = lanes & (lanes.wrapping_neg()); diff --git a/packages/react-reconciler/src/fiber_throw.rs b/packages/react-reconciler/src/fiber_throw.rs index b206495..0af34de 100644 --- a/packages/react-reconciler/src/fiber_throw.rs +++ b/packages/react-reconciler/src/fiber_throw.rs @@ -1,4 +1,8 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + rc::Rc, +}; use shared::{derive_from_js_value, type_of}; use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; @@ -6,20 +10,57 @@ use web_sys::js_sys::Function; use crate::{ fiber::FiberRootNode, fiber_flags::Flags, fiber_lanes::Lane, - suspense_context::get_suspense_handler, work_loop::ensure_root_is_scheduled, + suspense_context::get_suspense_handler, work_loop::ensure_root_is_scheduled, JsValueKey, }; fn attach_ping_listener(root: Rc>, wakeable: JsValue, lane: Lane) { - let then_value = derive_from_js_value(&wakeable, "then"); - let then = then_value.dyn_ref::().unwrap(); - let closure = Closure::wrap(Box::new(move || { - root.clone().borrow_mut().mark_root_updated(lane.clone()); - ensure_root_is_scheduled(root.clone()); - }) as Box); - let ping = closure.as_ref().unchecked_ref::().clone(); - closure.forget(); - then.call2(&wakeable, &ping, &ping) - .expect("failed to call then function"); + let mut ping_cache_option: Option>>>> = + root.borrow().ping_cache.clone(); + let mut ping_cache: HashMap>>>; + + if ping_cache_option.is_none() { + ping_cache = HashMap::new(); + ping_cache.insert( + JsValueKey(wakeable.clone()), + Rc::new(RefCell::new(HashSet::new())), + ); + } else { + ping_cache = ping_cache_option.unwrap(); + let _thread_ids = ping_cache.get(&JsValueKey(wakeable.clone())); + if _thread_ids.is_none() { + ping_cache.insert( + JsValueKey(wakeable.clone()), + Rc::new(RefCell::new(HashSet::new())), + ); + // thread_ids = &mut ids; + } + // } else { + // thread_ids = &mut _thread_ids.unwrap(); + // } + } + + let mut thread_ids = ping_cache.get(&JsValueKey(wakeable.clone())).unwrap(); + + if thread_ids.borrow().contains(&lane) { + thread_ids.borrow_mut().insert(lane.clone()); + let then_value = derive_from_js_value(&wakeable, "then"); + let then = then_value.dyn_ref::().unwrap(); + let wakable1 = wakeable.clone(); + let closure = Closure::wrap(Box::new(move || { + let mut ping_cache = { root.borrow().ping_cache.clone() }; + if ping_cache.is_some() { + let mut ping_cache = ping_cache.unwrap(); + ping_cache.remove(&JsValueKey(wakable1.clone())); + } + root.clone().borrow_mut().mark_root_updated(lane.clone()); + root.clone().borrow_mut().mark_root_pinged(lane.clone()); + ensure_root_is_scheduled(root.clone()); + }) as Box); + let ping = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + then.call2(&wakeable, &ping, &ping) + .expect("failed to call then function"); + } } pub fn throw_exception(root: Rc>, value: JsValue, lane: Lane) { diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index d63aef5..6cdea76 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -1,8 +1,9 @@ use std::any::Any; use std::cell::RefCell; +use std::hash::{Hash, Hasher}; use std::rc::Rc; - use wasm_bindgen::JsValue; +use web_sys::js_sys::Object; use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; @@ -94,3 +95,26 @@ impl Reconciler { element.clone() } } + +#[derive(Clone, Debug)] +struct JsValueKey(JsValue); + +impl PartialEq for JsValueKey { + fn eq(&self, other: &Self) -> bool { + Object::is(&self.0, &other.0) + } +} + +impl Eq for JsValueKey {} + +impl Hash for JsValueKey { + fn hash(&self, state: &mut H) { + if self.0.is_string() { + self.0.as_string().unwrap().hash(state) + } else if let Some(n) = self.0.as_f64() { + n.to_bits().hash(state) + } else if self.0.is_null() { + "null".hash(state) + } + } +} diff --git a/packages/react-reconciler/src/thenable.rs b/packages/react-reconciler/src/thenable.rs index d34dadb..3a72585 100644 --- a/packages/react-reconciler/src/thenable.rs +++ b/packages/react-reconciler/src/thenable.rs @@ -1,11 +1,75 @@ +use shared::derive_from_js_value; use wasm_bindgen::JsValue; -use web_sys::js_sys::wasm_bindgen::prelude::*; +use web_sys::js_sys::{wasm_bindgen::prelude::*, Function, Reflect}; #[wasm_bindgen] extern "C" { - pub static SuspenseException: JsValue; + pub static SUSPENSE_EXCEPTION: JsValue; } -pub fn track_used_thenable(usable: &JsValue) -> Result { - Err(SuspenseException.__inner.with(JsValue::clone)) +static mut SUSPENDED_THENABLE: Option = None; + +pub fn get_suspense_thenable() -> JsValue { + if unsafe { SUSPENDED_THENABLE.is_none() } { + panic!("Should have SUSPENDED_THENABLE"); + } + + let thenable = unsafe { SUSPENDED_THENABLE.clone() }; + unsafe { SUSPENDED_THENABLE = None }; + thenable.unwrap() +} + +pub fn track_used_thenable(thenable: JsValue) -> Result { + let status = derive_from_js_value(&thenable, "status"); + + if status.is_string() { + let status = status.as_string().unwrap(); + if status == "fulfilled" { + return Ok(derive_from_js_value(&thenable, "value")); + } else if status == "rejected" { + return Err(derive_from_js_value(&thenable, "reason")); + } + + let v = derive_from_js_value(&thenable, "then"); + let then = v.dyn_ref::().unwrap(); + + let closure = Closure::wrap(Box::new(move || {}) as Box); + let noop = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + then.call2(&thenable, &noop, &noop); + } else { + Reflect::set(&thenable, &"status".into(), &"pending".into()); + let v = derive_from_js_value(&thenable, "then"); + let then = v.dyn_ref::().unwrap(); + + let thenable1 = thenable.clone(); + let on_resolve_closure = Closure::wrap(Box::new(move |val: JsValue| { + if derive_from_js_value(&thenable1, "status") == "pending" { + Reflect::set(&thenable1, &"status".into(), &"fulfilled".into()); + Reflect::set(&thenable1, &"value".into(), &val); + } + }) as Box ()>); + let on_resolve = on_resolve_closure + .as_ref() + .unchecked_ref::() + .clone(); + on_resolve_closure.forget(); + + let thenable2 = thenable.clone(); + let on_reject_closure = Closure::wrap(Box::new(move |err: JsValue| { + if derive_from_js_value(&thenable2, "status") == "pending" { + Reflect::set(&thenable2, &"status".into(), &"rejected".into()); + Reflect::set(&thenable2, &"reason".into(), &err); + } + }) as Box ()>); + let on_reject = on_reject_closure + .as_ref() + .unchecked_ref::() + .clone(); + on_reject_closure.forget(); + + then.call2(&thenable, &on_resolve, &on_reject); + } + unsafe { SUSPENDED_THENABLE = Some(thenable.clone()) }; + Err(SUSPENSE_EXCEPTION.__inner.with(JsValue::clone)) } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 3d710b4..80d2403 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use wasm_bindgen::closure::Closure; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::js_sys::Function; +use web_sys::js_sys::{Function, Object}; use scheduler::{ unstable_cancel_callback, unstable_schedule_callback_no_delay, unstable_should_yield_to_host, @@ -18,12 +18,14 @@ use crate::commit_work::{ }; use crate::fiber::{FiberNode, FiberRootNode, PendingPassiveEffects, StateNode}; use crate::fiber_flags::{get_host_effect_mask, get_mutation_mask, get_passive_mask, Flags}; +use crate::fiber_hooks::reset_hooks_on_unwind; use crate::fiber_lanes::{ get_highest_priority, lanes_to_scheduler_priority, mark_root_suspended, merge_lanes, Lane, }; use crate::fiber_throw::throw_exception; use crate::fiber_unwind_work::unwind_work; use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; +use crate::thenable::{get_suspense_thenable, SUSPENSE_EXCEPTION}; use crate::work_tags::WorkTag; use crate::{COMPLETE_WORK, HOST_CONFIG}; @@ -494,9 +496,20 @@ fn complete_unit_of_work(fiber: Rc>) { } } -fn handle_throw(root: Rc>, thrown_value: JsValue) { +fn handle_throw(root: Rc>, mut thrown_value: JsValue) { + /* + throw possibilities: + 1. use thenable + 2. error (Error Boundary) + */ + if Object::is(&thrown_value, &SUSPENSE_EXCEPTION) { + unsafe { WORK_IN_PROGRESS_SUSPENDED_REASON = SUSPENDED_ON_DATA }; + thrown_value = get_suspense_thenable(); + } else { + // TODO + } + unsafe { - WORK_IN_PROGRESS_SUSPENDED_REASON = SUSPENDED_ON_DATA; WORK_IN_PROGRESS_THROWN_VALUE = Some(thrown_value); } } @@ -507,6 +520,7 @@ fn throw_and_unwind_work_loop( thrown_value: JsValue, lane: Lane, ) { + reset_hooks_on_unwind(unit_of_work.clone()); throw_exception(root.clone(), thrown_value, lane.clone()); unwind_unit_of_work(unit_of_work); } From 499799995f6d4ad9b2ac5e74efa64febe7a20bfc Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 6 Sep 2024 15:29:46 +0800 Subject: [PATCH 5/5] complete use --- examples/hello-world/src/suspense/index.tsx | 22 +++- packages/react-reconciler/src/begin_work.rs | 112 +++++++++---------- packages/react-reconciler/src/commit_work.rs | 87 +++++++------- packages/react-reconciler/src/fiber_throw.rs | 2 +- packages/react-reconciler/src/thenable.rs | 2 - packages/react-reconciler/src/work_loop.rs | 1 - 6 files changed, 120 insertions(+), 106 deletions(-) diff --git a/examples/hello-world/src/suspense/index.tsx b/examples/hello-world/src/suspense/index.tsx index 2c794c7..6ce2e81 100644 --- a/examples/hello-world/src/suspense/index.tsx +++ b/examples/hello-world/src/suspense/index.tsx @@ -1,5 +1,22 @@ import {Suspense, use} from 'react' +const delay = (t) => + new Promise((r) => { + setTimeout(r, t) + }) + +const cachePool: any[] = [] + +function fetchData(id, timeout) { + const cache = cachePool[id] + if (cache) { + return cache + } + return (cachePool[id] = delay(timeout).then(() => { + return {data: Math.random().toFixed(2) * 100} + })) +} + export default function App() { return ( loading}> @@ -9,6 +26,7 @@ export default function App() { } function Child() { - const a = use(new Promise((resolve) => setTimeout(() => resolve(1), 1000))) - return {a} + const {data} = use(fetchData(1, 1000)) + + return {data} } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 4bad91e..aba2024 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -51,56 +51,48 @@ pub fn begin_work( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { - log!("begin_work {:?}", work_in_progress); + log!("begin_work {:?}", work_in_progress.clone()); unsafe { DID_RECEIVE_UPDATE = false; }; - let current = { work_in_progress.borrow().alternate.clone() }; - if current.is_some() { - let current = current.clone().unwrap(); - let old_props = current.borrow().memoized_props.clone(); - let old_type = current.borrow()._type.clone(); - let new_props = work_in_progress.borrow().pending_props.clone(); - let new_type = work_in_progress.borrow()._type.clone(); - if !Object::is(&old_props, &new_props) || !Object::is(&old_type, &new_type) { - unsafe { DID_RECEIVE_UPDATE = true } - } else { - let has_scheduled_update_or_context = - check_scheduled_update_or_context(current.clone(), render_lane.clone()); - // The current fiber lane is not included in render_lane - // TODO context - if !has_scheduled_update_or_context { - unsafe { DID_RECEIVE_UPDATE = false } - // // if current.is_some() { - // let c = current.clone(); - // log!( - // "current tag:{:?} lanes:{:?} child_lanes:{:?} render_lane:{:?}", - // c.borrow().tag, - // c.borrow().lanes, - // c.borrow().child_lanes, - // render_lane - // ); - // // } - match work_in_progress.borrow().tag { - WorkTag::ContextProvider => { - let new_value = derive_from_js_value( - &work_in_progress.borrow().memoized_props, - "value", - ); - let context = - derive_from_js_value(&work_in_progress.borrow()._type, "_context"); - push_provider(&context, new_value); - } - _ => {} - } - return Ok(bailout_on_already_finished_work( - work_in_progress, - render_lane, - )); - } - } - } + // TODO work with suspense + // let current = { work_in_progress.borrow().alternate.clone() }; + + // if current.is_some() { + // let current = current.clone().unwrap(); + // let old_props = current.borrow().memoized_props.clone(); + // let old_type = current.borrow()._type.clone(); + // let new_props = work_in_progress.borrow().pending_props.clone(); + // let new_type = work_in_progress.borrow()._type.clone(); + // if !Object::is(&old_props, &new_props) || !Object::is(&old_type, &new_type) { + // unsafe { DID_RECEIVE_UPDATE = true } + // } else { + // let has_scheduled_update_or_context = + // check_scheduled_update_or_context(current.clone(), render_lane.clone()); + // // The current fiber lane is not included in render_lane + // // TODO context + // if !has_scheduled_update_or_context { + // unsafe { DID_RECEIVE_UPDATE = false } + // match work_in_progress.borrow().tag { + // WorkTag::ContextProvider => { + // let new_value = derive_from_js_value( + // &work_in_progress.borrow().memoized_props, + // "value", + // ); + // let context = + // derive_from_js_value(&work_in_progress.borrow()._type, "_context"); + // push_provider(&context, new_value); + // } + // _ => {} + // } + // return Ok(bailout_on_already_finished_work( + // work_in_progress, + // render_lane, + // )); + // } + // } + // } work_in_progress.borrow_mut().lanes = Lane::NoLane; // if current.is_some() { @@ -124,7 +116,7 @@ pub fn begin_work( WorkTag::MemoComponent => update_memo_component(work_in_progress.clone(), render_lane), WorkTag::Fragment => Ok(update_fragment(work_in_progress.clone())), WorkTag::SuspenseComponent => Ok(update_suspense_component(work_in_progress.clone())), - WorkTag::OffscreenComponent => Ok(update_offscreen_component(work_in_progress)), + WorkTag::OffscreenComponent => Ok(update_offscreen_component(work_in_progress.clone())), }; } @@ -161,8 +153,9 @@ fn update_suspense_fallback_children( fallback_children: JsValue, ) -> Rc> { let current = { work_in_progress.borrow().alternate.clone().unwrap() }; - let current_primary_child_fragment = current.borrow().child.clone().unwrap(); - let current_fallback_child_fragment = current_primary_child_fragment.borrow().sibling.clone(); + let current_primary_child_fragment = { current.borrow().child.clone().unwrap() }; + let current_fallback_child_fragment = + { current_primary_child_fragment.borrow().sibling.clone() }; let primary_child_props = Object::new(); Reflect::set(&primary_child_props, &"mode".into(), &"hidden".into()); @@ -219,8 +212,9 @@ fn update_suspense_primary_children( primary_children: JsValue, ) -> Rc> { let current = { work_in_progress.borrow().alternate.clone().unwrap() }; - let current_primary_child_fragment = current.borrow().child.clone().unwrap(); - let current_fallback_child_fragment = current_primary_child_fragment.borrow().sibling.clone(); + let current_primary_child_fragment = { current.borrow().child.clone().unwrap() }; + let current_fallback_child_fragment = + { current_primary_child_fragment.borrow().sibling.clone() }; let primary_child_props = Object::new(); Reflect::set(&primary_child_props, &"mode".into(), &"visible".into()); @@ -237,13 +231,13 @@ fn update_suspense_primary_children( if current_fallback_child_fragment.is_some() { let current_fallback_child_fragment = current_fallback_child_fragment.unwrap(); - let mut deletions = &work_in_progress.borrow().deletions; + let mut deletions = { work_in_progress.borrow().deletions.clone() }; if deletions.is_empty() { work_in_progress.borrow_mut().deletions = vec![current_fallback_child_fragment]; - work_in_progress.borrow_mut().flags != Flags::ChildDeletion; + work_in_progress.borrow_mut().flags |= Flags::ChildDeletion; } else { let deletions = &mut work_in_progress.borrow_mut().deletions; - deletions.push(current_primary_child_fragment) + deletions.push(current_fallback_child_fragment) } } @@ -257,8 +251,8 @@ fn update_suspense_component( let next_props = { work_in_progress.borrow().pending_props.clone() }; let mut show_fallback = false; - let did_suspend = - (work_in_progress.borrow().flags.clone() & Flags::DidCapture) != Flags::NoFlags; + let flags = { work_in_progress.borrow().flags.clone() }; + let did_suspend = (flags & Flags::DidCapture) != Flags::NoFlags; if did_suspend { show_fallback = true; @@ -301,14 +295,14 @@ fn update_suspense_component( fn update_offscreen_component( work_in_progress: Rc>, ) -> Option>> { - let next_props = work_in_progress.borrow().pending_props.clone(); + let next_props = { work_in_progress.borrow().pending_props.clone() }; let next_children = derive_from_js_value(&next_props, "children"); reconcile_children(work_in_progress.clone(), Some(next_children)); work_in_progress.borrow().child.clone() } fn update_fragment(work_in_progress: Rc>) -> Option>> { - let next_children = work_in_progress.borrow().pending_props.clone(); + let next_children = { work_in_progress.borrow().pending_props.clone() }; reconcile_children(work_in_progress.clone(), Some(next_children)); work_in_progress.borrow().child.clone() } @@ -392,7 +386,7 @@ fn update_function_component( let next_children = render_with_hooks(work_in_progress.clone(), Component, render_lane.clone())?; - let current = work_in_progress.borrow().alternate.clone(); + let current = { work_in_progress.borrow().alternate.clone() }; log!("{:?} {:?}", work_in_progress.clone(), unsafe { DID_RECEIVE_UPDATE }); diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index f0c437a..8049b74 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Function, Reflect}; -use shared::{derive_from_js_value, log, type_of}; +use shared::{is_dev, log, type_of}; use web_sys::Node; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; @@ -290,59 +290,63 @@ fn safely_attach_ref(fiber: Rc>) { } } -// fn commit_update(finished_work: Rc>) { -// let cloned = finished_work.clone(); -// match cloned.borrow().tag { -// WorkTag::HostText => { -// let new_content = derive_from_js_value(&cloned.borrow().pending_props, "content"); -// let state_node = FiberNode::derive_state_node(finished_work.clone()); -// log!("commit_update {:?} {:?}", state_node, new_content); -// if let Some(state_node) = state_node.clone() { -// unsafe { -// HOST_CONFIG -// .as_ref() -// .unwrap() -// .commit_text_update(state_node.clone(), &new_content) -// } -// } -// } -// _ => log!("commit_update, unsupported type"), -// }; -// } +fn record_host_children_to_delete( + children_to_delete: &mut Vec>>, + unmount_fiber: Rc>, +) { + let len = children_to_delete.len(); + if len == 0 { + children_to_delete.push(unmount_fiber.clone()); + } else { + let last_one = &children_to_delete[len - 1]; + let mut node_option = last_one.borrow().sibling.clone(); + while node_option.is_some() { + let node = node_option.unwrap(); + if Rc::ptr_eq(&unmount_fiber, &node) { + children_to_delete.push(unmount_fiber.clone()); + } + node_option = node.borrow().sibling.clone(); + } + } +} fn commit_deletion(child_to_delete: Rc>, root: Rc>) { - let first_host_fiber: Rc>>>> = Rc::new(RefCell::new(None)); + let mut root_children_to_delete: Vec>> = vec![]; + commit_nested_unmounts(child_to_delete.clone(), |unmount_fiber| { - let cloned = first_host_fiber.clone(); match unmount_fiber.borrow().tag { FunctionComponent => { commit_passive_effect(unmount_fiber.clone(), root.clone(), "unmount"); } HostComponent => { - if cloned.borrow().is_none() { - *cloned.borrow_mut() = Some(unmount_fiber.clone()); - } + record_host_children_to_delete(&mut root_children_to_delete, unmount_fiber.clone()); + safely_detach_ref(unmount_fiber.clone()); } HostText => { - if cloned.borrow().is_none() { - *cloned.borrow_mut() = Some(unmount_fiber.clone()); + record_host_children_to_delete(&mut root_children_to_delete, unmount_fiber.clone()); + } + _ => { + if is_dev() { + log!("unsupported unmount type {:?}", unmount_fiber); } } - _ => todo!(), }; }); - let first_host_fiber = first_host_fiber.clone(); - if first_host_fiber.borrow().is_some() { - let host_parent_state_node = - FiberNode::derive_state_node(get_host_parent(child_to_delete.clone()).unwrap()); - let first_host_fiber_state_node = - FiberNode::derive_state_node((*first_host_fiber.borrow()).clone().unwrap()); - unsafe { - HOST_CONFIG.as_ref().unwrap().remove_child( - first_host_fiber_state_node.unwrap(), - host_parent_state_node.unwrap(), - ) + if !root_children_to_delete.is_empty() { + let host_parent = get_host_parent(child_to_delete.clone()); + if host_parent.is_some() { + let host_parent = host_parent.unwrap(); + for child in root_children_to_delete { + let node = FiberNode::derive_state_node(child.clone()); + let host_parent_state_node = FiberNode::derive_state_node(host_parent.clone()); + unsafe { + HOST_CONFIG + .as_ref() + .unwrap() + .remove_child(node.unwrap(), host_parent_state_node.unwrap()) + } + } } } @@ -350,9 +354,10 @@ fn commit_deletion(child_to_delete: Rc>, root: Rc(root: Rc>, on_commit_unmount: F) +// depth-first traversal +fn commit_nested_unmounts(root: Rc>, mut on_commit_unmount: F) where - F: Fn(Rc>), + F: FnMut(Rc>), { let mut node = root.clone(); loop { diff --git a/packages/react-reconciler/src/fiber_throw.rs b/packages/react-reconciler/src/fiber_throw.rs index 0af34de..0b4cfdb 100644 --- a/packages/react-reconciler/src/fiber_throw.rs +++ b/packages/react-reconciler/src/fiber_throw.rs @@ -41,7 +41,7 @@ fn attach_ping_listener(root: Rc>, wakeable: JsValue, lan let mut thread_ids = ping_cache.get(&JsValueKey(wakeable.clone())).unwrap(); - if thread_ids.borrow().contains(&lane) { + if !thread_ids.borrow().contains(&lane) { thread_ids.borrow_mut().insert(lane.clone()); let then_value = derive_from_js_value(&wakeable, "then"); let then = then_value.dyn_ref::().unwrap(); diff --git a/packages/react-reconciler/src/thenable.rs b/packages/react-reconciler/src/thenable.rs index 3a72585..c513171 100644 --- a/packages/react-reconciler/src/thenable.rs +++ b/packages/react-reconciler/src/thenable.rs @@ -21,9 +21,7 @@ pub fn get_suspense_thenable() -> JsValue { pub fn track_used_thenable(thenable: JsValue) -> Result { let status = derive_from_js_value(&thenable, "status"); - if status.is_string() { - let status = status.as_string().unwrap(); if status == "fulfilled" { return Ok(derive_from_js_value(&thenable, "value")); } else if status == "rejected" { diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 80d2403..3450f69 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -73,7 +73,6 @@ pub fn mark_update_lane_from_fiber_to_root( let alternate = alternate.unwrap(); let child_lanes = { alternate.borrow().child_lanes.clone() }; alternate.borrow_mut().child_lanes = merge_lanes(child_lanes, lane.clone()); - log!("mark_update_lane_from_fiber_to_root alternate {:?}", p); } node = parent.clone().unwrap();