从零实现 React v18,但 WASM 版 - [3] Renderer 和 Reconciler 架构设计

模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!

代码地址:github.com/ParadeTo/bi...

本文对应 tag:v3

前言

上一篇文章末本计划本篇实现 render 阶段,但考虑到内容太多,还是分开写比较好。说是架构设计,有点夸张了,其实主要的目的在于要实现 RendererReconciler 的解耦,让 Reconciler 可以支持不同的 Renderer,因为后续还需要实现 ReactNoop Renderer 用来跑测试用例。

Reconciler

Rust 中要实现我们的目的,离不开 Trait,所以我们在 Reconciler 中先定义好 HostConfig Trait:

rust 复制代码
// react-reconciler/src/lib.rs
pub trait HostConfig {
    fn create_text_instance(&self, content: String) -> Rc<dyn Any>;
    fn create_instance(&self, _type: Rc<dyn Any>) -> Rc<dyn Any>;
    fn append_initial_child(&self, parent: Rc<dyn Any>, child: Rc<dyn Any>);
    fn append_child_to_container(&self, child: Rc<dyn Any>, parent: Rc<dyn Any>);
}

这里暂时只定义了部分方法,需要注意的是由于不同 Renderer 下的 stateNode 是不一样的,这里的类型都不能确定,所以使用了 std::any::Any

接着来定义 Reconciler

rust 复制代码
// react-reconciler/src/lib.rs
pub struct Reconciler {
    host_config: Box<dyn HostConfig>,
}

impl Reconciler {
    pub fn new(host_config: Box<dyn HostConfig>) -> Self {
        Reconciler { host_config }
    }
    pub fn create_container(&self, container: &JsValue) -> Rc<RefCell<FiberRootNode>> {
        Rc::new(RefCell::new(FiberRootNode {}))
    }

    pub fn update_container(&self, element: Rc<JsValue>, root: Rc<RefCell<FiberRootNode>>) {
        log!("{:?} {:?}", element, root)
    }
}

Reconciler 中包含 host_config 属性,使用 Trait Object 来表示泛型,当某个 Renderer 中实例化一个 Reconciler 对象时,需要传入实现了 HostConfig Trait 的类型的实例。

其他两个方法先简单的实现一下,供调试用。接下来看看 Renderer

Renderer

Renderer 中当然首先要实现 HostConfig

rust 复制代码
// react-dom/src/host_config.rs
use react_reconciler::HostConfig;


impl HostConfig for ReactDomHostConfig {
    fn create_text_instance(&self, content: String) -> Rc<dyn Any> {
        let window = window().expect("no global `window` exists");
        let document = window.document().expect("should have a document on window");
        Rc::new(document.create_text_node(content.as_str()))
    }

    fn create_instance(&self, _type: String) -> Rc<dyn Any> {
        let window = window().expect("no global `window` exists");
        let document = window.document().expect("should have a document on window");
        match document.create_element(_type.as_ref()) {
            Ok(element) => Rc::new(element),
            Err(_) => todo!(),
        }
    }

    fn append_initial_child(&self, parent: Rc<dyn Any>, child: Rc<dyn Any>) {
        let parent = parent.clone().downcast::<Element>().unwrap();
        let child = child.downcast::<Text>().unwrap();
        match parent.append_child(&child) {
            Ok(_) => {
                log!("append_initial_child successfully ele {:?} {:?}", parent, child);
            }
            Err(_) => todo!(),
        }
    }

    fn append_child_to_container(&self, child: Rc<dyn Any>, parent: Rc<dyn Any>) {
        todo!()
    }
}

可以看到我们可以通过 downcastAny 类型转化为具体的类型,这里暂时都很简单地实现了一下。

然后我们定义一个 Renderer

rust 复制代码
// react-dom/src/renderer.rs
#[wasm_bindgen]
pub struct Renderer {
    root: Rc<RefCell<FiberRootNode>>,
    reconciler: Reconciler,
}

impl Renderer {
    pub fn new(root: Rc<RefCell<FiberRootNode>>, reconciler: Reconciler) -> Self {
        Self { root, reconciler }
    }
}

#[wasm_bindgen]
impl Renderer {
    pub fn render(&self, element: &JsValue) {
        self.reconciler.update_container(Rc::new(element.clone()), self.root.clone())
    }
}

他包含 rootreconciler 两个属性,其中 root 是在调用 create_root 时通过 reconcilercreate_container 方法生成的:

rust 复制代码
#[wasm_bindgen(js_name = createRoot)]
pub fn create_root(container: &JsValue) -> Renderer {
    set_panic_hook();
    let reconciler = Reconciler::new(Box::new(ReactDomHostConfig));
    let root = reconciler.create_container(container);
    let renderer = Renderer::new(root, reconciler);
    renderer
}

测试

一切就绪了,我们加点代码来调试一下,我们把 hello-world 中的例子改成如下所示:

js 复制代码
import {createRoot} from 'react-dom'

const comp = <div>hello world</div>
const root = createRoot(document.getElementById('root'))
root.render(comp)

然后在 Reconciler 中,我们先硬编码实现首次渲染:

rust 复制代码
pub fn update_container(&self, element: Rc<JsValue>, root: Rc<RefCell<FiberRootNode>>) {
    let props = Reflect::get(&*element, &JsValue::from_str("props")).unwrap();
    let _type = Reflect::get(&*element, &JsValue::from_str("type")).unwrap();
    let children = Reflect::get(&props, &JsValue::from_str("children")).unwrap();

    let text_instance = self.host_config.create_text_instance(children.as_string().unwrap());
    let div_instance = self.host_config.create_instance(_type.as_string().unwrap());
    self.host_config.append_initial_child(div_instance.clone(), text_instance);

    let window = window().unwrap();
    let document = window.document().unwrap();
    let body = document.body().expect("document should have a body");

    body.append_child(&*div_instance.clone().downcast::<Element>().unwrap());
}

不出意外,重新构建并安装依赖,运行 hello world 项目就可以在浏览器中看到内容了。

跪求 star 并关注公众号"前端游"。

相关推荐
shimly1234564 小时前
(done) 速通 rustlings(9) 分支跳转
rust
不会敲代码15 小时前
从原子CSS到TailwindCSS:现代前端样式解决方案全解析
前端·css·react.js
冴羽5 小时前
2026 年 JavaScript 框架 3 大趋势
前端·javascript·react.js
www_stdio9 小时前
全栈项目第五天:构建现代企业级 React 应用:从工程化到移动端实战的全链路指南
前端·react.js·typescript
shimly12345610 小时前
(done) 速通 rustlings(4) 变量声明
rust
shimly12345611 小时前
(done) 速通 rustlings(11) 向量vector及其操作
rust
shimly12345611 小时前
(done) 速通 rustlings(3) intro1 println!()
rust
shimly12345611 小时前
(done) 速通 rustlings(12) 所有权
rust
ssshooter12 小时前
看完就懂 useLayoutEffect
前端·react.js·面试
www_stdio13 小时前
项目基础准备之Zustand:轻量级 React 状态管理的优雅之选
前端·react.js·typescript