Implement React v18 from Scratch Using WASM and Rust [16] Implement React Noop

RMAG news

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

The previous articles always mentioned the implementation of React Noop for unit testing, and today we will complete this task.

First, following the previous approach, we create a react-noop directory at the same level as react-dom:

├── packages
│ ├── react
│ ├── react-dom
│ ├── react-noop
│ ├── react-reconciler
│ ├── scheduler
│ └── shared

The project structure is similar to react-dom, but the difference lies in the implementation of HostConfig in react-noop. For example, in react-dom, the create_instance function returns an Element object:

fn create_instance(&self, _type: String, props: Rc<dyn Any>) -> Rc<dyn Any> {
let window = window().expect(“no global `window` exists”);
let document = window.document().expect(“should have a document on window”);
match document.create_element(_type.as_ref()) {
Ok(element) => {
let element = update_fiber_props(
element.clone(),
&*props.clone().downcast::<JsValue>().unwrap(),
);
Rc::new(Node::from(element))
}
Err(_) => {
panic!(“Failed to create_instance {:?}”, _type);
}
}
}

In react-noop, it returns a regular JavaScript object:

fn create_instance(&self, _type: String, props: Rc<dyn Any>) -> Rc<dyn Any> {
let obj = Object::new();
Reflect::set(&obj, &“id”.into(), &getCounter().into());
Reflect::set(&obj, &“type”.into(), &_type.into());
Reflect::set(&obj, &“children”.into(), &**Array::new());
Reflect::set(&obj, &“parent”.into(), &JsValue::from(1.0));
Reflect::set(
&obj,
&“props”.into(),
&*props.clone().downcast::<JsValue>().unwrap(),
);
Rc::new(JsValue::from(obj))
}

Other methods are also operations on regular JavaScript objects. For more details, please refer to this link.

Additionally, to facilitate testing, we need to add a method called getChildrenAsJSX:

impl Renderer {

pub fn getChildrenAsJSX(&self) -> JsValue {
let mut children = derive_from_js_value(&self.container, “children”);
if children.is_undefined() {
children = JsValue::null();
}
children = child_to_jsx(children);

if children.is_null() {
return JsValue::null();
}
if children.is_array() {
todo!(“Fragment”)
}
return children;
}
}

This allows us to obtain a tree structure containing JSX objects using the root object. For example, the following code:

const ReactNoop = require(react-noop)
const root = ReactNoop.createRoot()
root.render(
<div>
<p>hello</p>
<span>world</span>
</div>
)
setTimeout(() => {
console.log(———, root.getChildrenAsJSX())
}, 1000)

The final printed result would be:

{
$$typeof: react.element,
type: div,
key: null,
ref: null,
props: {
children: [
{
$$typeof: react.element,
type: p,
key: null,
ref: null,
props: {
children: hello,
},
},
{
$$typeof: react.element,
type: span,
key: null,
ref: null,
props: {
children: world,
},
},
],
},
}

Note that the code for printing the result is placed inside a setTimeout because we put the update process in a macro task while implementing Batch Update. You can refer to this article for more information.

Next, we include react-noop in the build script and set the build target to nodejs so that we can use it in a Node.js environment. However, to support JSX syntax in Node.js, we need to use Babel. Here, we can directly use babel-node to run our script and configure the necessary presets:

// .babelrc
{
presets: [
[
@babel/preset-react,
{
development: true
}
]
]
}

If everything goes well, the above code should run successfully in Node.js. However, when I tried to use react-noop in Jest, I encountered an error:

work_loop error JsValue(RuntimeError: unreachable
RuntimeError: unreachable
at null.<anonymous> (wasm://wasm/00016f66:1:14042)

Since I couldn’t solve the issue, I had to perform unit testing in Node.js instead. Here’s an example test case:

async function test1() {
const arr = []

function Parent() {
useEffect(() => {
return () => {
arr.push(Unmount parent)
}
})
return <Child />
}

function Child() {
useEffect(() => {
return () => {
arr.push(Unmount child)
}
})
return Child
}

root.render(<Parent a={1} />)
await sleep(10)
if (root.getChildrenAsJSX() !== Child) {
throw new Error(test1 failed)
}

root.render(null)
await sleep(10)
if (arr.join(,) !== Unmount parent,Unmount child) {
throw new Error(test1 failed)
}
}

Executing test1 successfully indicates that our React Noop is working correctly.

Please kindly give me a star!!!

Please follow and like us:
Pin Share