从零实现 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 流程包括 commitBeforeMutaionEffectscommitMutationEffectscommitLayoutEffects 等步骤。这里简单起见,先只实现 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 最近的 HostComponentHostRoot 作为其所要插入的目标父节点:

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 节点类型是不是 HostComponentHostText,如果不是会尝试插入其所有子节点:

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 版本,即可以实现单节点的首次渲染,支持 HostComponentHostText

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

从结果中还可以看到,有些插入操作是在 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 并关注公众号"前端游"

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
市民中心的蟋蟀15 小时前
第五章 使用Context和订阅来共享组件状态
前端·javascript·react.js
用户967151139167215 小时前
Rust 如何轻松实现 RTMP 流媒体推送?深入解析直播推流场景与解决方案
rust·ffmpeg
谦谦橘子16 小时前
服务端渲染原理解析
前端·javascript·react.js
无名之逆16 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s91236010116 小时前
rust 同时处理多个异步任务
java·数据库·rust
杰克逊的黑豹20 小时前
不再迷茫:Rust, Zig, Go 和 C
c++·rust·go
Source.Liu21 小时前
【学Rust写CAD】28 带 Alpha 通道的双线性插值函数(bilinear_interpolation_alpha.rs)
rust
Source.Liu21 小时前
【学Rust写CAD】27 双线性插值函数(bilinear_interpolation.rs)
后端·rust·cad
yinhezhanshen21 小时前
理解rust里面的copy和clone
开发语言·后端·rust