From d508c31a6f3c3259ebf8e3ea44dede6a6d1d8bfd Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 26 Jul 2024 11:49:08 +0800 Subject: [PATCH 1/3] add article 19 --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ec81a58..09ae787 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # How to start? - install wasm-pack -- `npm run build` +- `npm run build:dev` - cd `examples/hello-world` - `pnpm install` - `npm run dev` @@ -43,3 +43,5 @@ [从零实现 React v18,但 WASM 版 - [17] 实现 Concurrent 模式](https://www.paradeto.com/2024/06/19/big-react-wasm-17/) [从零实现 React v18,但 WASM 版 - [18] 实现 useRef, useCallback, useMemo](https://www.paradeto.com/2024/07/10/big-react-wasm-18/) + +[从零实现 React v18,但 WASM 版 - [19] 性能优化之 bailout 和 eagerState](https://www.paradeto.com/2024/07/19/big-react-wasm-19/) From de833baa54309390197de853f430ea2c177b7541 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 31 Jul 2024 14:55:42 +0800 Subject: [PATCH 2/3] add blog 20 21 --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 09ae787..b56a6a5 100644 --- a/readme.md +++ b/readme.md @@ -45,3 +45,7 @@ [从零实现 React v18,但 WASM 版 - [18] 实现 useRef, useCallback, useMemo](https://www.paradeto.com/2024/07/10/big-react-wasm-18/) [从零实现 React v18,但 WASM 版 - [19] 性能优化之 bailout 和 eagerState](https://www.paradeto.com/2024/07/19/big-react-wasm-19/) + +[从零实现 React v18,但 WASM 版 - [20] 实现 Context](https://www.paradeto.com/2024/07/26/big-react-wasm-20/) + +[从零实现 React v18,但 WASM 版 - [21] 性能优化支持 Context](https://www.paradeto.com/2024/07/26/big-react-wasm-21/) From 991ace7e28a67e968498fb87a061d81fe3386453 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 31 Jul 2024 17:08:12 +0800 Subject: [PATCH 3/3] support memo --- examples/hello-world/src/App.tsx | 2 +- examples/hello-world/src/memo/index.tsx | 27 +++++++++++ packages/react-reconciler/src/begin_work.rs | 48 +++++++++++++++++-- packages/react-reconciler/src/commit_work.rs | 10 +--- .../react-reconciler/src/complete_work.rs | 16 ++----- packages/react-reconciler/src/fiber.rs | 28 ++++++++--- packages/react-reconciler/src/work_tags.rs | 1 + packages/react/src/lib.rs | 29 ++++++++++- packages/shared/src/lib.rs | 37 +++++++++++++- 9 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 examples/hello-world/src/memo/index.tsx diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 52a923b..ac455c1 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './useContext/index2' +export {default} from './memo' diff --git a/examples/hello-world/src/memo/index.tsx b/examples/hello-world/src/memo/index.tsx new file mode 100644 index 0000000..e7c6494 --- /dev/null +++ b/examples/hello-world/src/memo/index.tsx @@ -0,0 +1,27 @@ +import {useState, memo} from 'react' + +export default function App() { + const [num, update] = useState(0) + console.log('App render ', num) + return ( +
update(num + 1)}> + + +
+ ) +} + +const Cpn = memo(function ({num, name}) { + console.log('render ', name) + return ( +
+ {name}: {num} + +
+ ) +}) + +function Child() { + console.log('Child render') + return

i am child

+} diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index bce4278..fac3c81 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -1,10 +1,10 @@ use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::JsValue; +use wasm_bindgen::{JsCast, JsValue}; -use shared::{derive_from_js_value, is_dev, log}; -use web_sys::js_sys::Object; +use shared::{derive_from_js_value, is_dev, log, shallow_equal}; +use web_sys::js_sys::{Function, Object}; use crate::child_fiber::{clone_child_fiblers, mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; @@ -118,11 +118,51 @@ pub fn begin_work( WorkTag::HostText => Ok(None), WorkTag::ContextProvider => Ok(update_context_provider( work_in_progress.clone(), - render_lane, + render_lane.clone(), )), + WorkTag::MemoComponent => update_memo_component(work_in_progress.clone(), render_lane), }; } +fn update_memo_component( + work_in_progress: Rc>, + render_lane: Lane, +) -> Result>>, JsValue> { + let current = { work_in_progress.borrow().alternate.clone() }; + let next_props = { work_in_progress.borrow().pending_props.clone() }; + + if current.is_some() { + let current = current.unwrap(); + let prev_props = current.borrow().memoized_props.clone(); + if !check_scheduled_update_or_context(current.clone(), render_lane.clone()) { + let mut props_equal = false; + let compare = derive_from_js_value(&work_in_progress.borrow()._type, "compare"); + if compare.is_function() { + let f = compare.dyn_ref::().unwrap(); + props_equal = f + .call2(&JsValue::null(), &prev_props, &next_props) + .unwrap() + .as_bool() + .unwrap(); + } else { + props_equal = shallow_equal(&prev_props, &next_props); + } + + if props_equal && Object::is(¤t.borrow()._ref, &work_in_progress.borrow()._ref) { + unsafe { DID_RECEIVE_UPDATE = false }; + work_in_progress.borrow_mut().pending_props = prev_props; + work_in_progress.borrow_mut().lanes = current.borrow().lanes.clone(); + return Ok(bailout_on_already_finished_work( + work_in_progress.clone(), + render_lane, + )); + } + } + } + let Component = { derive_from_js_value(&work_in_progress.borrow()._type, "type") }; + update_function_component(work_in_progress.clone(), Component, render_lane) +} + fn update_context_provider( work_in_progress: Rc>, render_lane: Lane, diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index 600aa17..be78b39 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -12,9 +12,7 @@ use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::fiber_flags::{get_mutation_mask, get_passive_mask, Flags}; use crate::fiber_hooks::Effect; use crate::work_tags::WorkTag; -use crate::work_tags::WorkTag::{ - ContextProvider, FunctionComponent, HostComponent, HostRoot, HostText, -}; +use crate::work_tags::WorkTag::{FunctionComponent, HostComponent, HostRoot, HostText}; use crate::HOST_CONFIG; static mut NEXT_EFFECT: Option>> = None; @@ -314,7 +312,6 @@ fn commit_deletion(child_to_delete: Rc>, root: Rc { commit_passive_effect(unmount_fiber.clone(), root.clone(), "unmount"); } - HostRoot => {} HostComponent => { if cloned.borrow().is_none() { *cloned.borrow_mut() = Some(unmount_fiber.clone()); @@ -325,10 +322,7 @@ fn commit_deletion(child_to_delete: Rc>, root: Rc todo!(), - HostComponent => todo!(), - HostText => todo!(), - ContextProvider => todo!(), + _ => todo!(), }; }); diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index caaf693..1ab3eb3 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use wasm_bindgen::JsValue; use web_sys::js_sys::{Object, Reflect}; -use shared::{derive_from_js_value, log}; +use shared::derive_from_js_value; use crate::fiber::{FiberNode, StateNode}; use crate::fiber_context::pop_provider; @@ -141,16 +141,6 @@ impl CompleteWork { 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()); - // log!("bubble_properties function {:?}", work_in_progress.clone()); - None - } - WorkTag::HostRoot => { - self.bubble_properties(work_in_progress.clone()); - // log!("bubble_properties HostRoot {:?}", work_in_progress.clone()); - None - } WorkTag::HostComponent => { if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { // todo compare @@ -222,6 +212,10 @@ impl CompleteWork { self.bubble_properties(work_in_progress.clone()); None } + _ => { + 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 c261740..dcffed3 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -9,7 +9,7 @@ use scheduler::Task; use wasm_bindgen::JsValue; use web_sys::js_sys::Reflect; -use shared::{derive_from_js_value, log, type_of, REACT_PROVIDER_TYPE}; +use shared::{derive_from_js_value, log, type_of, REACT_MEMO_TYPE, REACT_PROVIDER_TYPE}; use crate::fiber_context::ContextItem; use crate::fiber_flags::Flags; @@ -128,6 +128,17 @@ impl Debug for FiberNode { ) .expect("print error"); } + WorkTag::MemoComponent => { + write!( + f, + "{:?}(flags:{:?}, subtreeFlags:{:?}, lanes:{:?})", + self._type.as_ref(), + self.flags, + self.subtree_flags, + self.lanes + ) + .expect("print error"); + } }) } } @@ -167,12 +178,17 @@ impl FiberNode { let mut fiber_tag = WorkTag::FunctionComponent; if _type.is_string() { fiber_tag = WorkTag::HostComponent - } else if type_of(&_type, "object") - && derive_from_js_value(&_type, "$$typeof") == REACT_PROVIDER_TYPE - { - fiber_tag = WorkTag::ContextProvider; + } else if type_of(&_type, "object") { + let _typeof = derive_from_js_value(&_type, "$$typeof"); + if _typeof == REACT_PROVIDER_TYPE { + fiber_tag = WorkTag::ContextProvider; + } else if _typeof == REACT_MEMO_TYPE { + fiber_tag = WorkTag::MemoComponent; + } else { + log!("Unsupported type {:?}", _type); + } } else if !type_of(&_type, "function") { - log!("Unsupported type {:?}", ele); + log!("Unsupported type {:?}", _type); } let mut fiber = FiberNode::new(fiber_tag, props, key, _ref); diff --git a/packages/react-reconciler/src/work_tags.rs b/packages/react-reconciler/src/work_tags.rs index c16071f..cfecddf 100644 --- a/packages/react-reconciler/src/work_tags.rs +++ b/packages/react-reconciler/src/work_tags.rs @@ -5,4 +5,5 @@ pub enum WorkTag { HostComponent = 5, HostText = 6, ContextProvider = 8, + MemoComponent = 15, } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 1819888..800d31b 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -1,7 +1,10 @@ use js_sys::{Array, Object, Reflect, JSON}; use wasm_bindgen::prelude::*; -use shared::{derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_PROVIDER_TYPE}; +use shared::{ + derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_MEMO_TYPE, + REACT_PROVIDER_TYPE, +}; use crate::current_dispatcher::CURRENT_DISPATCHER; @@ -166,3 +169,27 @@ pub unsafe fn create_context(default_value: &JsValue) -> JsValue { Reflect::set(&context, &"Provider".into(), &provider); context.into() } + +#[wasm_bindgen] +pub unsafe fn memo(_type: &JsValue, compare: &JsValue) -> JsValue { + let fiber_type = Object::new(); + + Reflect::set( + &fiber_type, + &"$$typeof".into(), + &JsValue::from_str(REACT_MEMO_TYPE), + ); + Reflect::set(&fiber_type, &"type".into(), _type); + + let null = JsValue::null(); + Reflect::set( + &fiber_type, + &"compare".into(), + if compare.is_undefined() { + &null + } else { + compare + }, + ); + fiber_type.into() +} diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 3740cdf..e0c65ab 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -1,10 +1,11 @@ -use web_sys::js_sys::Reflect; use web_sys::js_sys::JSON::stringify; -use web_sys::wasm_bindgen::JsValue; +use web_sys::js_sys::{Object, Reflect}; +use web_sys::wasm_bindgen::{JsCast, JsValue}; pub static REACT_ELEMENT_TYPE: &str = "react.element"; pub static REACT_CONTEXT_TYPE: &str = "react.context"; pub static REACT_PROVIDER_TYPE: &str = "react.provider"; +pub static REACT_MEMO_TYPE: &str = "react.memo"; #[macro_export] macro_rules! log { @@ -67,3 +68,35 @@ pub fn to_string(js_value: &JsValue) -> String { } }) } + +pub fn shallow_equal(a: &JsValue, b: &JsValue) -> bool { + if Object::is(a, b) { + return true; + } + + if !type_of(a, "object") || a.is_null() || !type_of(b, "object") || b.is_null() { + return false; + } + + let a = a.dyn_ref::().unwrap(); + let b = b.dyn_ref::().unwrap(); + let keys_a = Object::keys(a); + let keys_b = Object::keys(b); + + if keys_a.length() != keys_b.length() { + return false; + } + + for key in keys_a { + if !Object::has_own_property(&b, &key) + || !Object::is( + &Reflect::get(&a, &key).unwrap(), + &Reflect::get(&b, &key).unwrap(), + ) + { + return false; + } + } + + true +}