模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/big-react-wasm
本文对应 tag:v25
在 React 新版本中,Suspense 一个非常大的作用就是结合 use 来获取数据,今天我们来实现一下,本次改动见这里。
我们用这个例子来解释一下本次改动:
1 | import {Suspense, use} from 'react' |
我们先按照之前新增 hooks 的流程把相关代码都加上,最后会来到 fiber_hooks.rs:
1 | fn _use(usable: JsValue) -> Result<JsValue, JsValue> { |
从代码可以看到 use 这个函数即可传入一个 Promise 对象,也可传入一个 Context 对象,这里暂时只讨论 Promise 对象,所以我们看看 track_used_thenable:
1 |
|
中间的部分先略过,最后会返回一个 Result 的变体 Err,里面的 payload 为 SUSPENSE_EXCEPTION,这个 SUSPENSE_EXCEPTION 会在构建的时候插入到结果之中:
1 | const SUSPENSE_EXCEPTION = new Error( |
这里不直接返回 thenable 而是返回 SUSPENSE_EXCEPTION 是为了后续好区分用户代码抛出的异常和 react 自己的异常,我们真正关心的值存在 SUSPENDED_THENABLE 里面。
之后,会来到 work_loop.rs 这里:
1 | loop { |
这个 e 就是前面所说的 SUSPENSE_EXCEPTION,来看看 handle_throw 是怎么处理的:
1 | fn handle_throw(root: Rc<RefCell<FiberRootNode>>, mut thrown_value: JsValue) { |
这里会判断异常是不是 SUSPENSE_EXCEPTION,如果是的,就把真正的值重新拿出来,这就跟前面说的对上了。
这个值最后会传给 throw_and_unwind_work_loop:
1 | loop { |
这个我们上篇文章已经介绍过了,这里就不啰嗦了。我们再回到 track_used_thenable:
1 | pub fn track_used_thenable(thenable: JsValue) -> Result<JsValue, JsValue> { |
这里首次进来会走 else,核心逻辑就是给 thenable 添加 on_resolve 和 on_reject 方法,修改它上面的 status,value 和 reason 属性。
等到 Promise 对象的状态不再是 pending 后,会触发重新渲染,当再次来到这个函数时,它的 status 上也有值了,此时会进入 if:
1 | if status.is_string() { |
如果其状态为 filfilled,就返回 value 的值,否则抛出 reason 上的异常。
Suspense 结合 use hook 获取数据的实现就介绍到这,不过调试发现 bailout 的逻辑会影响该流程的正常工作,所以目前只能暂时注释掉这一部分的代码,后面有时间再来看看如何解决。