Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blog 20 - Context #20

Open
wants to merge 2 commits into
base: feat-perf
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/hello-world/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {default} from './perf'
export {default} from './useContext/index2'
27 changes: 27 additions & 0 deletions examples/hello-world/src/useContext/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ctxA.Provider value={'A0'}>
<ctxB.Provider value={'B0'}>
<ctxA.Provider value={'A1'}>
<Cpn />
</ctxA.Provider>
</ctxB.Provider>
<Cpn />
</ctxA.Provider>
)
}

function Cpn() {
const a = useContext(ctxA)
const b = useContext(ctxB)
return (
<div>
A: {a} B: {b}
</div>
)
}
28 changes: 28 additions & 0 deletions examples/hello-world/src/useContext/index2.tsx
Original file line number Diff line number Diff line change
@@ -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 <Child />
}, [])
console.log('App render ', num)
return (
<ctx.Provider value={num}>
<div
onClick={() => {
update(1)
}}>
{memoChild}
</div>
</ctx.Provider>
)
}

function Child() {
console.log('Child render')
const val = useContext(ctx)

return <div>ctx: {val}</div>
}
14 changes: 14 additions & 0 deletions packages/react-reconciler/src/begin_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<RefCell<FiberNode>>,
) -> Option<Rc<RefCell<FiberNode>>> {
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<RefCell<FiberNode>>,
render_lane: Lane,
Expand Down
7 changes: 6 additions & 1 deletion packages/react-reconciler/src/child_fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,12 @@ fn update_from_map(
should_track_effects: bool,
) -> Option<Rc<RefCell<FiberNode>>> {
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");
Expand Down
16 changes: 11 additions & 5 deletions packages/react-reconciler/src/commit_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rc<RefCell<FiberNode>>> = None;
Expand Down Expand Up @@ -309,20 +311,24 @@ fn commit_deletion(child_to_delete: Rc<RefCell<FiberNode>>, root: Rc<RefCell<Fib
commit_nested_unmounts(child_to_delete.clone(), |unmount_fiber| {
let cloned = first_host_fiber.clone();
match unmount_fiber.borrow().tag {
WorkTag::FunctionComponent => {
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!(),
};
});

Expand Down
11 changes: 9 additions & 2 deletions packages/react-reconciler/src/complete_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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();
}
Expand Down Expand Up @@ -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
}
}
}
}
17 changes: 16 additions & 1 deletion packages/react-reconciler/src/fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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");
}
})
}
}
Expand Down Expand Up @@ -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);
}
Expand Down
25 changes: 25 additions & 0 deletions packages/react-reconciler/src/fiber_context.rs
Original file line number Diff line number Diff line change
@@ -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<JsValue> = 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();
}
}
}
39 changes: 30 additions & 9 deletions packages/react-reconciler/src/fiber_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<dyn Fn(JsValue) -> JsValue>);
let use_context = use_context_clusure
.as_ref()
.unchecked_ref::<Function>()
.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());
}
Expand Down Expand Up @@ -666,14 +677,15 @@ fn update_memo(create: Function, deps: JsValue) -> Result<JsValue, JsValue> {
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::<Array>().unwrap();
let prev_deps = arr.get(1);
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions packages/react-reconciler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/work_tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pub enum WorkTag {
HostRoot = 3,
HostComponent = 5,
HostText = 6,
ContextProvider = 8,
}
7 changes: 5 additions & 2 deletions packages/react/src/current_dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ 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_memo: Function,
pub use_callback: Function,
pub use_context: Function,
}

unsafe impl Send for Dispatcher {}
Expand All @@ -22,13 +21,15 @@ impl Dispatcher {
use_ref: Function,
use_memo: Function,
use_callback: Function,
use_context: Function,
) -> Self {
Dispatcher {
use_state,
use_effect,
use_ref,
use_memo,
use_callback,
use_context,
}
}
}
Expand All @@ -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,
)))
}
Loading