Implement React v18 from Scratch Using WASM and Rust - [16] Implement React Noop
ayou
Posted on June 11, 2024
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!!!
Posted on June 11, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 26, 2024
September 20, 2024
September 3, 2024