diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 3a7c6a3..862ad52 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,13 +1,20 @@ import {useState} from 'react' +let n = 0 + function App() { - const [name, setName] = useState(() => 'ayou') - setTimeout(() => { - setName('ayouayou') - }, 1000) - return ( -
{name}
- ) + const [name, setName] = useState(() => false) + const [age, setAge] = useState(() => 10) + if (n === 0) { + let tid = setTimeout(() => { + n++ + setName(true) + setAge(11) + clearTimeout((tid)) + }, 1000) + } + + return name ? {name + age} : 'N/A' } function Comp({children}) { diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index 8f70611..4f2a33c 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -2,5 +2,7 @@ import {createRoot} from 'react-dom' import App from './App.tsx' const root = createRoot(document.getElementById("root")) +const a = +console.log(a) root.render() diff --git a/package.json b/package.json index 01f35de..97d46ca 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "build": "node scripts/build.js", "build:test": "node scripts/build.js --test", - "test": "jest" + "test": "npm run build:test && jest" }, "author": "", "license": "ISC", diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index 42a276c..a55ec95 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -1,7 +1,7 @@ use std::any::Any; use std::rc::Rc; -use web_sys::{window, Node}; +use web_sys::{Node, window}; use react_reconciler::HostConfig; use shared::log; @@ -38,4 +38,20 @@ impl HostConfig for ReactDomHostConfig { fn append_child_to_container(&self, child: Rc, parent: Rc) { self.append_initial_child(parent, child) } + + fn remove_child(&self, child: Rc, container: Rc) { + let p = container.clone().downcast::().unwrap(); + let c = child.clone().downcast::().unwrap(); + match p.remove_child(&c) { + Ok(_) => { + log!("remove_child successfully {:?} {:?}", p, c); + } + Err(_) => todo!(), + } + } + + fn commit_text_update(&self, text_instance: Rc, content: String) { + let text_instance = text_instance.clone().downcast::().unwrap(); + text_instance.set_node_value(Some(content.as_str())); + } } diff --git a/packages/react-dom/src/renderer.rs b/packages/react-dom/src/renderer.rs index 9cf8982..a79090c 100644 --- a/packages/react-dom/src/renderer.rs +++ b/packages/react-dom/src/renderer.rs @@ -23,6 +23,6 @@ impl Renderer { impl Renderer { pub fn render(&self, element: &JsValue) -> JsValue { self.reconciler - .update_container(Rc::new(element.clone()), self.root.clone()) + .update_container(element.clone(), self.root.clone()) } } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index f52c61e..f865a2c 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -26,17 +26,32 @@ pub fn begin_work( fn update_function_component( work_in_progress: Rc>, ) -> Result>>, JsValue> { - let next_children = Rc::new(render_with_hooks(work_in_progress.clone())?); + let next_children = render_with_hooks(work_in_progress.clone())?; reconcile_children(work_in_progress.clone(), Some(next_children)); Ok(work_in_progress.clone().borrow().child.clone()) } fn update_host_root(work_in_progress: Rc>) -> Option>> { - process_update_queue(work_in_progress.clone()); - let next_children = work_in_progress.clone().borrow().memoized_state.clone(); + let work_in_progress_cloned = work_in_progress.clone(); + + let base_state; + let update_queue; + { + let work_in_progress_borrowed = work_in_progress_cloned.borrow(); + base_state = work_in_progress_borrowed.memoized_state.clone(); + update_queue = work_in_progress_borrowed.update_queue.clone(); + } + + { + work_in_progress.clone().borrow_mut().memoized_state = + process_update_queue(base_state, update_queue, work_in_progress.clone()); + } + + let next_children = work_in_progress_cloned.borrow().memoized_state.clone(); if next_children.is_none() { panic!("update_host_root next_children is none") } + if let MemoizedState::JsValue(next_children) = next_children.unwrap() { reconcile_children(work_in_progress.clone(), Some(next_children)); } @@ -50,22 +65,25 @@ fn update_host_component( let next_children = { let ref_fiber_node = work_in_progress.borrow(); - derive_from_js_value(ref_fiber_node.pending_props.clone().unwrap(), "children") + derive_from_js_value(&ref_fiber_node.pending_props, "children") }; { - reconcile_children(work_in_progress.clone(), next_children); + reconcile_children(work_in_progress.clone(), Some(next_children)); } work_in_progress.clone().borrow().child.clone() } -fn reconcile_children(work_in_progress: Rc>, children: Option>) { +fn reconcile_children(work_in_progress: Rc>, children: Option) { let work_in_progress = Rc::clone(&work_in_progress); let current = { work_in_progress.borrow().alternate.clone() }; if current.is_some() { // update - work_in_progress.borrow_mut().child = - reconcile_child_fibers(work_in_progress.clone(), current.clone(), children) + work_in_progress.borrow_mut().child = reconcile_child_fibers( + work_in_progress.clone(), + current.clone().unwrap().clone().borrow().child.clone(), + children, + ) } else { // mount work_in_progress.borrow_mut().child = diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index 8f30887..8bb5def 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -9,12 +9,20 @@ use shared::{derive_from_js_value, log, REACT_ELEMENT_TYPE}; use crate::fiber::FiberNode; use crate::fiber_flags::Flags; use crate::work_tags::WorkTag; +use crate::work_tags::WorkTag::HostText; + +fn use_fiber(fiber: Rc>, pending_props: JsValue) -> Rc> { + let clone = FiberNode::create_work_in_progress(fiber, pending_props); + clone.borrow_mut().index = 0; + clone.borrow_mut().sibling = None; + clone +} fn place_single_child( fiber: Rc>, should_track_effect: bool, ) -> Rc> { - if should_track_effect { + if should_track_effect && fiber.clone().borrow().alternate.is_none() { let fiber = fiber.clone(); let mut fiber = fiber.borrow_mut(); fiber.flags |= Flags::Placement; @@ -22,12 +30,77 @@ fn place_single_child( return fiber; } +fn delete_child( + return_fiber: Rc>, + child_to_delete: Rc>, + should_track_effect: bool, +) { + if !should_track_effect { + return; + } + + + let deletions = { + let return_fiber_borrowed = return_fiber.borrow(); + return_fiber_borrowed.deletions.clone() + }; + if deletions.is_none() { + return_fiber.borrow_mut().deletions = Some(vec![child_to_delete.clone()]); + return_fiber.borrow_mut().flags |= Flags::ChildDeletion; + } else { + let mut del = return_fiber.borrow_mut().deletions.clone().unwrap(); + del.push(child_to_delete.clone()); + } +} + fn reconcile_single_element( return_fiber: Rc>, current_first_child: Option>>, - element: Option>, + element: Option, + should_track_effect: bool, ) -> Rc> { - let mut fiber = FiberNode::create_fiber_from_element(element.unwrap()); + if element.is_none() { + panic!("reconcile_single_element, element is none") + } + + let element = element.as_ref().unwrap(); + let key = derive_from_js_value(&(*element).clone(), "key"); + if current_first_child.is_some() { + let current_first_child_cloned = current_first_child.clone().unwrap().clone(); + // Be careful, it is different with === + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#same-value_equality_using_object.is + if Object::is(¤t_first_child_cloned.borrow().key, &key) { + if derive_from_js_value(&(*element).clone(), "$$typeof") != REACT_ELEMENT_TYPE { + panic!("Undefined $$typeof"); + } + + if Object::is( + ¤t_first_child_cloned.borrow()._type, + &derive_from_js_value(&(*element).clone(), "type"), + ) { + // type is the same, update props + let existing = use_fiber( + current_first_child.clone().unwrap().clone(), + derive_from_js_value(&(*element).clone(), "props"), + ); + existing.clone().borrow_mut()._return = Some(return_fiber); + return existing; + } + delete_child( + return_fiber.clone(), + current_first_child.clone().unwrap().clone(), + should_track_effect, + ); + } else { + delete_child( + return_fiber.clone(), + current_first_child.clone().unwrap().clone(), + should_track_effect, + ); + } + } + + let mut fiber = FiberNode::create_fiber_from_element(element); fiber._return = Some(return_fiber.clone()); Rc::new(RefCell::new(fiber)) } @@ -35,12 +108,25 @@ fn reconcile_single_element( fn reconcile_single_text_node( return_fiber: Rc>, current_first_child: Option>>, - content: Option>, + content: Option, + should_track_effect: bool, ) -> Rc> { let props = Object::new(); Reflect::set(&props, &JsValue::from("content"), &content.unwrap().clone()) .expect("props panic"); - let mut created = FiberNode::new(WorkTag::HostText, Some(Rc::new(Object::into(props))), None); + + if current_first_child.is_some() && current_first_child.as_ref().unwrap().borrow().tag == HostText { + let existing = use_fiber(current_first_child.as_ref().unwrap().clone(), (*props).clone()); + existing.borrow_mut()._return = Some(return_fiber.clone()); + return existing; + } + + if current_first_child.is_some() { + delete_child(return_fiber.clone(), current_first_child.clone().unwrap(), should_track_effect); + } + + + let mut created = FiberNode::new(WorkTag::HostText, (*props).clone(), JsValue::null()); created._return = Some(return_fiber.clone()); Rc::new(RefCell::new(created)) } @@ -48,11 +134,11 @@ fn reconcile_single_text_node( fn _reconcile_child_fibers( return_fiber: Rc>, current_first_child: Option>>, - new_child: Option>, + new_child: Option, should_track_effect: bool, ) -> Option>> { if new_child.is_some() { - let new_child = Rc::clone(&new_child.unwrap()); + let new_child = &new_child.unwrap(); if new_child.is_string() { return Some(place_single_child( @@ -60,19 +146,19 @@ fn _reconcile_child_fibers( return_fiber, current_first_child, Some(new_child.clone()), + should_track_effect, ), should_track_effect, )); } else if new_child.is_object() { - if let Some(_typeof) = - Rc::clone(&derive_from_js_value(new_child.clone(), "$$typeof").unwrap()).as_string() - { + if let Some(_typeof) = derive_from_js_value(&new_child, "$$typeof").as_string() { if _typeof == REACT_ELEMENT_TYPE { return Some(place_single_child( reconcile_single_element( return_fiber, current_first_child, Some(new_child.clone()), + should_track_effect, ), should_track_effect, )); @@ -87,7 +173,7 @@ fn _reconcile_child_fibers( pub fn reconcile_child_fibers( return_fiber: Rc>, current_first_child: Option>>, - new_child: Option>, + new_child: Option, ) -> Option>> { _reconcile_child_fibers(return_fiber, current_first_child, new_child, true) } @@ -95,7 +181,7 @@ pub fn reconcile_child_fibers( pub fn mount_child_fibers( return_fiber: Rc>, current_first_child: Option>>, - new_child: Option>, + new_child: Option, ) -> Option>> { _reconcile_child_fibers(return_fiber, current_first_child, new_child, false) } diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index f91cf1f..6f10bc8 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -2,12 +2,12 @@ use std::any::Any; use std::cell::RefCell; use std::rc::Rc; -use shared::log; +use shared::{derive_from_js_value, log}; use crate::fiber::{FiberNode, StateNode}; -use crate::fiber_flags::{get_mutation_mask, Flags}; -use crate::work_tags::WorkTag; +use crate::fiber_flags::{Flags, get_mutation_mask}; use crate::HostConfig; +use crate::work_tags::WorkTag; pub struct CommitWork { next_effect: Option>>, @@ -69,7 +69,121 @@ impl CommitWork { let flags = finished_work.clone().borrow().flags.clone(); if flags.contains(Flags::Placement) { self.commit_placement(finished_work.clone()); - finished_work.clone().borrow_mut().flags -= Flags::Placement + finished_work.clone().borrow_mut().flags -= Flags::Placement; + } + + if flags.contains(Flags::ChildDeletion) { + let deletions = finished_work.clone().borrow().deletions.clone(); + if deletions.is_some() { + let deletions = deletions.unwrap(); + for child_to_delete in deletions { + self.commit_deletion(child_to_delete); + } + } + finished_work.clone().borrow_mut().flags -= Flags::ChildDeletion; + } + + if flags.contains(Flags::Update) { + self.commit_update(finished_work.clone()); + finished_work.clone().borrow_mut().flags -= Flags::Update; + } + } + + fn commit_update(&self, 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()); + if let Some(state_node) = state_node.clone() { + self.host_config + .commit_text_update(state_node.clone(), new_content.as_string().unwrap()) + } + } + _ => log!("commit_update, unsupported type"), + }; + } + + fn commit_deletion(&self, child_to_delete: Rc>) { + let first_host_fiber: Rc>>>> = Rc::new(RefCell::new(None)); + self.commit_nested_unmounts(child_to_delete.clone(), |unmount_fiber| { + let cloned = first_host_fiber.clone(); + match unmount_fiber.borrow().tag { + WorkTag::FunctionComponent => {} + WorkTag::HostRoot => {} + WorkTag::HostComponent => { + if cloned.borrow().is_none() { + *cloned.borrow_mut() = Some(unmount_fiber.clone()); + } + } + WorkTag::HostText => { + if cloned.borrow().is_none() { + *cloned.borrow_mut() = Some(unmount_fiber.clone()); + } + } + }; + }); + + let first_host_fiber = first_host_fiber.clone(); + if first_host_fiber.borrow().is_some() { + let host_parent_state_node = FiberNode::derive_state_node( + self.get_host_parent(child_to_delete.clone()).unwrap(), + ); + let first_host_fiber_state_node = + FiberNode::derive_state_node((*first_host_fiber.borrow()).clone().unwrap()); + self.host_config.remove_child( + first_host_fiber_state_node.unwrap(), + host_parent_state_node.unwrap(), + ) + } + + child_to_delete.clone().borrow_mut()._return = None; + child_to_delete.clone().borrow_mut().child = None; + } + + + fn commit_nested_unmounts(&self, root: Rc>, on_commit_unmount: F) + where + F: Fn(Rc>), + { + let mut node = root.clone(); + loop { + on_commit_unmount(node.clone()); + + let node_cloned = node.clone(); + if node_cloned.borrow().child.is_some() { + node_cloned + .borrow_mut() + .child + .clone() + .unwrap() + .clone() + .borrow_mut() + ._return = Some(node.clone()); + node = node_cloned.borrow().child.clone().unwrap(); + continue; + } + if Rc::ptr_eq(&node, &root.clone()) { + return; + } + while node.clone().borrow().sibling.is_none() { + if node.clone().borrow()._return.is_none() + || Rc::ptr_eq(node.clone().borrow()._return.as_ref().unwrap(), &root) + { + return; + } + node = node.clone().borrow()._return.clone().unwrap(); + } + + node_cloned + .borrow_mut() + .sibling + .clone() + .unwrap() + .clone() + .borrow_mut() + ._return = node_cloned.borrow()._return.clone(); + node = node_cloned.borrow().sibling.clone().unwrap(); } } @@ -103,7 +217,6 @@ impl CommitWork { let fiber = fiber.clone(); let tag = fiber.borrow().tag.clone(); if tag == WorkTag::HostComponent || tag == WorkTag::HostText { - log!("{:?}", fiber.clone().borrow()._type); let state_node = fiber.clone().borrow().state_node.clone().unwrap(); self.host_config.append_child_to_container( self.get_element_from_state_node(state_node), diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 6c9788c..27efe19 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -3,12 +3,14 @@ use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::JsValue; -use web_sys::js_sys::Reflect; +use web_sys::js_sys::{Object, Reflect}; + +use shared::{derive_from_js_value, log}; use crate::fiber::{FiberNode, StateNode}; use crate::fiber_flags::Flags; -use crate::work_tags::WorkTag; use crate::HostConfig; +use crate::work_tags::WorkTag; pub struct CompleteWork { pub host_config: Rc, @@ -63,9 +65,9 @@ impl CompleteWork { let node_cloned = node.clone().unwrap().clone(); if node_cloned.borrow()._return.is_none() || Rc::ptr_eq( - &node_cloned.borrow()._return.as_ref().unwrap(), - &work_in_progress, - ) + &node_cloned.borrow()._return.as_ref().unwrap(), + &work_in_progress, + ) { return; } @@ -110,12 +112,18 @@ impl CompleteWork { complete_work.clone().borrow_mut().subtree_flags |= subtree_flags.clone(); } + fn mark_update(fiber: Rc>) { + fiber.borrow_mut().flags |= Flags::Update; + } + pub fn complete_work( &self, work_in_progress: Rc>, ) -> Option>> { - let new_props = { work_in_progress.clone().borrow().pending_props.clone() }; - let tag = { work_in_progress.clone().borrow().tag.clone() }; + let work_in_progress_cloned = work_in_progress.clone(); + let new_props = { work_in_progress_cloned.borrow().pending_props.clone() }; + let current = { work_in_progress_cloned.borrow().alternate.clone() }; + let tag = { work_in_progress_cloned.borrow().tag.clone() }; match tag { WorkTag::FunctionComponent => { self.bubble_properties(work_in_progress.clone()); @@ -126,32 +134,44 @@ impl CompleteWork { None } WorkTag::HostComponent => { - let instance = self.host_config.create_instance( - work_in_progress - .clone() - .borrow() - ._type - .clone() - .unwrap() - .clone() - .as_string() - .unwrap(), - ); - self.append_all_children(instance.clone(), work_in_progress.clone()); - work_in_progress.clone().borrow_mut().state_node = - Some(Rc::new(StateNode::Element(instance.clone()))); + if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { + log!("update properties") + } else { + let instance = self.host_config.create_instance( + work_in_progress + .clone() + .borrow() + ._type + .as_ref() + .as_string() + .unwrap(), + ); + self.append_all_children(instance.clone(), work_in_progress.clone()); + work_in_progress.clone().borrow_mut().state_node = + Some(Rc::new(StateNode::Element(instance.clone()))); + } + self.bubble_properties(work_in_progress.clone()); None } WorkTag::HostText => { - let text_instance = self.host_config.create_text_instance( - Reflect::get(&new_props.unwrap(), &JsValue::from_str("content")) - .unwrap() - .as_string() - .unwrap(), - ); - work_in_progress.clone().borrow_mut().state_node = - Some(Rc::new(StateNode::Element(text_instance.clone()))); + if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { + let old_text = derive_from_js_value(¤t.clone().unwrap().clone().borrow().memoized_props, "content"); + let new_test = derive_from_js_value(&new_props, "content"); + if !Object::is(&old_text, &new_test) { + CompleteWork::mark_update(work_in_progress.clone()); + } + } else { + let text_instance = self.host_config.create_text_instance( + Reflect::get(&new_props, &JsValue::from_str("content")) + .unwrap() + .as_string() + .unwrap(), + ); + work_in_progress.clone().borrow_mut().state_node = + Some(Rc::new(StateNode::Element(text_instance.clone()))); + } + self.bubble_properties(work_in_progress.clone()); None } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index fdd2c06..2b1c553 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -23,31 +23,43 @@ pub enum StateNode { #[derive(Debug, Clone)] pub enum MemoizedState { - JsValue(Rc), + JsValue(JsValue), Hook(Rc>), } +impl MemoizedState { + pub fn js_value(&self) -> Option { + match self { + MemoizedState::JsValue(js_value) => Some(js_value.clone()), + MemoizedState::Hook(_) => None + } + } +} + #[derive(Debug)] pub struct FiberNode { + pub index: u32, pub tag: WorkTag, - pub pending_props: Option>, - key: Option, + pub pending_props: JsValue, + pub key: JsValue, pub state_node: Option>, pub update_queue: Option>>, pub _return: Option>>, pub sibling: Option>>, pub child: Option>>, pub alternate: Option>>, - pub _type: Option>, + pub _type: JsValue, pub flags: Flags, pub subtree_flags: Flags, - pub memoized_props: Option>, + pub memoized_props: JsValue, pub memoized_state: Option, + pub deletions: Option>>>, } impl FiberNode { - pub fn new(tag: WorkTag, pending_props: Option>, key: Option) -> Self { + pub fn new(tag: WorkTag, pending_props: JsValue, key: JsValue) -> Self { Self { + index: 0, tag, pending_props, key, @@ -57,26 +69,26 @@ impl FiberNode { sibling: None, child: None, alternate: None, - _type: None, - memoized_props: None, + _type: JsValue::null(), + memoized_props: JsValue::null(), memoized_state: None, flags: Flags::NoFlags, subtree_flags: Flags::NoFlags, + deletions: None, } } - pub fn create_fiber_from_element(ele: Rc) -> Self { - let _type = derive_from_js_value(ele.clone(), "type"); - let key = match derive_from_js_value(ele.clone(), "key") { - None => None, - Some(k) => k.as_string(), - }; - let props = derive_from_js_value(ele.clone(), "props"); + pub fn create_fiber_from_element(ele: &JsValue) -> Self { + let _type = derive_from_js_value(ele, "type"); + let key = derive_from_js_value(ele, "key"); + let props = derive_from_js_value(ele, "props"); let mut fiber_tag = WorkTag::FunctionComponent; - if _type.is_some() && (*_type.as_ref().unwrap()).is_string() { + if _type.is_string() { fiber_tag = WorkTag::HostComponent } + + let mut fiber = FiberNode::new(fiber_tag, props, key); fiber._type = _type; fiber @@ -96,7 +108,7 @@ impl FiberNode { pub fn create_work_in_progress( current: Rc>, - pending_props: Rc, + pending_props: JsValue, ) -> Rc> { let c_rc = Rc::clone(¤t); let w = { @@ -107,8 +119,13 @@ impl FiberNode { return if w.is_none() { let mut wip = { let c = c_rc.borrow(); - let mut wip = FiberNode::new(c.tag.clone(), c.pending_props.clone(), c.key.clone()); - wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); + let mut wip = FiberNode::new(c.tag.clone(), pending_props, c.key.clone()); + wip.update_queue = match c.update_queue.as_ref() { + None => None, + Some(update_queue) => { + Some(update_queue.clone()) + } + }; wip.flags = c.flags.clone(); wip.child = c.child.clone(); wip.memoized_props = c.memoized_props.clone(); @@ -119,21 +136,25 @@ impl FiberNode { wip.state_node = c_rc.borrow().state_node.clone(); wip.alternate = Some(current); let wip_rc = Rc::new(RefCell::new(wip)); - let mut fibler_node = c_rc.borrow_mut(); - fibler_node.alternate = Some(wip_rc.clone()); + { + let mut fibler_node = c_rc.borrow_mut(); + fibler_node.alternate = Some(wip_rc.clone()); + } wip_rc } else { - let c = c_rc.borrow(); - let a = w.clone().unwrap(); - let mut wip = a.borrow_mut(); - - wip.pending_props = Some(pending_props.clone()); - wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); - wip.flags = c.flags.clone(); - wip.child = c.child.clone(); - wip.memoized_props = c.memoized_props.clone(); - wip.memoized_state = c.memoized_state.clone(); - w.clone().unwrap() + let w = w.clone().unwrap(); + { + let wip_cloned = w.clone(); + let mut wip = wip_cloned.borrow_mut(); + let c = c_rc.borrow(); + wip.pending_props = pending_props; + wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); + wip.flags = c.flags.clone(); + wip.child = c.child.clone(); + wip.memoized_props = c.memoized_props.clone(); + wip.memoized_state = c.memoized_state.clone(); + } + w.clone() }; } @@ -182,11 +203,11 @@ impl Debug for FiberRootNode { write!( f, "{:?}(flags:{:?}, subtreeFlags:{:?})", - current_borrowed._type.as_ref().unwrap(), + current_borrowed._type.as_ref(), current_borrowed.flags, current_borrowed.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostRoot => { write!( @@ -195,7 +216,7 @@ impl Debug for FiberRootNode { WorkTag::HostRoot, current_ref.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostComponent => { let current_borrowed = current.borrow(); @@ -204,14 +225,11 @@ impl Debug for FiberRootNode { "{:?}(flags:{:?}, subtreeFlags:{:?})", current_borrowed ._type - .as_ref() - .unwrap() - .as_string() - .unwrap(), + .as_ref().as_string().unwrap(), current_borrowed.flags, current_borrowed.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostText => { let current_borrowed = current.borrow(); @@ -221,15 +239,15 @@ impl Debug for FiberRootNode { "{:?}(state_node:{:?}, flags:{:?})", current_borrowed.tag, Reflect::get( - current_borrowed.pending_props.as_ref().unwrap(), + current_borrowed.pending_props.as_ref(), &JsValue::from_str("content"), ) - .unwrap() - .as_string() - .unwrap(), + .unwrap() + .as_string() + .unwrap(), current_borrowed.flags ) - .expect("print error"); + .expect("print error"); } }; if let Some(ref child) = current_ref.child { diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index fae5601..39eb62e 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,14 +1,16 @@ use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen::prelude::{Closure, wasm_bindgen}; use web_sys::js_sys::{Function, Object, Reflect}; use shared::log; use crate::fiber::{FiberNode, MemoizedState}; -use crate::update_queue::{create_update, create_update_queue, enqueue_update, UpdateQueue}; +use crate::update_queue::{ + create_update, create_update_queue, enqueue_update, process_update_queue, UpdateQueue, +}; use crate::work_loop::WorkLoop; #[wasm_bindgen] @@ -18,6 +20,7 @@ extern "C" { static mut CURRENTLY_RENDERING_FIBER: Option>> = None; static mut WORK_IN_PROGRESS_HOOK: Option>> = None; +static mut CURRENT_HOOK: Option>> = None; pub static mut WORK_LOOP: Option>> = None; #[derive(Debug, Clone)] @@ -28,21 +31,24 @@ pub struct Hook { } impl Hook { - fn new() -> Self { + fn new( + memoized_state: Option, + update_queue: Option>>, + next: Option>>, + ) -> Self { Hook { - memoized_state: None, - update_queue: None, - next: None, + memoized_state, + update_queue, + next, } } } -fn update_mount_hooks_to_dispatcher() { +fn update_hooks_to_dispatcher(is_update: bool) { let object = Object::new(); - let closure = Closure::wrap( - Box::new(mount_state) as Box Result, JsValue>> - ); + let closure = Closure::wrap(Box::new(if is_update { update_state } else { mount_state }) + as Box Result, JsValue>>); let function = closure.as_ref().unchecked_ref::().clone(); closure.forget(); Reflect::set(&object, &"use_state".into(), &function).expect("TODO: panic set use_state"); @@ -63,26 +69,33 @@ pub fn render_with_hooks(work_in_progress: Rc>) -> Result(&_type).unwrap(); let children = component.call1(&JsValue::null(), &props); + + unsafe { + CURRENTLY_RENDERING_FIBER = None; + WORK_IN_PROGRESS_HOOK = None; + CURRENT_HOOK = None; + } + children } -fn mount_work_in_progress_nook() -> Option>> { - let hook = Rc::new(RefCell::new(Hook::new())); +fn mount_work_in_progress_hook() -> Option>> { + let hook = Rc::new(RefCell::new(Hook::new(None, None, None))); unsafe { if WORK_IN_PROGRESS_HOOK.is_none() { if CURRENTLY_RENDERING_FIBER.is_none() { @@ -109,8 +122,96 @@ fn mount_work_in_progress_nook() -> Option>> { } } +fn update_work_in_progress_hook() -> Option>> { + // case1: Update triggered by interaction, the wip_hook is none, use hook in current_hook to clone wip_hook + // case2: Update triggered in render process, the wip_hook exists + let mut next_current_hook: Option>> = None; + let mut next_work_in_progress_hook: Option>> = None; + + unsafe { + next_current_hook = match &CURRENT_HOOK { + None => { + let current = CURRENTLY_RENDERING_FIBER + .as_ref() + .unwrap() + .clone() + .borrow() + .alternate + .clone(); + + match current { + None => None, + Some(current) => { + match current.clone().borrow().memoized_state.clone() { + Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), + _ => None, + } + } + } + } + Some(current_hook) => current_hook.clone().borrow().next.clone(), + }; + + next_work_in_progress_hook = match &WORK_IN_PROGRESS_HOOK { + None => { + match CURRENTLY_RENDERING_FIBER.clone() { + Some(current) => { + match current.clone().borrow().memoized_state.clone() { + Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), + _ => None, + } + } + _ => None, + } + } + Some(work_in_progress_hook) => work_in_progress_hook.clone().borrow().next.clone(), + }; + + if next_work_in_progress_hook.is_some() { + WORK_IN_PROGRESS_HOOK = next_work_in_progress_hook.clone(); + CURRENT_HOOK = next_current_hook.clone(); + } else { + if next_current_hook.is_none() { + log!( + "{:?} hooks is more than last", + CURRENTLY_RENDERING_FIBER + .as_ref() + .unwrap() + .clone() + .borrow() + ._type + ); + } + + CURRENT_HOOK = next_current_hook; + let cloned = CURRENT_HOOK.clone().unwrap().clone(); + let current_hook = cloned.borrow(); + let new_hook = Rc::new(RefCell::new(Hook::new( + current_hook.memoized_state.clone(), + current_hook.update_queue.clone(), + None, + ))); + + if WORK_IN_PROGRESS_HOOK.is_none() { + WORK_IN_PROGRESS_HOOK = Some(new_hook.clone()); + CURRENTLY_RENDERING_FIBER + .as_ref() + .unwrap() + .clone() + .borrow_mut() + .memoized_state = Some(MemoizedState::Hook(new_hook.clone())); + } else { + let wip_hook = WORK_IN_PROGRESS_HOOK.clone().unwrap(); + wip_hook.borrow_mut().next = Some(new_hook.clone()); + WORK_IN_PROGRESS_HOOK = Some(new_hook.clone()); + } + } + WORK_IN_PROGRESS_HOOK.clone() + } +} + fn mount_state(initial_state: &JsValue) -> Result, JsValue> { - let hook = mount_work_in_progress_nook(); + let hook = mount_work_in_progress_hook(); let memoized_state: JsValue; if initial_state.is_function() { @@ -122,7 +223,7 @@ fn mount_state(initial_state: &JsValue) -> Result, JsValue> { memoized_state = initial_state.clone(); } hook.as_ref().unwrap().clone().borrow_mut().memoized_state = - Some(MemoizedState::JsValue(Rc::new((memoized_state.clone())))); + Some(MemoizedState::JsValue(memoized_state.clone())); unsafe { if CURRENTLY_RENDERING_FIBER.is_none() { @@ -130,28 +231,67 @@ fn mount_state(initial_state: &JsValue) -> Result, JsValue> { } } let queue = create_update_queue(); - hook.as_ref().unwrap().clone().borrow_mut().update_queue = Option::from(queue.clone()); - - let closure = Closure::wrap(Box::new(move |action: &JsValue| unsafe { + hook.as_ref().unwrap().clone().borrow_mut().update_queue = Some(queue.clone()); + let q_rc = Rc::new(queue.clone()); + let q_rc_cloned = q_rc.clone(); + let fiber = unsafe { + CURRENTLY_RENDERING_FIBER.clone().unwrap() + }; + let closure = Closure::wrap(Box::new(move |action: &JsValue| { dispatch_set_state( - CURRENTLY_RENDERING_FIBER.clone().unwrap(), - queue.clone(), + fiber.clone(), + (*q_rc_cloned).clone(), action, ) }) as Box); let function = closure.as_ref().unchecked_ref::().clone(); closure.forget(); + queue.clone().borrow_mut().dispatch = Some(function.clone()); + Ok(vec![memoized_state, function.into()]) } +fn update_state(initial_state: &JsValue) -> Result, JsValue> { + let hook = update_work_in_progress_hook(); + + if hook.is_none() { + panic!("update_state hook is none") + } + + let hook_cloned = hook.clone().unwrap().clone(); + let queue = hook_cloned.borrow().update_queue.clone(); + let base_state = hook_cloned.borrow().memoized_state.clone(); + + // Todo update when render + unsafe { + hook_cloned.borrow_mut().memoized_state = process_update_queue( + base_state, + queue.clone(), + CURRENTLY_RENDERING_FIBER.clone().unwrap(), + ); + } + log!("memoized_state {:?}", hook_cloned.borrow().memoized_state); + + Ok(vec![ + hook.clone().unwrap().clone() + .borrow() + .memoized_state + .clone() + .unwrap() + .js_value() + .unwrap().clone(), + queue.clone().unwrap().borrow().dispatch.clone().into(), + ]) +} + fn dispatch_set_state( fiber: Rc>, update_queue: Rc>, action: &JsValue, ) { - let update = create_update(Rc::new(action.clone())); - enqueue_update(update_queue, update); + let update = create_update(action.clone()); + enqueue_update(update_queue.clone(), update); unsafe { WORK_LOOP .as_ref() diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 205a952..6d30a4b 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -26,6 +26,8 @@ pub trait HostConfig { fn create_instance(&self, _type: String) -> Rc; fn append_initial_child(&self, parent: Rc, child: Rc); fn append_child_to_container(&self, child: Rc, parent: Rc); + fn remove_child(&self, child: Rc, container: Rc); + fn commit_text_update(&self, text_instance: Rc, content: String); } pub struct Reconciler { @@ -37,7 +39,7 @@ impl Reconciler { Reconciler { host_config } } pub fn create_container(&self, container: Rc) -> Rc> { - let host_root_fiber = Rc::new(RefCell::new(FiberNode::new(WorkTag::HostRoot, None, None))); + let host_root_fiber = Rc::new(RefCell::new(FiberNode::new(WorkTag::HostRoot, JsValue::null(), JsValue::null()))); host_root_fiber.clone().borrow_mut().update_queue = Some(create_update_queue()); let root = Rc::new(RefCell::new(FiberRootNode::new( container.clone(), @@ -48,7 +50,7 @@ impl Reconciler { root.clone() } - pub fn update_container(&self, element: Rc, root: Rc>) -> JsValue { + pub fn update_container(&self, element: JsValue, root: Rc>) -> JsValue { let host_root_fiber = Rc::clone(&root).borrow().current.clone(); let update = create_update(element.clone()); enqueue_update( @@ -63,6 +65,6 @@ impl Reconciler { .clone() .borrow() .schedule_update_on_fiber(host_root_fiber); - (&*element.clone()).clone() + element.clone() } } diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 234db7d..4279db7 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -13,7 +13,7 @@ pub struct UpdateAction; #[derive(Clone, Debug)] pub struct Update { - pub action: Option>, + pub action: Option, } #[derive(Clone, Debug)] @@ -24,9 +24,10 @@ pub struct UpdateType { #[derive(Clone, Debug)] pub struct UpdateQueue { pub shared: UpdateType, + pub dispatch: Option, } -pub fn create_update(action: Rc) -> Update { +pub fn create_update(action: JsValue) -> Update { Update { action: Some(action), } @@ -39,37 +40,44 @@ pub fn enqueue_update(update_queue: Rc>, update: Update) { pub fn create_update_queue() -> Rc> { Rc::new(RefCell::new(UpdateQueue { shared: UpdateType { pending: None }, + dispatch: None, })) } -pub fn process_update_queue(fiber: Rc>) { - let rc_fiber = fiber.clone(); - let mut new_state = rc_fiber.borrow().memoized_state.clone(); - match rc_fiber.borrow().update_queue.clone() { - None => { - log!("{:?} process_update_queue, update_queue is empty", fiber) - } - Some(q) => { - let update_queue = q.clone(); - let pending = update_queue.clone().borrow().shared.pending.clone(); - update_queue.borrow_mut().shared.pending = None; - if pending.is_some() { - let action = pending.unwrap().action; - match action { - None => {} - Some(action) => { - let f = action.dyn_ref::(); - new_state = match f { - None => Some(MemoizedState::JsValue(action.clone())), - Some(f) => Some(MemoizedState::JsValue(Rc::new( - f.call0(&JsValue::null()).unwrap(), - ))), +pub fn process_update_queue( + mut base_state: Option, + update_queue: Option>>, + fiber: Rc>, +) -> Option { + if update_queue.is_some() { + let update_queue = update_queue.clone().unwrap().clone(); + let pending = update_queue.borrow().shared.pending.clone(); + update_queue.borrow_mut().shared.pending = None; + if pending.is_some() { + let action = pending.unwrap().action; + match action { + None => {} + Some(action) => { + let f = action.dyn_ref::(); + base_state = match f { + None => Some(MemoizedState::JsValue(action.clone())), + Some(f) => { + if let MemoizedState::JsValue(base_state) = base_state.as_ref().unwrap() { + Some(MemoizedState::JsValue( + f.call1(&JsValue::null(), base_state).unwrap(), + )) + } else { + log!("process_update_queue, base_state is not JsValue"); + None + } } } } } } + } else { + log!("{:?} process_update_queue, update_queue is empty", fiber) } - fiber.clone().borrow_mut().memoized_state = new_state + base_state } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 5e51d08..f25edd1 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -10,8 +10,8 @@ use crate::commit_work::CommitWork; use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::fiber_flags::get_mutation_mask; -use crate::work_tags::WorkTag; use crate::HostConfig; +use crate::work_tags::WorkTag; static mut WORK_IN_PROGRESS: Option>> = None; @@ -137,7 +137,7 @@ impl WorkLoop { unsafe { WORK_IN_PROGRESS = Some(FiberNode::create_work_in_progress( root.borrow().current.clone(), - Rc::new(JsValue::null()), + JsValue::null(), )); log!( "prepare_fresh_stack {:?} {:?}", @@ -166,7 +166,10 @@ impl WorkLoop { fn perform_unit_of_work(&self, fiber: Rc>) -> Result<(), JsValue> { let next = begin_work(fiber.clone())?; - + let pending_props = { + fiber.clone().borrow().pending_props.clone() + }; + fiber.clone().borrow_mut().memoized_props = pending_props; if next.is_none() { self.complete_unit_of_work(fiber.clone()); } else { diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index b5d8682..34ef729 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use web_sys::js_sys::Reflect; use web_sys::wasm_bindgen::JsValue; @@ -12,12 +10,12 @@ macro_rules! log { } } -pub fn derive_from_js_value(js_value: Rc, str: &str) -> Option> { +pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { match Reflect::get(&js_value, &JsValue::from_str(str)) { - Ok(v) => Some(Rc::new(v)), + Ok(v) => v, Err(_) => { log!("derive {} from {:?} error", str, js_value); - None + JsValue::undefined() } } }