diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index eb18dd9..1808250 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './lazy' +export {default} from './useTransition' diff --git a/examples/hello-world/src/useTransition/AboutTab.tsx b/examples/hello-world/src/useTransition/AboutTab.tsx new file mode 100644 index 0000000..4b91b29 --- /dev/null +++ b/examples/hello-world/src/useTransition/AboutTab.tsx @@ -0,0 +1,3 @@ +export default function AboutTab() { + return

Welcome to my profile!

+} diff --git a/examples/hello-world/src/useTransition/ContactTab.tsx b/examples/hello-world/src/useTransition/ContactTab.tsx new file mode 100644 index 0000000..b6cad8a --- /dev/null +++ b/examples/hello-world/src/useTransition/ContactTab.tsx @@ -0,0 +1,11 @@ +export default function ContactTab() { + return ( + <> +

You can find me online here:

+ + + ) +} diff --git a/examples/hello-world/src/useTransition/PostsTab.tsx b/examples/hello-world/src/useTransition/PostsTab.tsx new file mode 100644 index 0000000..01fca7b --- /dev/null +++ b/examples/hello-world/src/useTransition/PostsTab.tsx @@ -0,0 +1,23 @@ +import {memo} from 'react' + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 ') + + let items = [] + for (let i = 0; i < 500; i++) { + items.push() + } + return +}) + +function SlowPost({index}) { + let startTime = performance.now() + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return
  • Post #{index + 1}
  • +} + +export default PostsTab diff --git a/examples/hello-world/src/useTransition/TabButton.tsx b/examples/hello-world/src/useTransition/TabButton.tsx new file mode 100644 index 0000000..e546a23 --- /dev/null +++ b/examples/hello-world/src/useTransition/TabButton.tsx @@ -0,0 +1,13 @@ +export default function TabButton({children, isActive, onClick}) { + if (isActive) { + return {children} + } + return ( + + ) +} diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx new file mode 100644 index 0000000..a0c5704 --- /dev/null +++ b/examples/hello-world/src/useTransition/index.tsx @@ -0,0 +1,37 @@ +import {useState, useTransition} from 'react' +import TabButton from './TabButton.js' +import AboutTab from './AboutTab.js' +import PostsTab from './PostsTab.js' +import ContactTab from './ContactTab.js' +import './style.css' + +export default function TabContainer() { + const [isPending, startTransition] = useTransition() + const [tab, setTab] = useState('about') + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab) + }) + } + + return ( +
    + selectTab('about')}> + About + + selectTab('posts')}> + Posts (slow) + + selectTab('contact')}> + Contact + +
    + {tab === 'about' && } + {tab === 'posts' && } + {tab === 'contact' && } +
    + ) +} diff --git a/examples/hello-world/src/useTransition/style.css b/examples/hello-world/src/useTransition/style.css new file mode 100644 index 0000000..7890858 --- /dev/null +++ b/examples/hello-world/src/useTransition/style.css @@ -0,0 +1,55 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +h1 { + margin-top: 0; + font-size: 22px; +} + +h2 { + margin-top: 0; + font-size: 20px; +} + +h3 { + margin-top: 0; + font-size: 18px; +} + +h4 { + margin-top: 0; + font-size: 16px; +} + +h5 { + margin-top: 0; + font-size: 14px; +} + +h6 { + margin-top: 0; + font-size: 12px; +} + +code { + font-size: 1.2em; +} + +ul { + padding-inline-start: 20px; +} + +button { + margin-right: 10px; +} +b { + display: inline-block; + margin-right: 10px; +} diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index fecc212..5b2ffd8 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -81,7 +81,6 @@ fn trigger_event_flow(paths: Vec, se: &Event) { fn dispatch_event(container: &Element, event_type: String, e: &Event) { if e.target().is_none() { - log!("Target is none"); return; } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 4d32fd0..984fb4d 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -51,7 +51,6 @@ pub fn begin_work( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { - log!("begin_work {:?}", work_in_progress.clone()); unsafe { DID_RECEIVE_UPDATE = false; }; @@ -96,7 +95,7 @@ pub fn begin_work( } } - work_in_progress.borrow_mut().lanes = Lane::NoLane; + work_in_progress.borrow_mut().lanes -= render_lane.clone(); // if current.is_some() { // let current = current.clone().unwrap(); // current.borrow_mut().lanes = Lane::NoLane; @@ -280,7 +279,7 @@ fn update_suspense_component( let next_primary_children = derive_from_js_value(&next_props, "children"); let next_fallback_children = derive_from_js_value(&next_props, "fallback"); push_suspense_handler(work_in_progress.clone()); - log!("show_fallback {:?}", show_fallback); + if current.is_none() { if show_fallback { return Some(mount_suspense_fallback_children( @@ -405,9 +404,6 @@ fn update_function_component( render_with_hooks(work_in_progress.clone(), Component, render_lane.clone())?; let current = { work_in_progress.borrow().alternate.clone() }; - log!("{:?} {:?}", work_in_progress.clone(), unsafe { - DID_RECEIVE_UPDATE - }); if current.is_some() && unsafe { !DID_RECEIVE_UPDATE } { bailout_hook(work_in_progress.clone(), render_lane.clone()); return Ok(bailout_on_already_finished_work( diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index be41696..3c5ebcc 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -389,11 +389,11 @@ fn reconcile_children_array( for (_, fiber) in existing_children { delete_child(return_fiber.clone(), fiber, should_track_effects); } - log!( - "first_new_fiber {:?} {:?}", - first_new_fiber, - first_new_fiber.clone().unwrap().borrow().sibling - ); + // log!( + // "first_new_fiber {:?} {:?}", + // first_new_fiber, + // first_new_fiber.clone().unwrap().borrow().sibling + // ); first_new_fiber } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 664c47d..00f05ef 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -115,7 +115,7 @@ impl Debug for FiberNode { self.flags, self.subtree_flags, self.lanes, - self.child_lanes + self.child_lanes, ) .expect("print error"); } @@ -299,7 +299,7 @@ pub struct FiberRootNode { pub finished_lanes: Lane, pub suspended_lanes: Lane, pub pinged_lanes: Lane, // Records the processed suspended lanes, comes from suspended lanes - pub callback_node: Option, + pub callback_node: Option>>, pub callback_priority: Lane, pub pending_passive_effects: Rc>, pub ping_cache: Option>>>>, diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index b489b3f..aaf206e 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; +use react::current_batch_config::REACT_CURRENT_BATCH_CONFIG; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; @@ -157,6 +158,18 @@ fn update_hooks_to_dispatcher(is_update: bool) { .clone(); use_context_closure.forget(); + // use_transition + let use_transition_closure = Closure::wrap(Box::new(if is_update { + update_transition + } else { + mount_transition + }) as Box Vec>); + let use_transition = use_transition_closure + .as_ref() + .unchecked_ref::() + .clone(); + use_transition_closure.forget(); + // use let use_closure = Closure::wrap(Box::new(_use) as Box Result>); @@ -171,6 +184,8 @@ fn update_hooks_to_dispatcher(is_update: bool) { .expect("TODO: panic set use_callback"); Reflect::set(&object, &"use_context".into(), &use_context) .expect("TODO: panic set use_context"); + Reflect::set(&object, &"use_transition".into(), &use_transition) + .expect("TODO: panic set use_transition"); Reflect::set(&object, &"use".into(), &use_fn).expect("TODO: panic set use"); updateDispatcher(&object.into()); @@ -604,7 +619,6 @@ fn update_effect(create: Function, deps: JsValue) { .unwrap() .borrow_mut() .flags |= Flags::PassiveEffect; - log!("CURRENTLY_RENDERING_FIBER.as_ref().unwrap().borrow_mut()"); hook.as_ref().unwrap().clone().borrow_mut().memoized_state = Some(MemoizedState::Effect(push_effect( @@ -774,3 +788,49 @@ pub fn reset_hooks_on_unwind(wip: Rc>) { WORK_IN_PROGRESS_HOOK = None; } } + +fn mount_transition() -> Vec { + let result = mount_state(&JsValue::from(false)).unwrap(); + let is_pending = result[0].as_bool().unwrap(); + let set_pending = result[1].clone().dyn_into::().unwrap(); + let hook = mount_work_in_progress_hook(); + let set_pending_cloned = set_pending.clone(); + let closure = Closure::wrap(Box::new(move |callback: Function| { + start_transition(set_pending_cloned.clone(), callback); + }) as Box); + let start: Function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(start.clone().into())); + vec![JsValue::from_bool(is_pending), start.into()] +} + +fn update_transition() -> Vec { + let result = update_state(&JsValue::undefined()).unwrap(); + let is_pending = result[0].as_bool().unwrap(); + let hook = update_work_in_progress_hook(); + if let MemoizedState::MemoizedJsValue(start) = hook + .as_ref() + .unwrap() + .clone() + .borrow() + .memoized_state + .as_ref() + .unwrap() + { + return vec![JsValue::from_bool(is_pending), start.into()]; + } + panic!("update_transition") +} + +fn start_transition(set_pending: Function, callback: Function) { + set_pending.call1(&JsValue::null(), &JsValue::from_bool(true)); + let prev_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition }; + + // low priority + unsafe { REACT_CURRENT_BATCH_CONFIG.transition = Lane::TransitionLane.bits() }; + callback.call0(&JsValue::null()); + set_pending.call1(&JsValue::null(), &JsValue::from_bool(false)); + + unsafe { REACT_CURRENT_BATCH_CONFIG.transition = prev_transition }; +} diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index 1a511bc..b113d15 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use react::current_batch_config::REACT_CURRENT_BATCH_CONFIG; use scheduler::{unstable_get_current_priority_level, Priority}; use std::cell::RefCell; use std::hash::{Hash, Hasher}; @@ -13,6 +14,7 @@ bitflags! { const SyncLane = 0b0000000000000000000000000000001; // onClick const InputContinuousLane = 0b0000000000000000000000000000010; // Continuous Trigger, example: onScroll const DefaultLane = 0b0000000000000000000000000000100; // useEffect + const TransitionLane = 0b0000000000000000000000000001000; const IdleLane = 0b1000000000000000000000000000000; } } @@ -46,6 +48,10 @@ pub fn is_subset_of_lanes(set: Lane, subset: Lane) -> bool { } pub fn request_update_lane() -> Lane { + let is_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition } != 0; + if is_transition { + return Lane::TransitionLane; + } let current_scheduler_priority_level = unstable_get_current_priority_level(); let update_lane = scheduler_priority_to_lane(current_scheduler_priority_level); update_lane diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index b3e6c8d..1239762 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -1,19 +1,19 @@ use std::cell::RefCell; use std::rc::Rc; +use shared::log; +use std::fmt::{write, Debug, Formatter}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::Function; -use shared::log; - use crate::fiber::{FiberNode, MemoizedState}; -use crate::fiber_hooks::{basic_state_reducer, Effect}; +use crate::fiber_hooks::Effect; use crate::fiber_lanes::{is_subset_of_lanes, merge_lanes, Lane}; #[derive(Clone, Debug)] pub struct UpdateAction; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Update { pub action: Option, pub lane: Lane, @@ -22,6 +22,12 @@ pub struct Update { pub eager_state: Option, } +impl Debug for Update { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?} {:?}", self.action, self.lane) + } +} + #[derive(Clone, Debug)] pub struct UpdateType { pub pending: Option>>, @@ -88,6 +94,31 @@ pub struct ReturnOfProcessUpdateQueue { pub base_queue: Option>>, } +impl Debug for ReturnOfProcessUpdateQueue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "memoized_state:{:?} base_state:{:?}", + self.memoized_state, self.base_state + ); + + if self.base_queue.is_some() { + let base_queue = self.base_queue.clone().unwrap(); + write!(f, " base_queue:{:?}", base_queue); + let mut next_option = base_queue.borrow().next.clone(); + while next_option.is_some() { + let next = next_option.clone().unwrap(); + if Rc::ptr_eq(&next, &base_queue) { + break; + } + write!(f, "---> {:?}", next); + next_option = next.borrow().next.clone(); + } + } + Ok(()) + } +} + pub fn process_update_queue( base_state: Option, pending_update: Option>>, @@ -116,8 +147,9 @@ pub fn process_update_queue( loop { let mut update = pending.clone().unwrap(); let update_lane = update.borrow().lane.clone(); + // log!("update {:?} render_lanes {:?}", update, render_lanes); if !is_subset_of_lanes(render_lanes.clone(), update_lane.clone()) { - // underpriority + // log!("underpriority update {:?}", update); let clone = Rc::new(RefCell::new(create_update( update.borrow().action.clone().unwrap(), update_lane.clone(), @@ -134,6 +166,7 @@ pub fn process_update_queue( new_base_state = result.memoized_state.clone(); } else { new_base_queue_last.clone().unwrap().borrow_mut().next = Some(clone.clone()); + new_base_queue_last = Some(clone.clone()); } } else { if new_base_queue_last.is_some() { @@ -232,13 +265,13 @@ pub fn process_update_queue( if new_base_queue_last.is_none() { new_base_state = new_state.clone(); } else { - new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_last.clone(); + new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_first.clone(); } result.memoized_state = new_state; result.base_state = new_base_state; result.base_queue = new_base_queue_last.clone(); } - + // log!("result:{:?}", result); result } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 5a965ee..0c67ca0 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -142,8 +142,12 @@ pub fn ensure_root_is_scheduled(root: Rc>) { .schedule_microtask(Box::new(|| flush_sync_callbacks())); } } else { + if is_dev() { + log!("Schedule in macrotask, priority {:?}", update_lanes); + } let scheduler_priority = lanes_to_scheduler_priority(cur_priority.clone()); let closure = Closure::wrap(Box::new(move |did_timeout_js_value: JsValue| { + log!("did_timeout_js_value1 {:?}", did_timeout_js_value); let did_timeout = did_timeout_js_value.as_bool().unwrap(); perform_concurrent_work_on_root(root_cloned.clone(), did_timeout) }) as Box JsValue>); @@ -205,16 +209,17 @@ fn render_root(root: Rc>, lane: Lane, should_time_slice: Ok(_) => { break; } - Err(e) => handle_throw(root.clone(), e), + Err(e) => { + log!("e {:?}", e); + handle_throw(root.clone(), e) + } }; } log!("render over {:?}", *root.clone().borrow()); - // log!("render over {:?}", unsafe { WORK_IN_PROGRESS.clone() }); - // log!("render over"); unsafe { - WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; + // WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; if should_time_slice && WORK_IN_PROGRESS.is_some() { return ROOT_INCOMPLETE; @@ -253,7 +258,9 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout ensure_root_is_scheduled(root.clone()); if exit_status == ROOT_INCOMPLETE { - if root.borrow().callback_node.as_ref().unwrap().id != cur_callback_node.unwrap().id { + if root.borrow().callback_node.clone().unwrap().borrow().id + != cur_callback_node.unwrap().borrow().id + { // 调度了更高优更新,这个更新已经被取消了 return JsValue::undefined(); } @@ -279,7 +286,7 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout }; root.clone().borrow_mut().finished_work = finished_work; root.clone().borrow_mut().finished_lanes = lanes; - + unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; commit_root(root); } else { todo!("Unsupported status of concurrent render") @@ -313,7 +320,7 @@ fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; commit_root(root); } else if exit_status == ROOT_DID_NOT_COMPLETE { - unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; + // unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; mark_root_suspended(root.clone(), next_lane); ensure_root_is_scheduled(root.clone()); } else { @@ -436,7 +443,6 @@ fn work_loop_sync() -> Result<(), JsValue> { fn work_loop_concurrent() -> Result<(), JsValue> { unsafe { while WORK_IN_PROGRESS.is_some() && !unstable_should_yield_to_host() { - log!("work_loop_concurrent"); perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; } } diff --git a/packages/react/src/current_batch_config.rs b/packages/react/src/current_batch_config.rs new file mode 100644 index 0000000..63e1d2a --- /dev/null +++ b/packages/react/src/current_batch_config.rs @@ -0,0 +1,6 @@ +pub struct ReactCurrentBatchConfig { + pub transition: u32, +} + +pub static mut REACT_CURRENT_BATCH_CONFIG: ReactCurrentBatchConfig = + ReactCurrentBatchConfig { transition: 0 }; diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index c41bd75..28cdbe6 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -10,6 +10,7 @@ pub struct Dispatcher { pub use_memo: Function, pub use_callback: Function, pub use_context: Function, + pub use_transition: Function, pub _use: Function, } @@ -23,6 +24,7 @@ impl Dispatcher { use_memo: Function, use_callback: Function, use_context: Function, + use_transition: Function, _use: Function, ) -> Self { Dispatcher { @@ -32,6 +34,7 @@ impl Dispatcher { use_memo, use_callback, use_context, + use_transition, _use, } } @@ -58,6 +61,7 @@ pub unsafe fn update_dispatcher(args: &JsValue) { 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"); + let use_transition = derive_function_from_js_value(args, "use_transition"); let _use = derive_function_from_js_value(args, "use"); CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( use_state, @@ -66,6 +70,7 @@ pub unsafe fn update_dispatcher(args: &JsValue) { use_memo, use_callback, use_context, + use_transition, _use, ))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index de09020..8aa70a4 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -9,6 +9,7 @@ use shared::{ use crate::current_dispatcher::CURRENT_DISPATCHER; +pub mod current_batch_config; pub mod current_dispatcher; mod lazy; @@ -158,6 +159,12 @@ pub unsafe fn _use(usable: &JsValue) -> Result { _use.call1(&JsValue::null(), usable) } +#[wasm_bindgen(js_name = useTransition)] +pub unsafe fn use_transition() -> Result { + let use_transition = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_transition; + use_transition.call0(&JsValue::null()) +} + #[wasm_bindgen(js_name = createContext)] pub unsafe fn create_context(default_value: &JsValue) -> JsValue { let context = Object::new(); diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index fbc551d..f59368b 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -1,4 +1,6 @@ +use std::cell::RefCell; use std::cmp::{Ordering, PartialEq}; +use std::rc::Rc; use shared::log; use wasm_bindgen::prelude::*; @@ -11,8 +13,8 @@ mod heap; static FRAME_YIELD_MS: f64 = 5.0; static mut TASK_ID_COUNTER: u32 = 1; -static mut TASK_QUEUE: Vec = vec![]; -static mut TIMER_QUEUE: Vec = vec![]; +static mut TASK_QUEUE: Vec>> = vec![]; +static mut TIMER_QUEUE: Vec>> = vec![]; static mut IS_HOST_TIMEOUT_SCHEDULED: bool = false; static mut IS_HOST_CALLBACK_SCHEDULED: bool = false; static mut IS_PERFORMING_WORK: bool = false; @@ -23,7 +25,7 @@ static mut MESSAGE_CHANNEL: Option = None; // static mut MESSAGE_CHANNEL_LISTENED: bool = false; static mut START_TIME: f64 = -1.0; static mut CURRENT_PRIORITY_LEVEL: Priority = Priority::NormalPriority; -static mut CURRENT_TASK: Option<&Task> = None; +static mut CURRENT_TASK: Option<&Rc>> = None; static mut PORT1: Option = None; static mut PORT2: Option = None; @@ -243,19 +245,20 @@ fn main() { */ fn advance_timers(current_time: f64) { unsafe { - let mut timer = peek_mut(&mut TIMER_QUEUE); + let mut timer = peek(&TIMER_QUEUE); while timer.is_some() { let task = timer.unwrap(); - if task.callback.is_null() { + if task.borrow().callback.is_null() { pop(&mut TIMER_QUEUE); - } else if task.start_time <= current_time { + } else if task.borrow().start_time <= current_time { let t = pop(&mut TIMER_QUEUE); - task.sort_index = task.expiration_time; + let expiration_time = { task.borrow().expiration_time }; + task.borrow_mut().sort_index = expiration_time; push(&mut TASK_QUEUE, task.clone()); } else { return; } - timer = peek_mut(&mut TIMER_QUEUE); + timer = peek(&mut TIMER_QUEUE); } } } @@ -307,35 +310,34 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result current_time + if t.borrow().expiration_time > current_time && (!has_time_remaining || unstable_should_yield_to_host()) { break; } - let callback = t.callback.clone(); + let callback = t.borrow().callback.clone(); if callback.is_function() { - t.callback = JsValue::null(); - CURRENT_PRIORITY_LEVEL = t.priority_level.clone(); - let did_user_callback_timeout = t.expiration_time <= current_time; + t.borrow_mut().callback = JsValue::null(); + CURRENT_PRIORITY_LEVEL = t.borrow().priority_level.clone(); + let did_user_callback_timeout = t.borrow().expiration_time <= current_time; let continuation_callback = callback .dyn_ref::() .unwrap() .call1(&JsValue::null(), &JsValue::from(did_user_callback_timeout))?; current_time = unstable_now(); - if continuation_callback.is_function() { - t.callback = continuation_callback; + t.borrow_mut().callback = continuation_callback; } else { if match peek(&TASK_QUEUE) { None => false, - Some(task) => task == t, + Some(task) => task.borrow().id == t.borrow().id, } { pop(&mut TASK_QUEUE); } @@ -346,7 +348,6 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result Result>) { + let id = t.borrow().id; + unsafe { - for mut task in &mut TASK_QUEUE { - if task.id == id { - task.callback = JsValue::null(); + for task in &TASK_QUEUE { + if task.borrow().id == id { + task.borrow_mut().callback = JsValue::null(); } } - for mut task in &mut TIMER_QUEUE { - if task.id == id { - task.callback = JsValue::null(); + for task in &TIMER_QUEUE { + if task.borrow().id == id { + task.borrow_mut().callback = JsValue::null(); } } } @@ -430,7 +432,7 @@ pub fn unstable_schedule_callback( priority_level: Priority, callback: Function, delay: f64, -) -> Task { +) -> Rc> { let current_time = unstable_now(); let mut start_time = current_time; @@ -440,16 +442,16 @@ pub fn unstable_schedule_callback( let timeout = get_priority_timeout(priority_level.clone()); let expiration_time = start_time + timeout; - let mut new_task = Task::new( + let mut new_task = Rc::new(RefCell::new(Task::new( callback, priority_level.clone(), start_time, expiration_time, - ); + ))); let cloned = new_task.clone(); unsafe { if start_time > current_time { - new_task.sort_index = start_time; + new_task.borrow_mut().sort_index = start_time; push(&mut TIMER_QUEUE, new_task.clone()); if peek(&mut TASK_QUEUE).is_none() { @@ -465,7 +467,7 @@ pub fn unstable_schedule_callback( } } } else { - new_task.sort_index = expiration_time; + new_task.borrow_mut().sort_index = expiration_time; push(&mut TASK_QUEUE, new_task); if !IS_HOST_CALLBACK_SCHEDULED && !IS_PERFORMING_WORK { @@ -478,7 +480,10 @@ pub fn unstable_schedule_callback( cloned } -pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> Task { +pub fn unstable_schedule_callback_no_delay( + priority_level: Priority, + callback: Function, +) -> Rc> { unstable_schedule_callback(priority_level, callback, 0.0) } diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index c121773..19ed72b 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -20,10 +20,7 @@ macro_rules! log { pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { match Reflect::get(&js_value, &JsValue::from_str(str)) { Ok(v) => v, - Err(_) => { - log!("derive {} from {:?} error", str, js_value); - JsValue::undefined() - } + Err(_) => JsValue::undefined(), } } diff --git a/readme.md b/readme.md index 0786952..b0fa33b 100644 --- a/readme.md +++ b/readme.md @@ -55,3 +55,7 @@ [从零实现 React v18,但 WASM 版 - [23] 实现 Fragment](https://www.paradeto.com/2024/08/01/big-react-wasm-23/) [从零实现 React v18,但 WASM 版 - [24] 实现 Suspense(一):渲染 Fallback](https://www.paradeto.com/2024/08/01/big-react-wasm-24/) + +[从零实现 React v18,但 WASM 版 - [25] 实现 Suspense(二):结合 use hooks 获取数据](https://www.paradeto.com/2024/08/01/big-react-wasm-25/) + +[从零实现 React v18,但 WASM 版 - [26] 实现 React.lazy](https://www.paradeto.com/2024/08/01/big-react-wasm-26/) diff --git a/scripts/build.js b/scripts/build.js index bad3355..0b0f6ec 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -72,17 +72,22 @@ const reactDomIndexBgData = fs.readFileSync(reactDomIndexFilename) fs.writeFileSync(reactDomIndexFilename, code1 + reactDomIndexBgData) // add Suspense + Fragment -const reactIndexFilename = `${cwd}/dist/react/index.js` -const reactIndexData = fs.readFileSync(reactIndexFilename) -fs.writeFileSync( - reactIndexFilename, - reactIndexData + - `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` -) -const reactTsIndexFilename = `${cwd}/dist/react/index.d.ts` -const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) -fs.writeFileSync( - reactTsIndexFilename, - reactTsIndexData + - `export const Suspense: string;\nexport const Fragment: string;\n` -) +;[ + {filename: 'index.js', tsFilename: 'index.d.ts'}, + {filename: 'jsx-dev-runtime.js', tsFilename: 'jsx-dev-runtime.d.ts'}, +].forEach(({filename, tsFilename}) => { + const reactIndexFilename = `${cwd}/dist/react/${filename}` + const reactIndexData = fs.readFileSync(reactIndexFilename) + fs.writeFileSync( + reactIndexFilename, + reactIndexData + + `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` + ) + const reactTsIndexFilename = `${cwd}/dist/react/${tsFilename}` + const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) + fs.writeFileSync( + reactTsIndexFilename, + reactTsIndexData + + `export const Suspense: string;\nexport const Fragment: string;\n` + ) +})