پیاده سازی React v18 از ابتدا با استفاده از WASM و Rust – [27] پیاده سازی useTransition
بر اساس big-react، من قصد دارم ویژگی های هسته React v18 را از ابتدا با استفاده از WASM و Rust پیاده سازی کنم.
مخزن کد:https://github.com/ParadeTo/big-react-wasm
برچسب مربوط به این مقاله:v27
useTransition
یک هوک جدید است که در React معرفی شده است که به شما امکان می دهد بدون مسدود کردن رابط کاربری، وضعیت را به روز کنید. وب سایت رسمی React مثالی ارائه می دهد که تفاوت قبل و بعد از استفاده را نشان می دهد useTransition
. همچنین مقاله ای وجود دارد که اصول اساسی را تحلیل می کند. حالا بیایید آن را پیاده سازی کنیم. جزئیات تغییرات را می توانید در این لینک مشاهده کنید.
ابتدا مراحل افزودن هوک جدید را دنبال کرده و کد مربوطه را اضافه می کنیم. در نهایت، ما به آن خواهیم رسید fiber_hooks.rs
:
fn mount_transition() -> Vec<JsValue> {
let result = mount_state(&JsValue::from(false)).unwrap();
let is_pending = result[0].as_bool().unwrap();
let set_pending = result[1].clone().dyn_into::<Function>().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<dyn Fn(Function)>);
let start: Function = closure.as_ref().unchecked_ref::<Function>().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()]
}
در طول mount_transition
فرآیند، ساختار داده زیر تشکیل می شود:
پس کی update_transition
نامیده می شود، می توانیم مقادیر را از هوک ها بازیابی کنیم:
fn update_transition() -> Vec<JsValue> {
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")
}
کلید در پیاده سازی نهفته است start_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 };
}
با توجه به تجزیه و تحلیل در این مقاله، پیاده سازی ابتدا به روز رسانی می شود isPending
به true
با اولویت فعلی سپس اولویت را کاهش می دهد، اجرا می کند callback
، و به روز رسانی ها isPending
به false
. در نهایت، اولویت قبلی را بازیابی می کند.
فرآیند بهروزرسانی با اولویت کمتر از حالت همزمان استفاده میکند، به همین دلیل است که رابط کاربری را مسدود نمیکند:
if cur_priority == Lane::SyncLane {
...
} 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| {
let did_timeout = did_timeout_js_value.as_bool().unwrap();
perform_concurrent_work_on_root(root_cloned.clone(), did_timeout)
}) as Box<dyn Fn(JsValue) -> JsValue>);
let function = closure.as_ref().unchecked_ref::<Function>().clone();
closure.forget();
new_callback_node = Some(unstable_schedule_callback_no_delay(
scheduler_priority,
function,
))
}
با این کار، اجرای useTransition
اکثرا کامل است با این حال، در طول فرآیند با چند اشکال مواجه شد:
اولین باگ وارد شده است begin_work.rs
:
work_in_progress.borrow_mut().lanes = Lane::NoLane;
هنگامی که یک FiberNode چندین Lane دارد، این رویکرد باعث ایجاد مشکلاتی می شود. باید به :
work_in_progress.borrow_mut().lanes -= render_lane;
به طوری که هر بار فقط Lane رندر شده فعلی حذف می شود.
باگ دوم وارد شده است work_loop.rs
:
log!("render over {:?}", *root.clone().borrow());
WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane;
در اصل، این خط در بود render_root
تابع، تنظیم مجدد متغیر پس از تکمیل فاز Render. اما در حالت همزمان، زمانی که فرآیند Render قطع می شود، این متغیر نباید ریست شود. بنابراین، این خط به perform_concurrent_work_on_root
:
if exit_status == ROOT_COMPLETED {
...
unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane };
}
به طوری که متغیر تنها زمانی ریست می شود که فرآیند Render کامل شود.
باگ سوم وارد شده است update_queue.rs
، همانطور که در تصویر زیر نشان داده شده است:
علاوه بر این، Scheduler بازسازی شده است. قبلاً min-heap به صورت زیر تعریف می شد:
static mut TASK_QUEUE: Vec<Task> = vec![];
static mut TIMER_QUEUE: Vec<Task> = vec![];
این نیاز به پیاده سازی جداگانه دارد peek_mut
عملکرد هنگام اصلاح ویژگی های Task
در پشته:
let mut task = peek_mut(&mut TASK_QUEUE);
task.callback = JsValue::null();
اکنون به این صورت تغییر کرده است:
static mut TASK_QUEUE: Vec<Rc<RefCell<Task>>> = vec![];
static mut TIMER_QUEUE: Vec<Rc<RefCell<Task>>> = vec![];
و peek
تابع را می توان به طور یکنواخت استفاده کرد:
let task = peek(&TASK_QUEUE);
task.borrow_mut().callback = JsValue::null();
لطفا به من یک ستاره بدهید!