To implement a simple render function, you need to have a basic understanding of JSX syntax and how DOM elements work

const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById("root");
ReactDOM.render(element, container);
Copy the code

We’ll implement the React render function with just three lines of code:

  • The first line defines a DOM through JSX
  • The second line gets a root node from the HTML
  • The third line injects its own DOM into the root node

Note that we are only implementing the function itself, and don’t care how JSX is compiled into the render function, because that is the job of a compiler (such as Babel). JSX is converted to JS at compile time using some build tool (such as Babel), and the conversion process is very simple: Replace our definition with the createElement function, passing the tag name, props, and child elements as parameters

Let’s delete all the React code and replace it with plain JavaScript:

In the above code, the first line is the element defined in JSX, which is not actually a valid JavaScript, so in order to use valid JS, we first need to replace JSX with createElement:

const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
);
Copy the code

Then convert the createElement function in the above code to a normal Element object as follows:

const element = {
  type: "h1".props: {
    title: "foo".children: "Hello",}};Copy the code

As you can see, the createElement function creates an object based on its parameters. The object has two properties: Type and props

  • Type is a string representing the tagName of the tag, which specifies the type of DOM node we want to create to pass to the document tagName
  • Props is another object that contains all the keys and values from JSX, and it also has a special property: children, which represents the child element of the tag. In this case, the child element is a string, but usually children is an array of many elements. That’s why the DOM node is a tree

The other part of the React code we need to replace is the call to reactdom.render:

  • First, we use type to create a node, in this case H1
  • Then assign all of the props properties to the node, in this case just title
  • We then create the child node, in this case there is only one string as the child node, so we create a text node
  • Finally, we append the child node to its parent and append the parent node to the root node
const element = {
  type: "h1".props: {
    title: "foo".children: "Hello",}};const container = document.getElementById("root"); // Get the root node
const node = document.createElement(element.type); // Create a node using type
node["title"] = element.props.title; // Assign the title attribute to the node
const text = document.createTextNode(""); // Create a child node
text["nodeValue"] = element.props.children; // Set the content of the child node with nodeValue
node.appendChild(text); // Append the child node to the parent node
container.appendChild(node); // Append the parent to the root node
Copy the code

Now we have the same functionality as the first few lines of code, but without React

Next, let’s start writing our own createElement function:

const element = React.createElement(
  "div",
  { id: "foo" },
  React.createElement("a".null."bar"),
  React.createElement("b"));Copy the code

As we saw in the previous step: Element is an object with type and props. The only thing our function needs to do is create this object:

function createElement(type, props, ... children) {
    return {
        type,
        props: {
            ...props,
            children,
        },
    };
}
Copy the code

We use the extension operator for props and rest syntax for the children argument to ensure that the child element props will always be an array, for example:

  • createElement("div")Returns:
{
  "type": "div"."props": { "children": []}}Copy the code
  • createElement("div", null, a)Returns:
{
  "type": "div"."props": { "children": [a] },
}
Copy the code
  • CreateElement ("div", null, a, b)Returns:
{
  "type": "div"."props": { "children": [a, b] },
}
Copy the code

Note that children can also contain primitives such as string or number. Therefore, we need to write a function that distinguishes values of primitive type from values of object type, and then encapsulate all values that are not of object type and create a special type for them: TEXT_ELEMENT

function createTextElement(text) {
    return {
        type: "TEXT_ELEMENT".props: {
            nodeValue: text,
            children: [].}}; }function createElement(type, props, ... children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child= > (
                typeof child === "object" ? child : createTextElement(child)
            )),
        },
    }
}
Copy the code

Remember that in the React source code react doesn’t encapsulate raw values or create empty arrays when there are no child elements, and the reason we do this is simply because I’m lazy and it’s easy to write, and I prefer simple code to performance code for our simple render function. So don’t go for the details (just lie flat)

Then still go back to the React createElement function:

const element = React.createElement(
    "div",
    { id: "foo" },
    React.createElement("a".null."bar"),
    React.createElement("b"));Copy the code

To replace it, we give our function a new name: myReactRender

const myReactRender = {
    createElement,
};

const element = myReactRender.createElement(
    "div",
    { id: "foo" },
    myReactRender.createElement("a".null."bar"),
    myReactRender.createElement("b"));Copy the code

Now that we’ve implemented the createElement function, we need to implement the render function:

function render(element, container) {
    // TODO create dom nodes
}
const myReactRender = {
    createElement,
    render,
};
const element = myReactRender.createElement(
    "div",
    { id: "foo" },
    myReactRender.createElement("a".null."bar"),
    myReactRender.createElement("b"));const container = document.getElementById("root");
myReactRender.render(element, container);
Copy the code

Next we need to deal with the render function:

  • The DOM node is first created using type
  • You also need to deal with the text node if type isTEXT_ELEMENT, we will create a text node
  • The same operation is then recursively performed on each child
  • Finally, append the new node to the parent node
function render(element, container) {
    const dom = element.type == "TEXT_ELEMENT"
        ? document.createTextNode("")
        : document.createElement(element.type);
    element.props.children.forEach(child= > render(child, dom));
    container.appendChild(dom);
}
Copy the code

The final thing to do is to assign each value of props to the dom being created:

function render(element, container) {
    const dom = element.type == "TEXT_ELEMENT"
        ? document.createTextNode("")
        : document.createElement(element.type);
    Object.keys(element.props)
        .filter(key= >key ! = ="children")
        .forEach(name= > { dom[name] = element.props[name] });
    element.props.children.forEach(child= > render(child, dom));
    container.appendChild(dom);
}
Copy the code

Finally, if we still want to use JSX syntax here, how do we tell Babel to use myReactRender’s rendering function instead of React’s? We just need to add comments like this, and when Babel compiles JSX, it will use the function we defined:

/ * *@jsx myReactRender.createElement */
const element = (
    <div id="foo">
        <a>11111</a>
    </div>
);
Copy the code

That’s it. Now we have a library that can render JSX into the DOM. The complete code is as follows:

function createTextElement(text) {
    return {
        type: "TEXT_ELEMENT".props: {
            nodeValue: text,
            children: [].}}; }function createElement(type, props, ... children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child= > (
                typeof child === "object" ? child : createTextElement(child)
            )),
        },
    }
}
function render(element, container) {
    const dom = element.type == "TEXT_ELEMENT"
        ? document.createTextNode("")
        : document.createElement(element.type);
    Object.keys(element.props)
        .filter(key= >key ! = ="children")
        .forEach(name= > { dom[name] = element.props[name] });
    element.props.children.forEach(child= > render(child, dom));
    container.appendChild(dom);
}
const myReactRender = { createElement, render };
const container = document.getElementById("root");
/ * *@jsx myReactRender.createElement */
const element = (
    <div id="foo">
        <a>abc</a>
    </div>
);
myReactRender.render(element, container);
Copy the code

So far, we’ve implemented the Render function of React with 40 lines of code.