We've already done pretty much preparation before diving into the code. First, we need to find a point to get started with. Core is just core. So let's start with the most used and important top-level APIs, React.createElement and ReactDOM.render.
Note
We're gonna first try to build a simple version of React, and dive into more details. Much of this part refers to @zpao's Building React From Scratch. We might come back later to add more features on top of it. But it's absolutely not intuitive to try to finish all stuff at one time. That's not engineering.
We all know that the JSX compiles html-like code to React.createElement(name, config, children). Like you've read in the JSX advanced guide. We could have these two mapping as below:
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
Essentially, when you're returning a JSX fragment, you're actually returning a element tree object, describing the hierarchy of the DOM. React use this method to recursively go down to the very end where all the component type are all DOM nodes and finally form a nested DOM representative. But createElement per se, is atomic (not recursive).
function createElement(type, config, ...children) {
let props = Object.assign({}, config);
props.children = [...children]
// React Features not supported:
// - keys
// - refs
// - defaultProps (usually set here)
return {
type,
props,
};
}
So if you use the last example, when we return that JSX fragment, we are actually calling:
Mounting is the process of convert a react component instance to a DOM node (for the first time, i.e. initial render).
When talking about a React component, there are three kinds of possibilities. The component could be native type of node, like div, span, or customized React component with capitalized letter, or it could be as simple as a string (text node).
function mount(component, node) {
// mark this node as root, so that next time we could possibly use `update` instead of `mount`
node[DATA_KEY] = rootId;
if (typeof component === 'string' || typeof component === 'number') {
mountTextNode(component, node);
} else if (typeof component.type === 'function') {
mountComposite(component, node);
} else {
mountHost(component, node);
}
rootId++;
}
function mountTextNode(text, node) {
const textNode = document.createTextNode(text);
node.appendChild(textNode);
}
function mountComposite(component, node) {
// go one level deeper each time until it's a native node
const deeperNode = component.type(component.props);
// delegate to mount
return mount(deeperNode, node);
}
function mountHost(component, node) {
const nativeNode = document.createElement(component.type)
const children = component.props.children
Object.keys(component.props).forEach((propName) => {
if (propName !== 'children') {
nativeNode.setAttribute(propName, component.props[propName])
}
})
node.appendChild(nativeNode)
children.filter((child) => !!child).forEach((child) => {
mount(child, nativeNode)
})
}
What've We Achieved So Far
In this post, we talked mainly about two things:
How a React component is converted and rendered to a DOM node, which is the mounting process.
How to create a React element, which is what a React component returns, and how this simplifies the syntax and the building process, so that we don've have to write an react element object like this:
These two processes are highly related, and each do their job clearly. Now with these two functions, you could already write a test like this (with the JSX syntax):
function BigButton({ color, text }) {
return (
<div>
<button style={{ backgroundColor: color }}>
{text}
<button>
</div>
)
}
const App = function() {
return (
<div>
Hello World
<BigButton color="#aa234d" text="Click Me">
</div>
)
}
mount(<App />, document.getElementById('root'))
Tip
I suggest you go through this example in your heart and say it out each of the process to make yourself completely understand the mounting process.
Dive Into A Minimum Implementation
We've already done pretty much preparation before diving into the code. First, we need to find a point to get started with. Core is just core. So let's start with the most used and important top-level APIs,
React.createElement
andReactDOM.render
.React.createElement(component, props, ...children)
We all know that the JSX compiles html-like code to
React.createElement(name, config, children)
. Like you've read in the JSX advanced guide. We could have these two mapping as below:is the same as
Essentially, when you're returning a JSX fragment, you're actually returning a element tree object, describing the hierarchy of the DOM. React use this method to recursively go down to the very end where all the component type are all DOM nodes and finally form a nested DOM representative. But
createElement
per se, is atomic (not recursive).So if you use the last example, when we return that JSX fragment, we are actually calling:
which is also:
Mount
Mounting is the process of convert a react component instance to a DOM node (for the first time, i.e. initial render).
When talking about a React component, there are three kinds of possibilities. The component could be native type of node, like
div
,span
, or customized React component with capitalized letter, or it could be as simple as a string (text node).What've We Achieved So Far
In this post, we talked mainly about two things:
These two processes are highly related, and each do their job clearly. Now with these two functions, you could already write a test like this (with the JSX syntax):