From bc3d004b730cdca4f98c819852012ced9c0994fb Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 9 Jul 2024 16:53:13 +0800 Subject: [PATCH 1/3] useRef --- examples/hello-world/src/App.tsx | 56 +- examples/hello-world/src/ref/index.tsx | 22 + packages/react-dom/src/host_config.rs | 25 +- packages/react-reconciler/src/begin_work.rs | 14 + packages/react-reconciler/src/child_fiber.rs | 8 +- packages/react-reconciler/src/commit_work.rs | 816 ++++++++++-------- .../react-reconciler/src/complete_work.rs | 24 +- packages/react-reconciler/src/fiber.rs | 11 +- packages/react-reconciler/src/fiber_flags.rs | 6 +- packages/react-reconciler/src/fiber_hooks.rs | 24 + packages/react-reconciler/src/lib.rs | 4 +- packages/react-reconciler/src/work_loop.rs | 35 +- packages/react/src/current_dispatcher.rs | 10 +- packages/react/src/lib.rs | 23 +- 14 files changed, 605 insertions(+), 473 deletions(-) create mode 100644 examples/hello-world/src/ref/index.tsx diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index ad1bd41..98a592a 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,55 +1 @@ -import {useState, useEffect} from 'react' -// function App() { -// const [num, updateNum] = useState(0) -// const len = 100 - -// console.log('num', num) -// return ( -// -// ) -// } - -// function Child({i}) { -// return

i am child {i}

-// } - -// export default App - -const Item = ({i, children}) => { - for (let i = 0; i < 999999; i++) {} - return {children} -} - -export default () => { - const [count, updateCount] = useState(0) - - const onClick = () => { - updateCount(2) - } - - useEffect(() => { - const button = document.querySelector('button') - setTimeout(() => updateCount((num) => num + 1), 1000) - setTimeout(() => button.click(), 1100) - }, []) - - return ( -
- -
- {Array.from(new Array(1000)).map((v, index) => ( - {count} - ))} -
-
- ) -} +export {default} from './ref' diff --git a/examples/hello-world/src/ref/index.tsx b/examples/hello-world/src/ref/index.tsx new file mode 100644 index 0000000..c0be166 --- /dev/null +++ b/examples/hello-world/src/ref/index.tsx @@ -0,0 +1,22 @@ +import {useState, useEffect, useRef} from 'react' + +export default function App() { + const [isDel, del] = useState(false) + const divRef = useRef(null) + + console.warn('render divRef', divRef.current) + + useEffect(() => { + console.warn('useEffect divRef', divRef.current) + }, []) + + return ( +
del(true)}> + {isDel ? null : } +
+ ) +} + +function Child() { + return

console.warn('dom is:', dom)}>Child

+} diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index 48bb999..90b02d8 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -2,11 +2,11 @@ use std::any::Any; use std::cell::RefCell; use std::rc::Rc; -use js_sys::{Function, global, Promise}; use js_sys::JSON::stringify; -use wasm_bindgen::JsValue; +use js_sys::{global, Function, Promise}; use wasm_bindgen::prelude::*; -use web_sys::{Node, window}; +use wasm_bindgen::JsValue; +use web_sys::{window, Node}; use react_reconciler::HostConfig; use shared::{log, type_of}; @@ -162,7 +162,14 @@ impl HostConfig for ReactDomHostConfig { .is_function() { let closure_clone = closure.clone(); - queueMicrotask(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::()); + queueMicrotask( + &closure_clone + .borrow_mut() + .as_ref() + .unwrap() + .as_ref() + .unchecked_ref::(), + ); closure_clone.borrow_mut().take().unwrap_throw().forget(); } else if js_sys::Reflect::get(&*global(), &JsValue::from_str("Promise")) .map(|value| value.is_function()) @@ -179,7 +186,15 @@ impl HostConfig for ReactDomHostConfig { c.forget(); } else { let closure_clone = closure.clone(); - setTimeout(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::(), 0); + setTimeout( + &closure_clone + .borrow_mut() + .as_ref() + .unwrap() + .as_ref() + .unchecked_ref::(), + 0, + ); closure_clone.borrow_mut().take().unwrap_throw().forget(); } } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 8bf815d..ec8e1c2 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -4,9 +4,11 @@ use std::rc::Rc; use wasm_bindgen::JsValue; use shared::derive_from_js_value; +use web_sys::js_sys::Object; use crate::child_fiber::{mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; +use crate::fiber_flags::Flags; use crate::fiber_hooks::render_with_hooks; use crate::fiber_lanes::Lane; use crate::update_queue::{process_update_queue, ReturnOfProcessUpdateQueue}; @@ -74,6 +76,15 @@ fn update_host_root( work_in_progress.clone().borrow().child.clone() } +fn mark_ref(current: Option>>, work_in_progress: Rc>) { + let _ref = { work_in_progress.borrow()._ref.clone() }; + if (current.is_none() && !_ref.is_null()) + || (current.is_some() && Object::is(¤t.as_ref().unwrap().borrow()._ref, &_ref)) + { + work_in_progress.borrow_mut().flags |= Flags::Ref; + } +} + fn update_host_component( work_in_progress: Rc>, ) -> Option>> { @@ -84,6 +95,9 @@ fn update_host_component( derive_from_js_value(&ref_fiber_node.pending_props, "children") }; + let alternate = { work_in_progress.borrow().alternate.clone() }; + mark_ref(alternate, work_in_progress.clone()); + { reconcile_children(work_in_progress.clone(), Some(next_children)); } diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index 0c653c2..f2df818 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -172,7 +172,12 @@ fn reconcile_single_text_node( current = current_rc.borrow().sibling.clone(); } - let mut created = FiberNode::new(WorkTag::HostText, props.clone(), JsValue::null()); + let mut created = FiberNode::new( + WorkTag::HostText, + props.clone(), + JsValue::null(), + JsValue::null(), + ); created._return = Some(return_fiber.clone()); Rc::new(RefCell::new(created)) } @@ -235,6 +240,7 @@ fn update_from_map( WorkTag::HostText, props.clone(), JsValue::null(), + JsValue::null(), )))) }; } else if type_of(element, "object") && !element.is_null() { diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index b5a609d..b3739c8 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -3,461 +3,535 @@ use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::js_sys::Function; +use web_sys::js_sys::{Function, Reflect}; -use shared::{derive_from_js_value, log}; +use shared::{derive_from_js_value, log, type_of}; +use web_sys::Node; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; -use crate::fiber_flags::{Flags, get_mutation_mask, get_passive_mask}; +use crate::fiber_flags::{get_mutation_mask, get_passive_mask, Flags}; use crate::fiber_hooks::Effect; -use crate::HostConfig; use crate::work_tags::WorkTag; use crate::work_tags::WorkTag::{HostComponent, HostRoot, HostText}; +use crate::HOST_CONFIG; -pub struct CommitWork { - next_effect: Option>>, - host_config: Rc, +static mut NEXT_EFFECT: Option>> = None; + +enum Phrase { + Mutation, + Layout, } -impl CommitWork { - pub fn new(host_config: Rc) -> Self { - Self { - next_effect: None, - host_config, - } +fn commit_passive_effect( + finished_work: Rc>, + root: Rc>, + _type: &str, +) { + let finished_work_b = finished_work.borrow(); + if finished_work_b.tag != WorkTag::FunctionComponent + || (_type == "update" + && (finished_work_b.flags.clone() & Flags::PassiveEffect == Flags::NoFlags)) + { + return; } - fn commit_passive_effect( - finished_work: Rc>, - root: Rc>, - _type: &str, - ) { - let finished_work_b = finished_work.borrow(); - if finished_work_b.tag != WorkTag::FunctionComponent - || (_type == "update" - && (finished_work_b.flags.clone() & Flags::PassiveEffect == Flags::NoFlags)) - { - return; + let update_queue = &finished_work_b.update_queue; + if update_queue.is_some() { + let update_queue = update_queue.clone().unwrap(); + if update_queue.borrow().last_effect.is_none() { + log!("When FC has PassiveEffect, the effect should exist.") } - - let update_queue = &finished_work_b.update_queue; - if update_queue.is_some() { - let update_queue = update_queue.clone().unwrap(); - if update_queue.borrow().last_effect.is_none() { - log!("When FC has PassiveEffect, the effect should exist.") - } - if _type == "unmount" { - root.borrow() - .pending_passive_effects - .borrow_mut() - .unmount - .push(update_queue.borrow().last_effect.clone().unwrap()); - } else { - root.borrow() - .pending_passive_effects - .borrow_mut() - .update - .push(update_queue.borrow().last_effect.clone().unwrap()); - } + if _type == "unmount" { + root.borrow() + .pending_passive_effects + .borrow_mut() + .unmount + .push(update_queue.borrow().last_effect.clone().unwrap()); + } else { + root.borrow() + .pending_passive_effects + .borrow_mut() + .update + .push(update_queue.borrow().last_effect.clone().unwrap()); } } +} - pub fn commit_hook_effect_list( - flags: Flags, - last_effect: Rc>, - callback: fn(effect: Rc>), - ) { - let mut effect = last_effect.borrow().next.clone(); - loop { - let mut effect_rc = effect.clone().unwrap(); - if effect_rc.borrow().tag.clone() & flags.clone() == flags.clone() { - callback(effect_rc.clone()) - } - effect = effect_rc.borrow().next.clone(); - if Rc::ptr_eq( - &effect.clone().unwrap(), - last_effect.borrow().next.as_ref().unwrap(), - ) { - break; - } +pub fn commit_hook_effect_list( + flags: Flags, + last_effect: Rc>, + callback: fn(effect: Rc>), +) { + let mut effect = last_effect.borrow().next.clone(); + loop { + let mut effect_rc = effect.clone().unwrap(); + if effect_rc.borrow().tag.clone() & flags.clone() == flags.clone() { + callback(effect_rc.clone()) + } + effect = effect_rc.borrow().next.clone(); + if Rc::ptr_eq( + &effect.clone().unwrap(), + last_effect.borrow().next.as_ref().unwrap(), + ) { + break; } } - pub fn commit_hook_effect_list_destroy(flags: Flags, last_effect: Rc>) { - CommitWork::commit_hook_effect_list(flags, last_effect, |effect: Rc>| { - let destroy = { effect.borrow().destroy.clone() }; - if destroy.is_function() { - destroy - .dyn_ref::() - .unwrap() - .call0(&JsValue::null()); +} +pub fn commit_hook_effect_list_destroy(flags: Flags, last_effect: Rc>) { + commit_hook_effect_list(flags, last_effect, |effect: Rc>| { + let destroy = { effect.borrow().destroy.clone() }; + if destroy.is_function() { + destroy + .dyn_ref::() + .unwrap() + .call0(&JsValue::null()); + } + effect.borrow_mut().tag &= !Flags::HookHasEffect; + }); +} + +pub fn commit_hook_effect_list_unmount(flags: Flags, last_effect: Rc>) { + commit_hook_effect_list(flags, last_effect, |effect: Rc>| { + let destroy = &effect.borrow().destroy; + if destroy.is_function() { + destroy + .dyn_ref::() + .unwrap() + .call0(&JsValue::null()); + } + }); +} + +pub fn commit_hook_effect_list_mount(flags: Flags, last_effect: Rc>) { + commit_hook_effect_list(flags, last_effect, |effect: Rc>| { + let create = { effect.borrow().create.clone() }; + if create.is_function() { + let destroy = create.call0(&JsValue::null()).unwrap(); + effect.borrow_mut().destroy = destroy; + } + }); +} + +pub fn commit_effects( + phrase: Phrase, + mask: Flags, + callbak: fn(Rc>, Rc>) -> (), +) -> Box>, Rc>) -> ()> { + Box::new( + move |finished_work: Rc>, root: Rc>| -> () { + unsafe { + NEXT_EFFECT = Some(finished_work); + while NEXT_EFFECT.is_some() { + let next_effect = NEXT_EFFECT.clone().unwrap().clone(); + let child = next_effect.borrow().child.clone(); + if child.is_some() + && next_effect.borrow().subtree_flags.clone() & mask.clone() + != Flags::NoFlags + { + NEXT_EFFECT = child; + } else { + while NEXT_EFFECT.is_some() { + callbak(NEXT_EFFECT.clone().unwrap(), root.clone()); + let sibling = NEXT_EFFECT + .clone() + .clone() + .unwrap() + .borrow() + .sibling + .clone(); + if sibling.is_some() { + NEXT_EFFECT = sibling; + break; + } + + let _return = NEXT_EFFECT + .clone() + .unwrap() + .clone() + .borrow() + ._return + .clone(); + + if _return.is_none() { + NEXT_EFFECT = None + } else { + NEXT_EFFECT = _return; + } + } + } + } } - effect.borrow_mut().tag &= !Flags::HookHasEffect; - }); + }, + ) +} + +pub fn commit_layout_effects( + finished_work: Rc>, + root: Rc>, +) { + commit_effects( + Phrase::Layout, + Flags::LayoutMask, + commit_layout_effects_on_fiber, + )(finished_work, root) +} + +pub fn commit_mutation_effects( + finished_work: Rc>, + root: Rc>, +) { + commit_effects( + Phrase::Mutation, + get_mutation_mask() | get_passive_mask(), + commit_mutation_effects_on_fiber, + )(finished_work, root) +} + +fn commit_layout_effects_on_fiber( + finished_work: Rc>, + root: Rc>, +) { + let flags = finished_work.borrow().flags.clone(); + let tag = finished_work.borrow().tag.clone(); + if flags & Flags::Ref != Flags::NoFlags && tag == HostComponent { + safely_attach_ref(finished_work.clone()); + finished_work.borrow_mut().flags -= Flags::Ref; } +} - pub fn commit_hook_effect_list_unmount(flags: Flags, last_effect: Rc>) { - CommitWork::commit_hook_effect_list(flags, last_effect, |effect: Rc>| { - let destroy = &effect.borrow().destroy; - if destroy.is_function() { - destroy - .dyn_ref::() - .unwrap() - .call0(&JsValue::null()); - } - }); +fn commit_mutation_effects_on_fiber( + finished_work: Rc>, + root: Rc>, +) { + let flags = finished_work.borrow().flags.clone(); + if flags.contains(Flags::Placement) { + commit_placement(finished_work.clone()); + finished_work.borrow_mut().flags -= Flags::Placement; } - pub fn commit_hook_effect_list_mount(flags: Flags, last_effect: Rc>) { - CommitWork::commit_hook_effect_list(flags, last_effect, |effect: Rc>| { - let create = { effect.borrow().create.clone() }; - if create.is_function() { - let destroy = create.call0(&JsValue::null()).unwrap(); - effect.borrow_mut().destroy = destroy; + if flags.contains(Flags::ChildDeletion) { + { + let deletions = &finished_work.borrow().deletions; + if !deletions.is_empty() { + for child_to_delete in deletions { + commit_deletion(child_to_delete.clone(), root.clone()); + } } - }); + } + + finished_work.borrow_mut().flags -= Flags::ChildDeletion; } - pub fn commit_mutation_effects( - &mut self, - finished_work: Rc>, - root: Rc>, - ) { - self.next_effect = Some(finished_work); - while self.next_effect.is_some() { - let next_effect = self.next_effect.clone().unwrap().clone(); - let child = next_effect.borrow().child.clone(); - if child.is_some() - && next_effect.borrow().subtree_flags.clone() - & (get_mutation_mask() | get_passive_mask()) - != Flags::NoFlags - { - self.next_effect = child; - } else { - while self.next_effect.is_some() { - self.commit_mutation_effects_on_fiber( - self.next_effect.clone().unwrap(), - root.clone(), - ); - let sibling = self - .next_effect - .clone() - .clone() - .unwrap() - .borrow() - .sibling - .clone(); - if sibling.is_some() { - self.next_effect = sibling; - break; - } + if flags.contains(Flags::Update) { + commit_update(finished_work.clone()); + finished_work.borrow_mut().flags -= Flags::Update; + } - let _return = self - .next_effect - .clone() - .unwrap() - .clone() - .borrow() - ._return - .clone(); + if flags.clone() & Flags::PassiveEffect != Flags::NoFlags { + commit_passive_effect(finished_work.clone(), root, "update"); + finished_work.borrow_mut().flags -= Flags::PassiveEffect; + } - if _return.is_none() { - self.next_effect = None - } else { - self.next_effect = _return; - } - } - } - } + if flags & Flags::Ref != Flags::NoFlags && finished_work.borrow().tag.clone() == HostComponent { + safely_detach_ref(finished_work); } +} - fn commit_mutation_effects_on_fiber( - &self, - finished_work: Rc>, - root: Rc>, - ) { - let flags = finished_work.borrow().flags.clone(); - if flags.contains(Flags::Placement) { - self.commit_placement(finished_work.clone()); - finished_work.borrow_mut().flags -= Flags::Placement; +fn safely_detach_ref(current: Rc>) { + let _ref = current.borrow()._ref.clone(); + if !_ref.is_null() { + if type_of(&_ref, "function") { + _ref.dyn_ref::() + .unwrap() + .call1(&JsValue::null(), &JsValue::null()); + } else { + Reflect::set(&_ref, &"current".into(), &JsValue::null()); } + } +} - if flags.contains(Flags::ChildDeletion) { - { - let deletions = &finished_work.borrow().deletions; - if !deletions.is_empty() { - for child_to_delete in deletions { - self.commit_deletion(child_to_delete.clone(), root.clone()); - } +fn safely_attach_ref(fiber: Rc>) { + let _ref = fiber.borrow()._ref.clone(); + if !_ref.is_null() { + let instance = match fiber.borrow().state_node.clone() { + Some(s) => match &*s { + StateNode::Element(element) => { + let node = (*element).downcast_ref::().unwrap(); + Some(node.clone()) } - } - - finished_work.borrow_mut().flags -= Flags::ChildDeletion; - } + StateNode::FiberRootNode(_) => None, + }, + None => None, + }; - if flags.contains(Flags::Update) { - self.commit_update(finished_work.clone()); - finished_work.borrow_mut().flags -= Flags::Update; + if instance.is_none() { + panic!("instance is none") } - if flags & Flags::PassiveEffect != Flags::NoFlags { - CommitWork::commit_passive_effect(finished_work.clone(), root, "update"); - finished_work.borrow_mut().flags -= Flags::PassiveEffect; + let instance = instance.as_ref().unwrap(); + if type_of(&_ref, "function") { + _ref.dyn_ref::() + .unwrap() + .call1(&JsValue::null(), instance); + } else { + Reflect::set(&_ref, &"current".into(), instance); } } +} - 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 +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()); + 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"), - }; - } + } + _ => log!("commit_update, unsupported type"), + }; +} - fn commit_deletion( - &self, - child_to_delete: Rc>, - root: 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 => { - CommitWork::commit_passive_effect( - unmount_fiber.clone(), - root.clone(), - "unmount", - ); - } - WorkTag::HostRoot => {} - WorkTag::HostComponent => { - if cloned.borrow().is_none() { - *cloned.borrow_mut() = Some(unmount_fiber.clone()); - } +fn commit_deletion(child_to_delete: Rc>, root: Rc>) { + let first_host_fiber: Rc>>>> = Rc::new(RefCell::new(None)); + commit_nested_unmounts(child_to_delete.clone(), |unmount_fiber| { + let cloned = first_host_fiber.clone(); + match unmount_fiber.borrow().tag { + WorkTag::FunctionComponent => { + commit_passive_effect(unmount_fiber.clone(), root.clone(), "unmount"); + } + 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()); - } + } + 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( + } + }; + }); + + 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(), ) } - - 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()); + child_to_delete.clone().borrow_mut()._return = None; + child_to_delete.clone().borrow_mut().child = None; +} - 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(); - } +fn commit_nested_unmounts(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(); - let _return = { node_cloned.borrow()._return.clone() }; + let node_cloned = node.clone(); + if node_cloned.borrow().child.is_some() { node_cloned .borrow_mut() - .sibling + .child .clone() .unwrap() .clone() .borrow_mut() - ._return = _return; - node = node_cloned.borrow().sibling.clone().unwrap(); + ._return = Some(node.clone()); + node = node_cloned.borrow().child.clone().unwrap(); + continue; } - } - - fn commit_placement(&self, finished_work: Rc>) { - let host_parent = self.get_host_parent(finished_work.clone()); - if host_parent.is_none() { + if Rc::ptr_eq(&node, &root.clone()) { return; } - let parent_state_node = FiberNode::derive_state_node(host_parent.unwrap()); - let sibling = self.get_host_sibling(finished_work.clone()); - - if parent_state_node.is_some() { - self.insert_or_append_placement_node_into_container( - finished_work.clone(), - parent_state_node.unwrap(), - sibling, - ); + 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(); } + + let node_cloned = node.clone(); + let _return = { node_cloned.borrow()._return.clone() }; + node_cloned + .borrow_mut() + .sibling + .clone() + .unwrap() + .clone() + .borrow_mut() + ._return = _return; + node = node_cloned.borrow().sibling.clone().unwrap(); } +} - fn get_element_from_state_node(&self, state_node: Rc) -> Rc { - match &*state_node { - StateNode::FiberRootNode(root) => root.clone().borrow().container.clone(), - StateNode::Element(ele) => ele.clone(), - } +fn commit_placement(finished_work: Rc>) { + let host_parent = get_host_parent(finished_work.clone()); + if host_parent.is_none() { + return; + } + let parent_state_node = FiberNode::derive_state_node(host_parent.unwrap()); + let sibling = get_host_sibling(finished_work.clone()); + + if parent_state_node.is_some() { + insert_or_append_placement_node_into_container( + finished_work.clone(), + parent_state_node.unwrap(), + sibling, + ); + } +} + +fn get_element_from_state_node(state_node: Rc) -> Rc { + match &*state_node { + StateNode::FiberRootNode(root) => root.clone().borrow().container.clone(), + StateNode::Element(ele) => ele.clone(), } +} - fn insert_or_append_placement_node_into_container( - &self, - fiber: Rc>, - parent: Rc, - before: Option>, - ) { - let fiber = fiber.clone(); - let tag = fiber.borrow().tag.clone(); - if tag == WorkTag::HostComponent || tag == WorkTag::HostText { - let state_node = fiber.clone().borrow().state_node.clone().unwrap(); - let state_node = self.get_element_from_state_node(state_node); - - if before.is_some() { - self.host_config.insert_child_to_container( +fn insert_or_append_placement_node_into_container( + fiber: Rc>, + parent: Rc, + before: Option>, +) { + let fiber = fiber.clone(); + let tag = fiber.borrow().tag.clone(); + if tag == WorkTag::HostComponent || tag == WorkTag::HostText { + let state_node = fiber.clone().borrow().state_node.clone().unwrap(); + let state_node = get_element_from_state_node(state_node); + + if before.is_some() { + unsafe { + HOST_CONFIG.as_ref().unwrap().insert_child_to_container( state_node, parent, before.clone().unwrap(), - ); - } else { - self.host_config - .append_child_to_container(state_node, parent.clone()); - } - - return; + ) + }; + } else { + unsafe { + HOST_CONFIG + .as_ref() + .unwrap() + .append_child_to_container(state_node, parent.clone()) + }; } - let child = fiber.borrow().child.clone(); - if child.is_some() { - self.insert_or_append_placement_node_into_container( - child.clone().unwrap(), + return; + } + + let child = fiber.borrow().child.clone(); + if child.is_some() { + insert_or_append_placement_node_into_container( + child.clone().unwrap(), + parent.clone(), + before.clone(), + ); + let mut sibling = child.unwrap().clone().borrow().sibling.clone(); + while sibling.is_some() { + insert_or_append_placement_node_into_container( + sibling.clone().unwrap(), parent.clone(), before.clone(), ); - let mut sibling = child.unwrap().clone().borrow().sibling.clone(); - while sibling.is_some() { - self.insert_or_append_placement_node_into_container( - sibling.clone().unwrap(), - parent.clone(), - before.clone(), - ); - sibling = sibling.clone().unwrap().clone().borrow().sibling.clone(); - } + sibling = sibling.clone().unwrap().clone().borrow().sibling.clone(); } } +} - fn get_host_parent(&self, fiber: Rc>) -> Option>> { - let mut parent = fiber.clone().borrow()._return.clone(); - while parent.is_some() { - let p = parent.clone().unwrap(); - let parent_tag = p.borrow().tag.clone(); - if parent_tag == WorkTag::HostComponent || parent_tag == WorkTag::HostRoot { - return Some(p); - } - parent = p.borrow()._return.clone(); +fn get_host_parent(fiber: Rc>) -> Option>> { + let mut parent = fiber.clone().borrow()._return.clone(); + while parent.is_some() { + let p = parent.clone().unwrap(); + let parent_tag = p.borrow().tag.clone(); + if parent_tag == WorkTag::HostComponent || parent_tag == WorkTag::HostRoot { + return Some(p); } - - None + parent = p.borrow()._return.clone(); } - /** - * 难点在于目标fiber的hostSibling可能并不是他的同级sibling - * 比如: 其中:function B() {return
} 所以A的hostSibling实际是B的child - * 实际情况层级可能更深 - * 同时:一个fiber被标记Placement,那他就是不稳定的(他对应的DOM在本次commit阶段会移动),也不能作为hostSibling - */ - fn get_host_sibling(&self, fiber: Rc>) -> Option> { - let mut node = Some(fiber); - 'find_sibling: loop { - let node_rc = node.clone().unwrap(); - while node_rc.borrow().sibling.is_none() { - let parent = node_rc.borrow()._return.clone(); - let tag = parent.clone().unwrap().borrow().tag.clone(); - if parent.is_none() || tag == HostComponent || tag == HostRoot { - return None; - } - node = parent.clone(); + None +} + +/** + * 难点在于目标fiber的hostSibling可能并不是他的同级sibling + * 比如: 其中:function B() {return
} 所以A的hostSibling实际是B的child + * 实际情况层级可能更深 + * 同时:一个fiber被标记Placement,那他就是不稳定的(他对应的DOM在本次commit阶段会移动),也不能作为hostSibling + */ +fn get_host_sibling(fiber: Rc>) -> Option> { + let mut node = Some(fiber); + 'find_sibling: loop { + let node_rc = node.clone().unwrap(); + while node_rc.borrow().sibling.is_none() { + let parent = node_rc.borrow()._return.clone(); + let tag = parent.clone().unwrap().borrow().tag.clone(); + if parent.is_none() || tag == HostComponent || tag == HostRoot { + return None; } + node = parent.clone(); + } - let node_rc = node.clone().unwrap(); - let _return = { node_rc.borrow()._return.clone() }; - node_rc - .borrow_mut() - .sibling - .clone() - .unwrap() - .borrow_mut() - ._return = _return; - node = node_rc.borrow().sibling.clone(); - - let node_rc = node.clone().unwrap(); - let tag = node_rc.borrow().tag.clone(); - while tag != HostText && tag != HostComponent { - if node_rc.borrow().flags.contains(Flags::Placement) { - continue 'find_sibling; - } - if node_rc.borrow().child.is_none() { - continue 'find_sibling; - } else { - node_rc - .borrow_mut() - .child - .clone() - .unwrap() - .borrow_mut() - ._return = node.clone(); - node = node_rc.borrow().child.clone(); - } + let node_rc = node.clone().unwrap(); + let _return = { node_rc.borrow()._return.clone() }; + node_rc + .borrow_mut() + .sibling + .clone() + .unwrap() + .borrow_mut() + ._return = _return; + node = node_rc.borrow().sibling.clone(); + + let node_rc = node.clone().unwrap(); + let tag = node_rc.borrow().tag.clone(); + while tag != HostText && tag != HostComponent { + if node_rc.borrow().flags.contains(Flags::Placement) { + continue 'find_sibling; } - if !node - .clone() - .unwrap() - .borrow() - .flags - .contains(Flags::Placement) - { - return Some(self.get_element_from_state_node( - node.clone().unwrap().borrow().state_node.clone().unwrap(), - )); + if node_rc.borrow().child.is_none() { + continue 'find_sibling; + } else { + node_rc + .borrow_mut() + .child + .clone() + .unwrap() + .borrow_mut() + ._return = node.clone(); + node = node_rc.borrow().child.clone(); } } + if !node + .clone() + .unwrap() + .borrow() + .flags + .contains(Flags::Placement) + { + return Some(get_element_from_state_node( + node.clone().unwrap().borrow().state_node.clone().unwrap(), + )); + } } } diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 455a3bb..fdfb179 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -9,13 +9,17 @@ use shared::{derive_from_js_value, log}; use crate::fiber::{FiberNode, StateNode}; use crate::fiber_flags::Flags; -use crate::HostConfig; use crate::work_tags::WorkTag; +use crate::HostConfig; pub struct CompleteWork { pub host_config: Rc, } +fn mark_ref(fiber: Rc>) { + fiber.borrow_mut().flags |= Flags::Ref; +} + impl CompleteWork { pub(crate) fn new(host_config: Rc) -> Self { Self { host_config } @@ -65,9 +69,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; } @@ -135,7 +139,14 @@ impl CompleteWork { } WorkTag::HostComponent => { if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { - log!("update properties") + log!("update properties"); + let current = current.unwrap(); + if !Object::is( + ¤t.borrow()._ref, + &work_in_progress_cloned.borrow()._ref, + ) { + mark_ref(work_in_progress.clone()); + } } else { let instance = self.host_config.create_instance( work_in_progress @@ -150,6 +161,9 @@ impl CompleteWork { 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 !work_in_progress.borrow()._ref.is_null() { + mark_ref(work_in_progress.clone()); + } } self.bubble_properties(work_in_progress.clone()); diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 9c801ec..f02cae9 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -58,6 +58,7 @@ pub struct FiberNode { pub child: Option>>, pub alternate: Option>>, pub _type: JsValue, + pub _ref: JsValue, pub flags: Flags, pub subtree_flags: Flags, pub memoized_props: JsValue, @@ -111,7 +112,7 @@ impl Debug for FiberNode { } impl FiberNode { - pub fn new(tag: WorkTag, pending_props: JsValue, key: JsValue) -> Self { + pub fn new(tag: WorkTag, pending_props: JsValue, key: JsValue, _ref: JsValue) -> Self { Self { index: 0, tag, @@ -130,6 +131,7 @@ impl FiberNode { subtree_flags: Flags::NoFlags, deletions: vec![], lanes: Lane::NoLane, + _ref, } } @@ -137,6 +139,7 @@ impl FiberNode { 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 _ref = derive_from_js_value(ele, "ref"); let mut fiber_tag = WorkTag::FunctionComponent; if _type.is_string() { @@ -145,7 +148,7 @@ impl FiberNode { log!("Unsupported type {:?}", ele); } - let mut fiber = FiberNode::new(fiber_tag, props, key); + let mut fiber = FiberNode::new(fiber_tag, props, key, _ref); fiber._type = _type; fiber } @@ -175,7 +178,8 @@ impl FiberNode { return if w.is_none() { let wip = { let c = c_rc.borrow(); - let mut wip = FiberNode::new(c.tag.clone(), pending_props, c.key.clone()); + let mut wip = + FiberNode::new(c.tag.clone(), pending_props, c.key.clone(), c._ref.clone()); wip._type = c._type.clone(); wip.state_node = c.state_node.clone(); @@ -210,6 +214,7 @@ impl FiberNode { wip.child = c.child.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); + wip._ref = c._ref.clone(); } w.clone() }; diff --git a/packages/react-reconciler/src/fiber_flags.rs b/packages/react-reconciler/src/fiber_flags.rs index ac3d87b..07cc8e6 100644 --- a/packages/react-reconciler/src/fiber_flags.rs +++ b/packages/react-reconciler/src/fiber_flags.rs @@ -8,6 +8,8 @@ bitflags! { const Update = 0b00000100; const ChildDeletion = 0b00010000; const PassiveEffect = 0b00100000; + const Ref = 0b01000000; + const LayoutMask = 0b01000000; // Ref // effect hook const HookHasEffect = 0b00100001; const Passive = 0b00000010; @@ -24,4 +26,6 @@ pub fn get_mutation_mask() -> Flags { Flags::Placement | Flags::Update | Flags::ChildDeletion } -pub fn get_passive_mask() -> Flags { Flags::PassiveEffect | Flags::ChildDeletion } +pub fn get_passive_mask() -> Flags { + Flags::PassiveEffect | Flags::ChildDeletion +} diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index b916b51..cfc1dc6 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -108,8 +108,15 @@ fn update_hooks_to_dispatcher(is_update: bool) { .clone(); use_effect_closure.forget(); + // use_ref + let use_ref_closure = Closure::wrap(Box::new(if is_update { update_ref } else { mount_ref }) + as Box JsValue>); + let use_ref = use_ref_closure.as_ref().unchecked_ref::().clone(); + use_ref_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"); + Reflect::set(&object, &"use_ref".into(), &use_ref).expect("TODO: panic set use_ref"); updateDispatcher(&object.into()); } @@ -529,3 +536,20 @@ fn are_hook_inputs_equal(next_deps: &JsValue, pre_deps: &JsValue) -> bool { } return true; } + +fn mount_ref(initial_value: &JsValue) -> JsValue { + let hook = mount_work_in_progress_hook(); + let ref_obj: Object = Object::new(); + Reflect::set(&ref_obj, &"current".into(), initial_value); + hook.as_ref().unwrap().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(ref_obj.clone().into())); + ref_obj.into() +} + +fn update_ref(initial_value: &JsValue) -> JsValue { + let hook = update_work_in_progress_hook(); + match hook.unwrap().borrow_mut().memoized_state.clone() { + Some(MemoizedState::MemoizedJsValue(value)) => value, + _ => panic!("ref is none"), + } +} diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index ee5e21b..454510a 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -4,7 +4,6 @@ use std::rc::Rc; use wasm_bindgen::JsValue; -use crate::commit_work::CommitWork; use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; // use crate::fiber_hooks::{WORK_LOOP as Fiber_HOOKS}; @@ -29,7 +28,6 @@ mod work_tags; pub static mut HOST_CONFIG: Option> = None; static mut COMPLETE_WORK: Option = None; -static mut COMMIT_WORK: Option = None; pub trait HostConfig { fn create_text_instance(&self, content: &JsValue) -> Rc; @@ -60,6 +58,7 @@ impl Reconciler { WorkTag::HostRoot, JsValue::null(), JsValue::null(), + JsValue::null(), ))); host_root_fiber.clone().borrow_mut().update_queue = Some(create_update_queue()); let root = Rc::new(RefCell::new(FiberRootNode::new( @@ -82,7 +81,6 @@ impl Reconciler { unsafe { HOST_CONFIG = Some(self.host_config.clone()); COMPLETE_WORK = Some(CompleteWork::new(self.host_config.clone())); - COMMIT_WORK = Some(CommitWork::new(self.host_config.clone())); schedule_update_on_fiber(host_root_fiber, root_render_priority); } element.clone() diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 16524b9..40bcab6 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -13,13 +13,16 @@ use scheduler::{ use shared::{is_dev, log}; use crate::begin_work::begin_work; -use crate::commit_work::CommitWork; +use crate::commit_work::{ + commit_hook_effect_list_destroy, commit_hook_effect_list_mount, + commit_hook_effect_list_unmount, commit_layout_effects, commit_mutation_effects, +}; use crate::fiber::{FiberNode, FiberRootNode, PendingPassiveEffects, StateNode}; use crate::fiber_flags::{get_mutation_mask, get_passive_mask, Flags}; use crate::fiber_lanes::{get_highest_priority, lanes_to_scheduler_priority, merge_lanes, Lane}; use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; use crate::work_tags::WorkTag; -use crate::{COMMIT_WORK, COMPLETE_WORK, HOST_CONFIG}; +use crate::{COMPLETE_WORK, HOST_CONFIG}; bitflags! { #[derive(Debug, Clone)] @@ -325,23 +328,17 @@ fn flush_passive_effects(pending_passive_effects: Rc>) { EXECUTION_CONTEXT |= ExecutionContext::CommitContext; } - unsafe { - COMMIT_WORK - .as_mut() - .unwrap() - .commit_mutation_effects(finished_work.clone(), root.clone()); - } + // effect + // 1/3: Before Mutation + + // 2/3: Mutation + commit_mutation_effects(finished_work.clone(), root.clone()); + + // Switch Fiber Tree cloned.borrow_mut().current = finished_work.clone(); + // 3/3: Layout + commit_layout_effects(finished_work.clone(), root.clone()); + unsafe { EXECUTION_CONTEXT = prev_execution_context; } diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index d7a79fd..9609e62 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -1,21 +1,22 @@ use js_sys::{Function, Reflect}; -use wasm_bindgen::JsValue; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; #[derive(Debug)] pub struct Dispatcher { pub use_state: Function, pub use_effect: Function, - // pub use_callback: *const dyn Fn(), + pub use_ref: Function, // pub use_callback: *const dyn Fn(), } unsafe impl Send for Dispatcher {} impl Dispatcher { - pub fn new(use_state: Function, use_effect: Function) -> Self { + pub fn new(use_state: Function, use_effect: Function, use_ref: Function) -> Self { Dispatcher { use_state, use_effect, + use_ref, } } } @@ -37,5 +38,6 @@ fn derive_function_from_js_value(js_value: &JsValue, name: &str) -> Function { pub unsafe fn update_dispatcher(args: &JsValue) { let use_state = derive_function_from_js_value(args, "use_state"); let use_effect = derive_function_from_js_value(args, "use_effect"); - CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new(use_state, use_effect))) + let use_ref = derive_function_from_js_value(args, "use_ref"); + CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new(use_state, use_effect, use_ref))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 6d0f80a..66f100b 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -1,4 +1,4 @@ -use js_sys::{Array, JSON, Object, Reflect}; +use js_sys::{Array, Object, Reflect, JSON}; use wasm_bindgen::prelude::*; use shared::{derive_from_js_value, REACT_ELEMENT_TYPE}; @@ -35,7 +35,7 @@ pub fn jsx_dev(_type: &JsValue, config: &JsValue, key: &JsValue) -> JsValue { &"$$typeof".into(), &JsValue::from_str(REACT_ELEMENT_TYPE), ) - .expect("$$typeof panic"); + .expect("$$typeof panic"); Reflect::set(&react_element, &"type".into(), _type).expect("type panic"); let props = Object::new(); @@ -90,7 +90,8 @@ pub fn jsx(_type: &JsValue, config: &JsValue, maybe_children: &JsValue) -> JsVal Reflect::set(&config, &"children".into(), &children.get(0)) .expect("TODO: panic children"); } else { - Reflect::set(&config, &"children".into(), maybe_children).expect("TODO: panic set children"); + Reflect::set(&config, &"children".into(), maybe_children) + .expect("TODO: panic set children"); } } } @@ -103,11 +104,11 @@ pub fn is_valid_element(object: &JsValue) -> bool { object.is_object() && !object.is_null() && Reflect::get(&object, &"$$typeof".into()) - .unwrap_or("".into()) - .as_string() - .unwrap_or("".into()) - .as_str() - == REACT_ELEMENT_TYPE + .unwrap_or("".into()) + .as_string() + .unwrap_or("".into()) + .as_str() + == REACT_ELEMENT_TYPE } #[wasm_bindgen(js_name = useState)] @@ -121,3 +122,9 @@ pub unsafe fn use_effect(create: &JsValue, deps: &JsValue) { let use_effect = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_effect; use_effect.call2(&JsValue::null(), create, deps); } + +#[wasm_bindgen(js_name = useRef)] +pub unsafe fn use_ref(initial_value: &JsValue) -> Result { + let use_ref = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_ref; + use_ref.call1(&JsValue::null(), initial_value) +} From 0bcf6c677b51cb56236ff12b42eca8d83744eed4 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 10 Jul 2024 10:36:17 +0800 Subject: [PATCH 2/3] useCallback useMemo --- examples/hello-world/src/App.tsx | 2 +- examples/hello-world/src/ref/index.tsx | 2 +- .../hello-world/src/useCallback/index.tsx | 29 ++++ examples/hello-world/src/useMemo/index.tsx | 27 ++++ packages/react-reconciler/src/fiber_hooks.rs | 125 ++++++++++++++++++ packages/react/src/current_dispatcher.rs | 26 +++- packages/react/src/lib.rs | 12 ++ packages/scheduler/src/lib.rs | 1 - 8 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 examples/hello-world/src/useCallback/index.tsx create mode 100644 examples/hello-world/src/useMemo/index.tsx diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 98a592a..918688f 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './ref' +export {default} from './useCallback' diff --git a/examples/hello-world/src/ref/index.tsx b/examples/hello-world/src/ref/index.tsx index c0be166..590e2b0 100644 --- a/examples/hello-world/src/ref/index.tsx +++ b/examples/hello-world/src/ref/index.tsx @@ -11,7 +11,7 @@ export default function App() { }, []) return ( -
del(true)}> +
del((prev) => !prev)}> {isDel ? null : }
) diff --git a/examples/hello-world/src/useCallback/index.tsx b/examples/hello-world/src/useCallback/index.tsx new file mode 100644 index 0000000..941547e --- /dev/null +++ b/examples/hello-world/src/useCallback/index.tsx @@ -0,0 +1,29 @@ +import {useState, useCallback} from 'react' + +let lastFn + +export default function App() { + const [num, update] = useState(1) + console.log('App render ', num) + + const addOne = useCallback(() => update((n) => n + 1), []) + // const addOne = () => update((n) => n + 1) + + if (lastFn === addOne) { + console.log('useCallback work') + } + + lastFn = addOne + + return ( +
+ + {num} +
+ ) +} + +const Cpn = function ({onClick}) { + console.log('Cpn render') + return
onClick()}>lll
+} diff --git a/examples/hello-world/src/useMemo/index.tsx b/examples/hello-world/src/useMemo/index.tsx new file mode 100644 index 0000000..8830014 --- /dev/null +++ b/examples/hello-world/src/useMemo/index.tsx @@ -0,0 +1,27 @@ +import {useState, useMemo} from 'react' + +let lastCpn +// 方式1:App提取 bailout四要素 +// 方式2:ExpensiveSubtree用memo包裹 +export default function App() { + const [num, update] = useState(0) + console.log('App render ', num) + + const Cpn = useMemo(() => , []) + + if (lastCpn === Cpn) { + console.log('useMemo work') + } + lastCpn = Cpn + return ( +
update(num + 100)}> +

num is: {num}

+ {Cpn} +
+ ) +} + +function ExpensiveSubtree() { + console.log('ExpensiveSubtree render') + return

i am child

+} diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index cfc1dc6..3e041ec 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -114,9 +114,34 @@ fn update_hooks_to_dispatcher(is_update: bool) { let use_ref = use_ref_closure.as_ref().unchecked_ref::().clone(); use_ref_closure.forget(); + // use_memo + let use_memo_closure = + Closure::wrap(Box::new(if is_update { update_memo } else { mount_memo }) + as Box Result>); + let use_memo = use_memo_closure + .as_ref() + .unchecked_ref::() + .clone(); + use_memo_closure.forget(); + + // use_callback + let use_callback_clusure = Closure::wrap(Box::new(if is_update { + update_callback + } else { + mount_callback + }) as Box JsValue>); + let use_callback = use_callback_clusure + .as_ref() + .unchecked_ref::() + .clone(); + use_callback_clusure.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"); Reflect::set(&object, &"use_ref".into(), &use_ref).expect("TODO: panic set use_ref"); + Reflect::set(&object, &"use_memo".into(), &use_memo).expect("TODO: panic set use_memo"); + Reflect::set(&object, &"use_callback".into(), &use_callback) + .expect("TODO: panic set use_callback"); updateDispatcher(&object.into()); } @@ -290,6 +315,8 @@ fn mount_state(initial_state: &JsValue) -> Result, JsValue> { } hook.as_ref().unwrap().clone().borrow_mut().memoized_state = Some(MemoizedState::MemoizedJsValue(memoized_state.clone())); + hook.as_ref().unwrap().clone().borrow_mut().base_state = + Some(MemoizedState::MemoizedJsValue(memoized_state.clone())); unsafe { if CURRENTLY_RENDERING_FIBER.is_none() { @@ -553,3 +580,101 @@ fn update_ref(initial_value: &JsValue) -> JsValue { _ => panic!("ref is none"), } } + +fn mount_memo(create: Function, deps: JsValue) -> Result { + let hook = mount_work_in_progress_hook(); + let next_deps = if deps.is_undefined() { + JsValue::null() + } else { + deps + }; + let next_value = create.call0(&JsValue::null())?; + let array = Array::new(); + array.push(&next_value); + array.push(&next_deps); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(array.into())); + Ok(next_value) +} + +fn update_memo(create: Function, deps: JsValue) -> Result { + let hook = update_work_in_progress_hook(); + let next_deps = if deps.is_undefined() { + JsValue::null() + } else { + deps + }; + + if let MemoizedState::MemoizedJsValue(prev_state) = hook + .clone() + .unwrap() + .borrow() + .memoized_state + .as_ref() + .unwrap() + { + if !next_deps.is_null() { + let arr = prev_state.dyn_ref::().unwrap(); + let prev_deps = arr.get(1); + if are_hook_inputs_equal(&next_deps, &prev_deps) { + return Ok(arr.get(0)); + } + } + let next_value = create.call0(&JsValue::null())?; + let array = Array::new(); + array.push(&next_value); + array.push(&next_deps); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(array.into())); + return Ok(next_value); + } + panic!("update_memo, memoized_state is not JsValue"); +} + +fn mount_callback(callback: Function, deps: JsValue) -> JsValue { + let hook = mount_work_in_progress_hook(); + let next_deps = if deps.is_undefined() { + JsValue::null() + } else { + deps + }; + let array = Array::new(); + array.push(&callback); + array.push(&next_deps); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(array.into())); + callback.into() +} + +fn update_callback(callback: Function, deps: JsValue) -> JsValue { + let hook = update_work_in_progress_hook(); + let next_deps = if deps.is_undefined() { + JsValue::null() + } else { + deps + }; + + if let MemoizedState::MemoizedJsValue(prev_state) = hook + .clone() + .unwrap() + .borrow() + .memoized_state + .as_ref() + .unwrap() + { + if !next_deps.is_null() { + let arr = prev_state.dyn_ref::().unwrap(); + let prev_deps = arr.get(1); + if are_hook_inputs_equal(&next_deps, &prev_deps) { + return arr.get(0); + } + } + let array = Array::new(); + array.push(&callback); + array.push(&next_deps); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(array.into())); + return callback.into(); + } + panic!("update_callback, memoized_state is not JsValue"); +} diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index 9609e62..9b63a0c 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -2,21 +2,33 @@ use js_sys::{Function, Reflect}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +use crate::use_callback; + #[derive(Debug)] pub struct Dispatcher { pub use_state: Function, pub use_effect: Function, - pub use_ref: Function, // pub use_callback: *const dyn Fn(), + pub use_ref: Function, + pub use_memo: Function, + pub use_callback: Function, } unsafe impl Send for Dispatcher {} impl Dispatcher { - pub fn new(use_state: Function, use_effect: Function, use_ref: Function) -> Self { + pub fn new( + use_state: Function, + use_effect: Function, + use_ref: Function, + use_memo: Function, + use_callback: Function, + ) -> Self { Dispatcher { use_state, use_effect, use_ref, + use_memo, + use_callback, } } } @@ -39,5 +51,13 @@ pub unsafe fn update_dispatcher(args: &JsValue) { let use_state = derive_function_from_js_value(args, "use_state"); let use_effect = derive_function_from_js_value(args, "use_effect"); let use_ref = derive_function_from_js_value(args, "use_ref"); - CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new(use_state, use_effect, use_ref))) + let use_memo = derive_function_from_js_value(args, "use_memo"); + let use_callback = derive_function_from_js_value(args, "use_callback"); + CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( + use_state, + use_effect, + use_ref, + use_memo, + use_callback, + ))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 66f100b..7b02db8 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -128,3 +128,15 @@ pub unsafe fn use_ref(initial_value: &JsValue) -> Result { let use_ref = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_ref; use_ref.call1(&JsValue::null(), initial_value) } + +#[wasm_bindgen(js_name = useMemo)] +pub unsafe fn use_memo(create: &JsValue, deps: &JsValue) -> Result { + let use_memo = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_memo; + use_memo.call2(&JsValue::null(), create, deps) +} + +#[wasm_bindgen(js_name = useCallback)] +pub unsafe fn use_callback(callback: &JsValue, deps: &JsValue) -> Result { + let use_callback = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_callback; + use_callback.call2(&JsValue::null(), callback, deps) +} diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index bbd5020..fbc551d 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -288,7 +288,6 @@ fn flush_work(has_time_remaining: bool, initial_time: f64) -> bool { pub fn unstable_should_yield_to_host() -> bool { unsafe { let time_elapsed = unstable_now() - START_TIME; - log!("start_time: {:?}, now: {:?}", START_TIME, unstable_now()); if time_elapsed < FRAME_YIELD_MS { return false; } From e76c14dce266a8cb05b56c0d1612863bf318227f Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 10 Jul 2024 14:16:28 +0800 Subject: [PATCH 3/3] fix mark_ref bugs --- packages/react-reconciler/src/begin_work.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index ec8e1c2..d77842b 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -79,7 +79,7 @@ fn update_host_root( fn mark_ref(current: Option>>, work_in_progress: Rc>) { let _ref = { work_in_progress.borrow()._ref.clone() }; if (current.is_none() && !_ref.is_null()) - || (current.is_some() && Object::is(¤t.as_ref().unwrap().borrow()._ref, &_ref)) + || (current.is_some() && !Object::is(¤t.as_ref().unwrap().borrow()._ref, &_ref)) { work_in_progress.borrow_mut().flags |= Flags::Ref; }