模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/big-react-wasm
本文对应 tag:v27
useTransition
是 React 中一个新推出的 Hook,可以让你在不阻塞 UI 的情况下更新状态。官网这里有个例子对比了使用它前后的区别,这里也有篇文章分析了一下原理。接下来我们就来实现一下,本次改动详情见这里。
我们先按照之前新增 Hook 的流程把相关代码都加上,最后会来到 fiber_hooks.rs
:
1 | fn mount_transition() -> Vec<JsValue> { |
mount_transition
时,会形成如下的数据结构:
所以 update_transition
时,依次取出 Hooks 上面的值即可:
1 | fn update_transition() -> Vec<JsValue> { |
关键在于 startTransition
的实现:
1 | fn start_transition(set_pending: Function, callback: Function) { |
根据这篇文章的分析结果,这里首先以当前优先级将 isPending
更新为 true
。然后降低优先级,执行 callbak
并将 isPending
更新为 false
。最后,将优先级恢复为原来的优先级。
降低优先级后的更新流程会使用 Concurrent Mode,这也是不阻塞 UI 的原因:
1 | if cur_priority == Lane::SyncLane { |
到这,useTransition
的实现基本上就完成了,不过实现过程中遇到了几个 bug:
第一个 Bug 在 begin_work.rs
中:
1 | work_in_progress.borrow_mut().lanes = Lane::NoLane; |
当一个 FiberNode 上有多个 Lane 时,这样会有问题,应该改成:
1 | work_in_progress.borrow_mut().lanes -= render_lane; |
即每次只把当前渲染的 Lane 给去掉。
第二个 Bug 在 work_loop.rs
中:
1 | log!("render over {:?}", *root.clone().borrow()); |
原来这一句是在 render_root
函数中,即 Render 阶段结束后,将该变量重置。但是 Concurrent Mode 模式下,当 Render 流程被打断后,不应该重置这个变量。所以,把这一句移到了 perform_concurrent_work_on_root
:
1 | if exit_status == ROOT_COMPLETED { |
即只有当 Render 流程完整的结束了以后才重置这个变量。
第三个 Bug 在 update_queue.rs
中,如下所示:
另外,还重构了 Scheduler。之前的小顶堆是这样定义的:
1 | static mut TASK_QUEUE: Vec<Task> = vec![]; |
这样就导致当我们需要修改堆中 Task
中的属性时,还得单独实现一个 peek_mut
的函数:
1 | let mut task = peek_mut(&mut TASK_QUEUE); |
现在改成这样:
1 | static mut TASK_QUEUE: Vec<Rc<RefCell<Task>>> = vec![]; |
统一使用 peek
就可以了:
1 | let task = peek(&TASK_QUEUE); |