Implement React v18 from Scratch Using WASM and Rust – [7] Support FunctionComponent Type

Implement React v18 from Scratch Using WASM and Rust – [7] Support FunctionComponent Type

Based on 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

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:

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:

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:

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

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

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:

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:

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:

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:

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:

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:


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:

// 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:

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

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

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

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!

Leave a Reply

Your email address will not be published. Required fields are marked *