从零实现 React v18,但 WASM 版 - [5] 实现 Commit 流程

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

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

本文对应 tag:v6

上篇文章已经实现了 React 一次更新过程的 Render 流程,本篇我们来实现最后一步,即 Commit。

首先,在 work_loop 中加入 commit 步骤:

rust 复制代码
fn perform_sync_work_on_root(&mut self, root: Rc<RefCell<FiberRootNode>>) {
  ...

  root.clone().borrow_mut().finished_work = finished_work;
  self.commit_root(root);
}

这里的 finished_work 就是根 FiberNode。

完整的 Commit 流程包括 commitBeforeMutaionEffects,commitMutationEffects,commitLayoutEffects 等步骤。这里简单起见,先只实现 commitMutationEffects, 当然可以先判断根节点或者其子节点是否有副作用,有副作用才需要执行:

rust 复制代码
fn commit_root(&self, root: Rc<RefCell<FiberRootNode>>) {
    ...
    if subtree_has_effect || root_has_effect {
        commit_work.commit_mutation_effects(finished_work.clone());
        cloned.borrow_mut().current = finished_work.clone();
    } else {
        cloned.borrow_mut().current = finished_work.clone();
    }
}

commit_mutation_effects 中会采取深度优先的方式遍历 Fiber Tree,比如下面这个例子的遍历顺序为 BEFCDA:

同时还会根据 subtree_flags 的值来决定是否继续遍历子树,比如下面这个例子会跳过 EF,最后的顺序为 BCDA:

而 subtree_flags 是在 CompleteWork 中的 bubble_properties 中更新的,即每完成一个节点,就会合并他所有子节点的 flags:

rust 复制代码
fn bubble_properties(&self, complete_work: Rc<RefCell<FiberNode>>) {
    let mut subtree_flags = Flags::NoFlags;
    {
        let mut child = complete_work.clone().borrow().child.clone();
        while child.is_some() {
            let child_rc = child.clone().unwrap().clone();
            {
                let child_borrowed = child_rc.borrow();
                subtree_flags |= child_borrowed.subtree_flags.clone();
                subtree_flags |= child_borrowed.flags.clone();
            }
            {
                child_rc.borrow_mut()._return = Some(complete_work.clone());
            }
            child = child_rc.borrow().sibling.clone();
        }
    }

    complete_work.clone().borrow_mut().subtree_flags |= subtree_flags.clone();
}

每个节点根据他本身的 flags 来提交不同的操作,这里暂时只处理 Placement:

rust 复制代码
fn commit_mutation_effects_on_fiber(&self, finished_work: Rc<RefCell<FiberNode>>) {
    let flags = finished_work.clone().borrow().flags.clone();
    if flags.contains(Flags::Placement) {
        self.commit_placement(finished_work.clone());
        finished_work.clone().borrow_mut().flags -= Flags::Placement
    }
}

commit_placement 中会先获取离当前处理的 FiberNode 最近的 HostComponent 或 HostRoot 作为其所要插入的目标父节点:

rust 复制代码
fn commit_placement(&self, finished_work: Rc<RefCell<FiberNode>>) {
    let host_parent = self.get_host_parent(finished_work.clone());
    if host_parent.is_none() {
        return;
    }
    let parent_state_node = FiberNode::derive_state_node(host_parent.unwrap());

    if parent_state_node.is_some() {
        self.append_placement_node_into_container(
            finished_work.clone(),
            parent_state_node.unwrap(),
        );
    }
}

fn get_host_parent(&self, fiber: Rc<RefCell<FiberNode>>) -> Option<Rc<RefCell<FiberNode>>> {
    let mut parent = fiber.clone().borrow()._return.clone();
    while parent.is_some() {
        let p = parent.clone().unwrap();
        let parent_tag = p.borrow().tag.clone();
        if parent_tag == WorkTag::HostComponent || parent_tag == WorkTag::HostRoot {
            return Some(p);
        }
        parent = p.borrow()._return.clone();
    }

    None
}

最后执行插入操作是会判断当前 FiberNode 节点类型是不是 HostComponent 或 HostText,如果不是会尝试插入其所有子节点:

rust 复制代码
fn append_placement_node_into_container(
    &self,
    fiber: Rc<RefCell<FiberNode>>,
    parent: Rc<dyn Any>,
) {
    let fiber = fiber.clone();
    let tag = fiber.borrow().tag.clone();
    if tag == WorkTag::HostComponent || tag == WorkTag::HostText {
        let state_node = fiber.clone().borrow().state_node.clone().unwrap();
        self.host_config.append_child_to_container(
            self.get_element_from_state_node(state_node),
            parent.clone(),
        );
        return;
    }

    let child = fiber.borrow().child.clone();
    if child.is_some() {
        self.append_placement_node_into_container(child.clone().unwrap(), parent.clone());
        let mut sibling = child.unwrap().clone().borrow().sibling.clone();
        while sibling.is_some() {
            self.append_placement_node_into_container(sibling.clone().unwrap(), parent.clone());
            sibling = sibling.clone().unwrap().clone().borrow().sibling.clone();
        }
    }
}

本次详细代码变更可以参考这里。

到此,我们已经复刻出了 big react v2 版本,即可以实现单节点的首次渲染,支持 HostComponent 和 HostText。

重新构建和安装依赖,还是运行上一篇的例子,最终效果如下图:

从结果中还可以看到,有些插入操作是在 Commit 阶段这前就完成了,具体到代码中,是在 complete_work 中:

rust 复制代码
...
WorkTag::HostComponent => {
  let instance = self.host_config.create_instance(
      work_in_progress
          .clone()
          .borrow()
          ._type
          .clone()
          .unwrap()
          .clone()
          .as_string()
          .unwrap(),
  );
  self.append_all_children(instance.clone(), work_in_progress.clone());
  work_in_progress.clone().borrow_mut().state_node =
      Some(Rc::new(StateNode::Element(instance.clone())));
  self.bubble_properties(work_in_progress.clone());
  None
}
...

这样在 Commit 阶段的时候,只需要一次性插入一颗离屏的 DOM 树就好了,也算是一个小优化了。

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

相关推荐
Highcharts.js3 小时前
React 中原生支持使用 JSX 组件配置自定义表格 Highcharts Grid
前端·javascript·react.js·前端框架·jsx·highcharts·自定义表格
JudithHuang4 小时前
React 嵌套路由与布局
前端·react.js·arcgis·react·react router
unconditional5 小时前
我拆了 sdkm 的自定义 SDK 机制:所谓“内置”,就是官方提前帮你填好的表
rust·开源
前端小D7 小时前
React Native项目开发(iOS侧为例)
javascript·react native·react.js
Ramble_Naylor21 小时前
Rust深度解读:Send 与 Sync 的本质与线程安全边界
python·算法·rust
Ramble_Naylor1 天前
Rust深度解读:异步流(Async Streams)与生成器的处理艺术
开发语言·后端·rust
RobinDevNotes1 天前
Memvid 把 Agent 长期记忆塞进一个文件
人工智能·rust·openresty
miofly1 天前
GitHub 今日推荐|agent-desktop:通过无障碍树让 AI 代理直接操作桌面应用
rust·开源·github
geovindu1 天前
rust:N-Barrier Pattern
开发语言·后端·设计模式·rust·屏障模式·并发型模式
Ramble_Naylor1 天前
Rust深度解读:高性能 Web 网关与 Axum 架构剖析
前端·架构·rust