Implement React v18 from Scratch Using WASM and Rust - [6] Implementation of Commit Process
Tags: react, webassembly, rust
Published: 2024-04-22 08:50:03

> Based on [big-react](https://github.com/BetaSu/big-react)，I am going to implement React v18 core features from scratch using WASM and Rust.
>
> Code Repository：https://github.com/ParadeTo/big-react-wasm
>
> The tag related to this article：[v6](https://github.com/ParadeTo/big-react-wasm/tree/v6)


The previous article has already implemented the render process. In this article, we will implement the final step, which is the commit phase.

Firstly, add the `commit` step to the `work_loop` function:

```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);
}
```

The `finished_work` here refers to the root `FiberNode`.


The complete commit process includes steps like `commitBeforeMutationEffects`, `commitMutationEffects`, `commitLayoutEffects`, and so on. For simplicity, let's start by implementing only the `commitMutationEffects` step. Of course, we can first check if the root node or its child nodes have any side effects ahead:

```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();
    }
}
```

In the `commit_mutation_effects` step, the Fiber tree is traversed in depth-first way. For example, the traversal order for the following example is BEFCDA:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p0i1tsvyu3gcqsug9gtc.png)

And the traversal of the subtree is determined based on the value of `subtree_flags`. For example, in the following example, EF is skipped, and the final order is BCDA:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2c1wh3ohs3husdtaap96.png)

The `subtree_flags` are updated in the `bubble_properties` of `CompleteWork`. This means that after completing a node, the `flags` of all its child nodes are merged:

```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();
}
```

Each node submits different operations based on its own `flags`. For now, we will only handle the `Placement` operation.

```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
    }
}
```

In `commit_placement`, the nearest `HostComponent` or `HostRoot` to the currently processed `FiberNode` is first obtained as the target parent node for insertion:

```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
}

```

In the `commit_placement` function, the nearest `HostComponent` or `HostRoot` to the currently processed `FiberNode` is obtained as the target parent node where it will be inserted.

```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();
        }
    }
}
```

You can refer to [this link](https://github.com/ParadeTo/big-react-wasm/pull/2) for detailed code changes in this update.


So far, we have replicated the big react v2 version, which can perform initial rendering of a single node and supports `HostComponent` and `HostText`.


After rebuilding and installing dependencies, running the example from the previous article, the final result will look like the following image:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnjmt0vnktw31dgfs18d.png)

From the result, we can also see that some insertion operations are completed before the Commit phase. Specifically in the code, this is done in the `complete_work` function:

```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
}
...
```

This way, during the Commit phase, we only need to insert an off-screen DOM tree once, which can be considered a small optimization.

*Please kindly give me a star.*