Implement React v18 from Scratch Using WASM and Rust - [7] Support FunctionComponent Type
Tags: react, webassembly, rust
Published: 2024-04-23 10:01:50

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

The previous article has already implemented the initial rendering for `HostComponent` and `HostText` types. In this article, we will add support for `FunctionComponent`, although `Hooks` are not supported for now.

Following the process, the first step is to add a branch for handling `FunctionComponent` in `begin_work`:

```rust
pub fn begin_work(work_in_progress: Rc<RefCell<FiberNode>>) -> Option<Rc<RefCell<FiberNode>> {
  let tag = work_in_progress.clone().borrow().tag.clone();
  return match tag {
      WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
      ...
  };
}

fn update_function_component(
    work_in_progress: Rc<RefCell<FiberNode>>,
) -> Option<Rc<RefCell<FiberNode>>> {
    let fiber_hooks = &mut FiberHooks::new();
    let next_children = Rc::new(fiber_hooks.render_with_hooks(work_in_progress.clone())?);
    reconcile_children(work_in_progress.clone(), Some(next_children));
    work_in_progress.clone().borrow().child.clone()
}
```

From the code, we can see that we need to create a new struct called `FiberHooks` and implement the `render_with_hooks` method. Currently, the core of this method is to extract `_type` and `pending_props` from the `FiberNode` and call the function pointed to by `_type` (with `pending_props` as the argument) to obtain `children`:

```rust
impl FiberHooks {
    ...
    pub fn render_with_hooks(&mut self, work_in_progress: Rc<RefCell<FiberNode>>) -> Result<JsValue, JsValue> {
        ...
        let work_in_progress_borrow = work_in_progress_cloned.borrow();
        let _type = work_in_progress_borrow._type.as_ref().unwrap();
        let props = work_in_progress_borrow.pending_props.as_ref().unwrap();
        let component = JsValue::dyn_ref::<Function>(_type).unwrap();
        let children = component.call1(&JsValue::null(), props);
        children
    }
}

```

Since function execution can potentially throw exceptions, Rust doesn't have a `try-catch` mechanism. Instead, it uses the `Result<T, E>` enum to handle exceptions. In this enum, `T` represents the return value, and `E` represents the thrown exception. It has two variants: `Ok(T)` represents a successful return, and `Err(E)` represents an error return:

```rust
enum Result {
  Ok(T),
  Err(E)
}
```

We can handle the return value or thrown exceptions using `match`, as shown in the following example:

```rust
fn fn1() -> Result<(), String> {
    Err("a".to_string())
}

fn my_fn() {
    match fn1() {
        Ok(return_value) => {
            println!("return: {:?}", return_value)
        }
        Err(e) => {
            println!("error: {:?}", e)
        }
    }
}

fn main() {
    my_fn();
}

```

We can also use the `?` operator to propagate errors, as shown in the following example:

```rust
fn fn1() -> Result<(), String> {
    Err("a".to_string())
}

fn my_fn() -> Result<String, String> {
    fn1()?;
    Ok("my_fn succeed".to_string())
}


fn main() {
    match my_fn() {
        Ok(return_value) => {
            println!("return: {:?}", return_value)
        }
        Err(e) => {
            println!("error: {:?}", e)
        }
    }
}
```

The expression `fn1()?;` is equivalent to:

```rust
match fn1() {
    Ok(return_value) => {
        return_value
    }
    Err(e) => {
        return Err(e.into())
    }
};
```

Returning to our code, after executing `component.call1(&JsValue::null(), props)`, the return type is `Result<JsValue, JsValue>`. We need to propagate the thrown error to `perform_sync_work_on_root` for handling:

```rust
fn perform_sync_work_on_root(&mut self, root: Rc<RefCell<FiberRootNode>>) {
  ...
  loop {
      match self.work_loop() {
          Ok(_) => {
              break;
          }
          Err(e) => {

              self.work_in_progress = None;
          }
      };
  }
  ...
}
```

Therefore, the return values of the functions between them need to be changed to `Result`. For example, `begin_work` needs to be modified as follows:

```rust
pub fn begin_work(work_in_progress: Rc<RefCell<FiberNode>>) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
    let tag = work_in_progress.clone().borrow().tag.clone();
    return match tag {
        WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
        WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone())),
        WorkTag::HostComponent => Ok(update_host_component(work_in_progress.clone())),
        WorkTag::HostText => Ok(None),
    };
}
```

For example, `perform_unit_of_work` has been modified to change its return value and uses `?` to propagate errors:

```rust
fn perform_unit_of_work(&mut self, fiber: Rc<RefCell<FiberNode>>) -> Result<(), JsValue> {
    let next = begin_work(fiber.clone())?;
    ...
}
```

Next is `complete_work`. For `FunctionComponent`, it is straightforward. We just need to execute `bubble_properties`:

```rust
...
WorkTag::FunctionComponent => {
    self.bubble_properties(work_in_progress.clone());
    None
}
...
```

Finally, we reach the Commit phase, which doesn't require any modifications. It looks great.

Next, let's test it out. Here's a demo:

```js
// App.tsx
import dayjs from 'dayjs'

function App() {
  return (
    <div>
      <Comp>{dayjs().format()}</Comp>
    </div>
  )
}

function Comp({children}) {
  return (
    <span>
      <i>{`Hello world, ${children}`}</i>
    </span>
  )
}

export default App

// main.tsx
import {createRoot} from 'react-dom'
import App from './App.tsx'

const root = createRoot(document.getElementById('root'))
root.render(<App />)
```

Rebuild and install the dependencies. After running it, you will see the following result:


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



The logs and the rendered result on the page are both normal.

Next, let's test the scenario where an exception is thrown:

```js
function App() {
  return (
    <div>
      {/* will throw error */}
      <Comp>{dayjs.format()}</Comp>
    </div>
  )
}
```


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



Currently, we haven't handled the exceptions，we have only interrupted the render process.

With this, the initial rendering support for `FunctionComponent` is complete. Since we haven't implemented `Hooks` yet, there aren't many areas that need modification.

*Please kindly give me a star!*