Implement React v18 from Scratch Using WASM and Rust - [5] Implementation of Complete Work Phase of Render Process
Tags: react, webassembly, rust
Published: 2024-04-19 08:51:12


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

The previous article implemented the "begin work" phase in the Render process, and this article will cover the implementation of the "complete work" phase.

For more information about the Render process, you can refer to [React 技术揭秘](https://react.iamkasong.com/process/reconciler.html) or [React 源码解读之首次渲染流程（含例子）](https://www.paradeto.com/2020/07/26/react-first-render/)。.

Since this is an imitation, the code will not be repeated. You can see the comparison between the two versions [here](https://github.com/ParadeTo/big-react-wasm/pull/1/files#diff-b572860610fbe556d94c04680b61e47a6a5724b351cdc1d3d1e89ad2ef556ca7). Below, we'll explain some specific details.

- completeWork

`CompleteWork` is still defined as a struct that contains the `host_config` property:

```rust
// complete_work.rs
pub struct CompleteWork {
    pub host_config: Rc<dyn HostConfig>,
}
```


And it also serves as a property of `WorkLoop`, being initialized along with the `WorkLoop` instance during initialization:

```rust
// work_loop.rs
pub struct WorkLoop {
    work_in_progress: Option<Rc<RefCell<FiberNode>>>,
    complete_work: CompleteWork,
}
impl WorkLoop {
    pub fn new(host_config: Rc<dyn HostConfig>) -> Self {
        Self {
            work_in_progress: None,
            complete_work: CompleteWork::new(host_config),
        }
    }
    ...
}
```

- Modify the implementation of `HostConfig` in `react-dom`

The original `create_text_instance` and `create_instance` functions return `Text` and `Element` respectively (although they are ultimately returned as `dyn Any`). However, it becomes cumbersome when trying to `downcast` them back in `append_initial_child` because `child` can be either `Text` or `Element`, requiring two attempts. Therefore, for convenience, they are unified to return `Node`.

```rust
fn create_text_instance(&self, content: String) -> Rc<dyn Any> {
  ...
  Rc::new(Node::from(document.create_text_node(content.as_str())))
}

fn create_instance(&self, _type: String) -> Rc<dyn Any> {
  ...
  match document.create_element(_type.as_ref()) {
      Ok(element) => {
          Rc::new(Node::from(element))
      }
      Err(_) => todo!(),
  }
}

```


This way, in `append_initial_child`, we only need to `downcast` it to `Node`:

```rust
fn append_initial_child(&self, parent: Rc<dyn Any>, child: Rc<dyn Any>) {
  let p = parent.clone().downcast::<Node>().unwrap();
  let c = child.clone().downcast::<Node>().unwrap();
  ...
}
```

- There are some seemingly redundant `{}` code blocks in the code to address certain ownership restrictions in Rust. This is because Rust enforces the rule that "we cannot have both an active mutable borrow and an active immutable borrow in the same scope." For example, the following example would result in a `already borrowed: BorrowMutError` error:

```rust
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    let b1 = data.borrow();
    let b2 = data.borrow_mut();

    println!("{}", b1);
}
```

Making the following changes resolves the issue:

```rust
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    {
      let b1 = data.borrow();
    }
    {
      let b2 = data.borrow_mut();
    }
    println!("{}", data.borrow());
}
```

The reason is that now the borrowing scope of `b1` and `b2` is limited to the `{}` block.


For those who are not familiar with the latest versions of React, they may wonder why the Render phase does not generate an Effect List. The reason is that, in order to support `Suspense`, React removed the Effect List in version `v16.14.0` and replaced it with `subtreeFlags` to mark whether there are side effects in the subtree. For more information, you can refer to [this article](https://mp.weixin.qq.com/s/-UNN45YttXJPA2TlrnSy3Q).


To validate the correctness of the code in the complete work phase, we add some debugging information about `flags` in the `fmt` method of `FiberRootNode`.

```rust
...
WorkTag::HostRoot => {
    write!(f, "{:?}(subtreeFlags:{:?})", WorkTag::HostRoot, current_ref.subtree_flags);
}
WorkTag::HostComponent => {
    let current_borrowed = current.borrow();
    write!(
        f,
        "{:?}(flags:{:?}, subtreeFlags:{:?})",
        ...
    );
}
WorkTag::HostText => {
    let current_borrowed = current.borrow();

    write!(
        f,
        "{:?}(state_node:{:?}, flags:{:?})",
...
```


Rebuild and install the dependencies, then run the hello world project.

```ts
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 results:

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

The rendering process concludes here for now, and the content will be modified when other functionalities are added. In the next article, we will implement the commit process.

*Please kindly give a star.*