diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index a9e1b84..52a923b 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './perf' +export {default} from './useContext/index2' diff --git a/examples/hello-world/src/useContext/index.tsx b/examples/hello-world/src/useContext/index.tsx new file mode 100644 index 0000000..0e9fd08 --- /dev/null +++ b/examples/hello-world/src/useContext/index.tsx @@ -0,0 +1,27 @@ +import {useState, createContext, useContext} from 'react' + +const ctxA = createContext('deafult A') +const ctxB = createContext('default B') + +export default function App() { + return ( + + + + + + + + + ) +} + +function Cpn() { + const a = useContext(ctxA) + const b = useContext(ctxB) + return ( +
+ A: {a} B: {b} +
+ ) +} diff --git a/examples/hello-world/src/useContext/index2.tsx b/examples/hello-world/src/useContext/index2.tsx new file mode 100644 index 0000000..6e8f6ac --- /dev/null +++ b/examples/hello-world/src/useContext/index2.tsx @@ -0,0 +1,28 @@ +import {useState, useContext, createContext, useMemo} from 'react' + +const ctx = createContext(0) + +export default function App() { + const [num, update] = useState(0) + const memoChild = useMemo(() => { + return + }, []) + console.log('App render ', num) + return ( + +
{ + update(1) + }}> + {memoChild} +
+
+ ) +} + +function Child() { + console.log('Child render') + const val = useContext(ctx) + + return
ctx: {val}
+} diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index d2ecadb..f208e26 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -8,6 +8,7 @@ use web_sys::js_sys::Object; use crate::child_fiber::{clone_child_fiblers, mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; +use crate::fiber_context::push_provider; use crate::fiber_flags::Flags; use crate::fiber_hooks::{bailout_hook, render_with_hooks}; use crate::fiber_lanes::{include_some_lanes, Lane}; @@ -108,9 +109,22 @@ pub fn begin_work( WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone(), render_lane)), WorkTag::HostComponent => Ok(update_host_component(work_in_progress.clone())), WorkTag::HostText => Ok(None), + WorkTag::ContextProvider => Ok(update_context_provider(work_in_progress.clone())), }; } +fn update_context_provider( + work_in_progress: Rc>, +) -> Option>> { + let provider_type = { work_in_progress.borrow()._type.clone() }; + let context = derive_from_js_value(&provider_type, "_context"); + let new_props = { work_in_progress.borrow().pending_props.clone() }; + push_provider(&context, derive_from_js_value(&new_props, "value")); + let next_children = derive_from_js_value(&new_props, "children"); + reconcile_children(work_in_progress.clone(), Some(next_children)); + work_in_progress.clone().borrow().child.clone() +} + fn update_function_component( work_in_progress: Rc>, render_lane: Lane, diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index 5d996e2..4d938ee 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -213,7 +213,12 @@ fn update_from_map( should_track_effects: bool, ) -> Option>> { let key_to_use; - if type_of(element, "string") || type_of(element, "null") || type_of(element, "number") { + if type_of(element, "string") + || type_of(element, "null") + || type_of(element, "number") + || type_of(element, "undefined") + || type_of(element, "null") + { key_to_use = JsValue::from(index); } else { let key = derive_from_js_value(element, "key"); diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index 96ac6fe..600aa17 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -12,7 +12,9 @@ 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::{HostComponent, HostRoot, HostText}; +use crate::work_tags::WorkTag::{ + ContextProvider, FunctionComponent, HostComponent, HostRoot, HostText, +}; use crate::HOST_CONFIG; static mut NEXT_EFFECT: Option>> = None; @@ -309,20 +311,24 @@ fn commit_deletion(child_to_delete: Rc>, root: Rc { + FunctionComponent => { commit_passive_effect(unmount_fiber.clone(), root.clone(), "unmount"); } - WorkTag::HostRoot => {} - WorkTag::HostComponent => { + HostRoot => {} + HostComponent => { if cloned.borrow().is_none() { *cloned.borrow_mut() = Some(unmount_fiber.clone()); } } - WorkTag::HostText => { + HostText => { if cloned.borrow().is_none() { *cloned.borrow_mut() = Some(unmount_fiber.clone()); } } + HostRoot => todo!(), + HostComponent => todo!(), + HostText => todo!(), + ContextProvider => todo!(), }; }); diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 415e9bf..caaf693 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -8,6 +8,7 @@ use web_sys::js_sys::{Object, Reflect}; use shared::{derive_from_js_value, log}; use crate::fiber::{FiberNode, StateNode}; +use crate::fiber_context::pop_provider; use crate::fiber_flags::Flags; use crate::fiber_lanes::{merge_lanes, Lane}; use crate::work_tags::WorkTag; @@ -100,7 +101,7 @@ impl CompleteWork { let mut subtree_flags = Flags::NoFlags; let mut new_child_lanes = Lane::NoLane; { - let mut child = complete_work.clone().borrow().child.clone(); + let mut child = { complete_work.clone().borrow().child.clone() }; while child.is_some() { let child_rc = child.clone().unwrap().clone(); @@ -123,7 +124,6 @@ impl CompleteWork { child = child_rc.borrow().sibling.clone(); } } - complete_work.clone().borrow_mut().subtree_flags |= subtree_flags.clone(); complete_work.clone().borrow_mut().child_lanes |= new_child_lanes.clone(); } @@ -215,6 +215,13 @@ impl CompleteWork { self.bubble_properties(work_in_progress.clone()); None } + WorkTag::ContextProvider => { + let _type = { work_in_progress.borrow()._type.clone() }; + let context = derive_from_js_value(&_type, "_context"); + pop_provider(&context); + 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 d29bc6e..29be5dd 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}; +use shared::{derive_from_js_value, log, type_of, REACT_PROVIDER_TYPE}; use crate::fiber_flags::Flags; use crate::fiber_hooks::{Effect, Hook}; @@ -109,6 +109,17 @@ impl Debug for FiberNode { ) .expect("print error"); } + WorkTag::ContextProvider => { + write!( + f, + "{:?}(flags:{:?}, subtreeFlags:{:?}, lanes:{:?})", + self._type.as_ref(), + self.flags, + self.subtree_flags, + self.lanes + ) + .expect("print error"); + } }) } } @@ -147,6 +158,10 @@ 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, "function") { log!("Unsupported type {:?}", ele); } diff --git a/packages/react-reconciler/src/fiber_context.rs b/packages/react-reconciler/src/fiber_context.rs new file mode 100644 index 0000000..2c9e808 --- /dev/null +++ b/packages/react-reconciler/src/fiber_context.rs @@ -0,0 +1,25 @@ +use wasm_bindgen::JsValue; +use web_sys::js_sys::Reflect; + +static mut PREV_CONTEXT_VALUE: JsValue = JsValue::null(); +static mut PREV_CONTEXT_VALUE_STACK: Vec = vec![]; + +pub fn push_provider(context: &JsValue, new_value: JsValue) { + unsafe { + PREV_CONTEXT_VALUE_STACK.push(PREV_CONTEXT_VALUE.clone()); + PREV_CONTEXT_VALUE = Reflect::get(context, &"_currentValue".into()).unwrap(); + Reflect::set(context, &"_currentValue".into(), &new_value); + } +} + +pub fn pop_provider(context: &JsValue) { + unsafe { + Reflect::set(context, &"_currentValue".into(), &PREV_CONTEXT_VALUE); + let top = PREV_CONTEXT_VALUE_STACK.pop(); + if top.is_none() { + PREV_CONTEXT_VALUE = JsValue::null(); + } else { + PREV_CONTEXT_VALUE = top.unwrap(); + } + } +} diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index ad46d63..e280e0d 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; -use shared::{is_dev, log}; +use shared::{derive_from_js_value, is_dev, log}; use crate::begin_work::mark_wip_received_update; use crate::fiber::{FiberNode, MemoizedState}; @@ -146,12 +146,23 @@ fn update_hooks_to_dispatcher(is_update: bool) { .clone(); use_callback_clusure.forget(); + // use_context + let use_context_clusure = + Closure::wrap(Box::new(read_context) as Box JsValue>); + let use_context = use_context_clusure + .as_ref() + .unchecked_ref::() + .clone(); + use_context_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"); + Reflect::set(&object, &"use_context".into(), &use_context) + .expect("TODO: panic set use_context"); updateDispatcher(&object.into()); } @@ -666,14 +677,15 @@ fn update_memo(create: Function, deps: JsValue) -> Result { deps }; - if let MemoizedState::MemoizedJsValue(prev_state) = hook - .clone() - .unwrap() - .borrow() - .memoized_state - .as_ref() - .unwrap() - { + let memoized_state = { + hook.clone() + .unwrap() + .borrow() + .memoized_state + .clone() + .unwrap() + }; + if let MemoizedState::MemoizedJsValue(prev_state) = memoized_state { if !next_deps.is_null() { let arr = prev_state.dyn_ref::().unwrap(); let prev_deps = arr.get(1); @@ -739,3 +751,12 @@ fn update_callback(callback: Function, deps: JsValue) -> JsValue { } panic!("update_callback, memoized_state is not JsValue"); } + +fn read_context(context: JsValue) -> JsValue { + let consumer = unsafe { CURRENTLY_RENDERING_FIBER.clone() }; + if consumer.is_none() { + panic!("Can only call useContext in Function Component"); + } + let value = derive_from_js_value(&context, "_currentValue"); + value +} diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 81e8d52..3b7eb3b 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -17,6 +17,7 @@ mod child_fiber; mod commit_work; mod complete_work; pub mod fiber; +mod fiber_context; mod fiber_flags; mod fiber_hooks; pub mod fiber_lanes; diff --git a/packages/react-reconciler/src/work_tags.rs b/packages/react-reconciler/src/work_tags.rs index 3b72990..c16071f 100644 --- a/packages/react-reconciler/src/work_tags.rs +++ b/packages/react-reconciler/src/work_tags.rs @@ -4,4 +4,5 @@ pub enum WorkTag { HostRoot = 3, HostComponent = 5, HostText = 6, + ContextProvider = 8, } diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index 9b63a0c..a62e1d6 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -2,8 +2,6 @@ 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, @@ -11,6 +9,7 @@ pub struct Dispatcher { pub use_ref: Function, pub use_memo: Function, pub use_callback: Function, + pub use_context: Function, } unsafe impl Send for Dispatcher {} @@ -22,6 +21,7 @@ impl Dispatcher { use_ref: Function, use_memo: Function, use_callback: Function, + use_context: Function, ) -> Self { Dispatcher { use_state, @@ -29,6 +29,7 @@ impl Dispatcher { use_ref, use_memo, use_callback, + use_context, } } } @@ -53,11 +54,13 @@ pub unsafe fn update_dispatcher(args: &JsValue) { let use_ref = derive_function_from_js_value(args, "use_ref"); let use_memo = derive_function_from_js_value(args, "use_memo"); let use_callback = derive_function_from_js_value(args, "use_callback"); + let use_context = derive_function_from_js_value(args, "use_context"); CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( use_state, use_effect, use_ref, use_memo, use_callback, + use_context, ))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 7b02db8..1819888 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -1,7 +1,7 @@ use js_sys::{Array, Object, Reflect, JSON}; use wasm_bindgen::prelude::*; -use shared::{derive_from_js_value, REACT_ELEMENT_TYPE}; +use shared::{derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_PROVIDER_TYPE}; use crate::current_dispatcher::CURRENT_DISPATCHER; @@ -140,3 +140,29 @@ pub unsafe fn use_callback(callback: &JsValue, deps: &JsValue) -> Result Result { + let use_context = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_context; + use_context.call1(&JsValue::null(), context) +} + +#[wasm_bindgen(js_name = createContext)] +pub unsafe fn create_context(default_value: &JsValue) -> JsValue { + let context = Object::new(); + Reflect::set( + &context, + &"$$typeof".into(), + &JsValue::from_str(REACT_CONTEXT_TYPE), + ); + Reflect::set(&context, &"_currentValue".into(), default_value); + let provider = Object::new(); + Reflect::set( + &provider, + &"$$typeof".into(), + &JsValue::from_str(REACT_PROVIDER_TYPE), + ); + Reflect::set(&provider, &"_context".into(), &context); + Reflect::set(&context, &"Provider".into(), &provider); + context.into() +} diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 3366641..3740cdf 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -1,8 +1,10 @@ -use web_sys::js_sys::JSON::stringify; use web_sys::js_sys::Reflect; +use web_sys::js_sys::JSON::stringify; use web_sys::wasm_bindgen::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"; #[macro_export] macro_rules! log {