Implement React v18 from Scratch Using WASM and Rust - [4] Implementation of Begin Work Phase of Render Process
Tags: react, webassembly, rust
Published: 2024-04-16 04:03:08

> 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：[v4](https://github.com/ParadeTo/big-react-wasm/tree/v4)

The update process in React can be divided into two main phases: Render and Commit. The Render phase can further be divided into two stages: begin work and complete work. This article focuses on implementing the begin work stage.

In the previous article, we discussed that when the `render()` method is called, it invokes the `update_container` method in the `reconciler`:

```rust
pub fn update_container(&self, element: Rc<JsValue>, root: Rc<RefCell<FiberRootNode>>) {
    let host_root_fiber = Rc::clone(&root).borrow().current.clone();
    let update = create_update(element);
    enqueue_update(host_root_fiber.borrow(), update);
    ...
}
```

After executing the above code, a data structure like the following will be constructed:


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



The definitions of `FiberRootNode` and `FiberNode` are as follows:

```rust
pub struct FiberRootNode {
    pub container: Rc<JsValue>,
    pub current: Rc<RefCell<FiberNode>>,
    pub finished_work: Option<Rc<RefCell<FiberNode>>>,
}

pub struct FiberNode {
    pub tag: WorkTag,
    pub pending_props: Option<Rc<JsValue>>,
    key: Option<String>,
    pub state_node: Option<Rc<StateNode>>,
    pub update_queue: Option<Rc<RefCell<UpdateQueue>>>,
    pub _return: Option<Rc<RefCell<FiberNode>>>,
    pub sibling: Option<Rc<RefCell<FiberNode>>>,
    pub child: Option<Rc<RefCell<FiberNode>>>,
    pub alternate: Option<Rc<RefCell<FiberNode>>>,
    pub _type: Option<Rc<JsValue>>,
    pub flags: Flags,
    pub subtree_flags: Flags,
    pub memoized_props: Option<Rc<JsValue>>,
    pub memoized_state: Option<Rc<JsValue>>,
}
```

Here, the `Rc` smart pointer is used to allow a value to have multiple owners, and `RefCell` is used for "interior mutability" for certain fields that need to be modified.

Next, we are going to build a FiberNode Tree:

```rust
pub fn update_container(&self, element: Rc<JsValue>, root: Rc<RefCell<FiberRootNode>>) {
    ...
    let mut work_loop = WorkLoop::new(self.host_config.clone());
    work_loop.schedule_update_on_fiber(host_root_fiber);
}
```

Because the subsequent code is mostly a translation of the implementation in big-react from JavaScript to Rust, there is no need to go into too much detail. Here are a few differences to note.

Difference 1: `workInProgress`

In big-react, `workInProgress` is a module-level variable. However, Rust does not have the concept of module-level variables, so it has been changed to be an attribute within a struct.

```rust
pub struct WorkLoop {
    work_in_progress: Option<Rc<RefCell<FiberNode>>>,
}
```

In Rust, a new struct called `WorkLoop` has been introduced, whereas in big-react, it was exported as a function in the `work_loop.js` module.

Difference 2: `stateNode`

In big-react, the `stateNode` is of type `any` because for the root `FiberNode`, its `stateNode` is a `FiberRootNode`, while for other nodes, the `stateNode` is a DOM object in JavaScript. In Rust, an enum is used to represent this:

```rust
pub enum StateNode {
    FiberRootNode(Rc<RefCell<FiberRootNode>>),
    Element(Rc<dyn Any>),
}
```

It is a bit more cumbersome to use, as it requires the use of match for branching and handling different cases:

```rust
match fiber_node.state_node {
    None => {}
    Some(state_node) => {
        match &*state_node {
            StateNode::FiberRootNode(fiber_root_node) => {}
            StateNode::Element(ele) => {},
        };
    }
}
```

Alternatively, it can be done similarly to destructuring assignment in JavaScript:

```rust
let Some(StateNode::FiberRootNode(fiber_root_node)) = fiber_node.state_node.clone();
```

Difference 3: performSyncWorkOnRoot

In big-react, `try-catch` is used to catch any errors that occur during the `workLoop` process:

```js
do {
  try {
    workLoop()
    break
  } catch (e) {
    console.error('workLoop error', e)
    workInProgress = null
  }
} while (true)
```

Since Rust does not support `try-catch`, but instead uses `Result` to handle errors, we won't consider it for now and will implement it later:

```rust
loop {
  self.work_loop();
  break;
}
```

Since we are currently only implementing the begin work phase, we will temporarily comment out `complete_unit_of_work` in `perform_unit_of_work`. Instead, we will assign `None` to `work_in_progress` to make the loop exit:

```rust
fn work_loop(&mut self) {
  while self.work_in_progress.is_some() {
      self.perform_unit_of_work(self.work_in_progress.clone().unwrap());
  }
}

fn perform_unit_of_work(&mut self, fiber: Rc<RefCell<FiberNode>>) {
  let next = begin_work(fiber.clone());

  if next.is_none() {
      // self.complete_unit_of_work(fiber.clone())
      self.work_in_progress = None;
  } else {
      self.work_in_progress = Some(next.unwrap());
  }
}
```

Next, let's print the generated FiberNode tree in this phase to see if the results are correct:

```rust
fn perform_sync_work_on_root(&mut self, root: Rc<RefCell<FiberRootNode>>) {
  self.prepare_fresh_stack(Rc::clone(&root));

  loop {
      self.work_loop();
      break;
  }

  log!("{:?}", *root.clone().borrow());
}
```

To print the `FiberRootNode`, we also need to implement the `Debug` trait for it:

```rust
impl Debug for FiberRootNode {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    }
}
```

The implementation approach is to use breadth-first traversal. You can refer to the code for more details. Now, let's modify the example in the "hello world" project:

```js
import {createRoot} from 'react-dom'
const comp = (
  <div>
    <p>
      <span>Hello World</span>
    </p>
  </div>
)
const root = createRoot(document.getElementById('root'))
root.render(comp)
```

You can see the following output:


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


Since the reconciliation process for children as an array has not been implemented yet, we can only test the case with a single child for now.

*Please kindly give a star.*