برنامه نویسی

پیاده سازی 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();

لطفا به من یک ستاره بدهید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا