模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/big-react-wasm
本文对应 tag:v26
Suspense 另外一个比较有用的功能是结合 React.lazy 进行组件懒加载,我们继续来实现一下,本次改动详见这里。
我们用下面这个例子来进行说明:
1 | import {Suspense, lazy} from 'react' |
首先,还是要在 react 这个库中导出这个方法:
1 |
|
翻译成 JS 更直观,如下所示:
1 | const payload = { |
这里值得关注的是 lazy_initializer
这个方法,还是用 JS 版本的来说明:
1 | function lazy_initializer(payload) { |
这个跟上篇文章实现的 use hook 有点类似,这里的 ctor
就是上面例子的 () => import('./Cpn').then((res) => delay(res))
,执行它返回的是一个 Promise 对象。只有当对象状态为 Resolved
才会返回它的结果,即 res
,这里的 res
是一个模块对象,它的属性 default
是模块中通过 export default
导出的内容。其他状态则直接抛出 _result
,当状态为 Pending
时,_result
是 Promsie 对象本身,当状态为 Rejected
时,_result
是错误对象。
接着,主要需要修改的文件为 begin_work.rs
:
1 | .... |
这里的关键在这一行 let Component = init.call1(&JsValue::null(), &payload)?;
,执行 init
如果抛出了异常,根据上一篇文章的流程,会往上找到最近的 Suspense
再次开始 render 流程,此时会渲染 Suspense 的 fallback。等到 Promise 对象 resolve 时,会重新出发更新流程,再次到这里的时候执行 init
返回的就是模块导出的组件了,即 Cpn
。
此外,还需要修改 work_loop.rs
中的 handle_throw
,在 else
中补充非 use
抛出错误的场景:
1 | fn handle_throw(root: Rc<RefCell<FiberRootNode>>, mut thrown_value: JsValue) { |
最后,上一篇文章还留了一个尾巴,即 bailout 影响了 Suspense 的正常工作,最后的解决办法是首先把冒泡更新优先级的代码移到了 fiber_throw.rs
中:
1 | let closure = Closure::wrap(Box::new(move || { |
同时,在 begin_work.rs
中将 Suspense 组件排除在 bailout 逻辑之外:
1 | if !has_scheduled_update_or_context |