Implement React v18 from Scratch Using WASM and Rust - [12] Implement Update for Multi Node
Tags: react, webassembly, rust
Published: 2024-05-11 07:40:05


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

We have previously implemented the update process for a single node. This article continues to introduce how to implement updates for multiple nodes.

First, let's look at the begin work stage. The implementation here is similar to the official one. You can refer to the section on multi-node Diff in the [previous article](https://www.paradeto.com/2021/03/08/react-reconcile-diff/). The main difference is that the first round of traversal process has been removed.

It's worth noting that in TypeScript, you can declare a Map type like this:

```ts
Map<string | number, Fiber>
```

However, the `key` in WASM is of `JsValue` type. When we try to declare the type like this, it will prompt that `JsValue` has not implemented `Hash` and `Eq` traits:

```rust
HashMap<JsValue, Rc<RefCell<FiberNode>>>
```

So, why not just implement it? Due to the "orphan rule" restriction in Rust, we cannot directly implement these traits for `JsValue`. We need to create a new `struct` to wrap it:

```rust
struct Key(JsValue);

impl PartialEq for Key {
    fn eq(&self, other: &Self) -> bool {
        Object::is(&self.0, &other.0)
    }
}

impl Eq for Key {}

impl Hash for Key {
    fn hash<H: Hasher>(&self, state: &mut H) {
        if self.0.is_string() {
            self.0.as_string().unwrap().hash(state)
        } else if let Some(n) = self.0.as_f64() {
            n.to_bits().hash(state)
        } else if self.0.is_null() {
            "null".hash(state)
        }
    }
}
```

After the multi-node Diff is completed, the `FiberNode` that needs to be moved will be marked with `Placement`, and the parent node of the `FiberNode` that needs to be deleted will be marked with `ChildDeletion`. Take the following as an example:

```
// before
<ul>
  <li key={1}>a</li> // key: "1"
  <li>b</li> // key: 1
  <li>c</li> // key: 2
  <li key={4}>d</li> // key: "4"
</ul>

// after
<ul>
  <li>b</li> // key: 0
  <li>c</li> // key: 1
  <li key={1}>d</li> // key: "1"
</ul>
```

The results after begin work and complete work are shown below:


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


To briefly explain:

- The input keys are all converted to string types. If no key is passed, the index is used as the key.
- The li node with key 0 cannot find a node with the same key, so a new node needs to be inserted, marked as `Placement`.
- The li node with key 1 can be reused, only need to update its child node content from b to c, marked as `Update`.
- The li node with key "1" can be reused, but since `oldIndex` is less than `lastPlacedIndex`, it needs to be moved, marked as `Placement`.


Next, we come to the commit phase, which processes the side effects on the nodes in a depth-first traversal manner.

When executing the insertion operation, it will try to find an insertion point `before` in `siblings`. The difficulty is that this insertion point may not be its sibling at the same level. For example, `<div/><B/>` where B is a `FunctionComponent` type: `function B() {return <div/>}`, here `before` is actually B's `child`, and the actual level may be deeper. At the same time, if a `FiberNode` is marked `Placement`, then it is unstable (its corresponding `Element` will move in this commit phase), and it cannot be used as `before`.

If the insertion point `before` can be found, call `parent.insertBefore(node, before)`, otherwise call `parent.appendChild(node)`.

Using the above example, it will process the side effects on `li(0) -> c -> li("1") -> ul` in order, and the results will be as follows:

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


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


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


- commitDeletion

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


For details on this update, see [here](https://github.com/ParadeTo/big-react-wasm/pull/11). Please kindly give me a star!