As a front-end engineer, front-end frameworks are used almost every day and need to be well mastered, and the degree of mastery of a particular technology can be judged by whether one can be implemented. Writing a front-end framework by hand is a great way to get a better handle on it.

Modern front-end frameworks have become complex over the years, making it difficult to understand how they work. So I thought I’d write the simplest version of a front-end framework to help you figure things out.

A complete front-end framework involves more content or more, we step by step, this article to achieve vDOM rendering.

Vdom rendering

Vdom, virtual DOM, is used to declaratively describe pages. Many modern front-end frameworks are based on VDOM. The front-end framework is responsible for turning the VDOM into an add-on, subtraction, or rendering of the real DOM.

So what does vDOM look like? How do you render it?

Dom is an element, an attribute, and a text. The same is true of VDOM, where elements are structures of {type, props, and children} and text is strings and numbers.

For example, a vDOM like this:

{
    type: 'ul'.props: {
        className: 'list'
    },
    children: [{type: 'li'.props: {
                className: 'item'.style: {
                    background: 'blue'.color: '#fff'
                },
                onClick: function() {
                    alert(1); }},children: [
                'aaaa'] {},type: 'li'.props: {
                className: 'item'
            },
            children: [
                'bbbbddd'] {},type: 'li'.props: {
                className: 'item'
            },
            children: [
                'cccc']]}}Copy the code

As you can see, it describes a UL element with three LI child elements, the first of which has a style and an onClick event.

The front-end framework describes the interface through this object structure and then renders it into the DOM.

How do you render this object structure?

It’s obvious to use recursion, to do different things for different types.

  • If it is a text type, the text node is created with Document.createTextNode.

  • If it is an element type, then document.createElement is used to create the element node, which has attributes to process, and recursively render the child nodes.

So, vDOM’s render logic looks like this:

if (isTextVdom(vdom)) {
    return mount(document.createTextNode(vdom));
} else if (isElementVdom(vdom)) {
    const dom = mount(document.createElement(vdom.type));
    for (const child of vdom.children) {
        render(child, dom);
    }
    for (const prop in vdom.props) {
        setAttribute(dom, prop, vdom.props[prop]);
    }
    return dom;
}
Copy the code

Text is judged by strings and numbers:

function isTextVdom(vdom) {
    return typeof vdom == 'string' || typeof vdom == 'number';
}
Copy the code

The element is the object and type is the tag name string:

function isElementVdom(vdom) {
   return typeof vdom == 'object' && typeof vdom.type == 'string';
}
Copy the code

After the element is created, if there is a parent node to mount to, assemble the DOM tree:

const mount = parent ? (el= > parent.appendChild(el)) : (el= > el);
Copy the code

So, the full render function looks like this:

const render = (vdom, parent = null) = > {
    const mount = parent ? (el= > parent.appendChild(el)) : (el= > el);
    if (isTextVdom(vdom)) {
        return mount(document.createTextNode(vdom));
    } else if (isElementVdom(vdom)) {
        const dom = mount(document.createElement(vdom.type));
        for (const child of vdom.children) {
            render(child, dom);
        }
        for (const prop in vdom.props) {
            setAttribute(dom, prop, vdom.props[prop]);
        }
        returndom; }};Copy the code

The element’s DOM also has attributes, such as the style and onClick attributes in the vDOM above.

The style attribute is the style that supports the object and is set to the style after the object is merged. The onClick attribute is the event listener and is set with addEventListener. The rest of the attributes are set with setAttribute.

const setAttribute = (dom, key, value) = > {
    if (typeof value == 'function' && key.startsWith('on')) {
        const eventType = key.slice(2).toLowerCase();
        dom.addEventListener(eventType, value);
    } else if (key == 'style' && typeof value == 'object') {
        Object.assign(dom.style, value);
    } else if (typeofvalue ! ='object' && typeofvalue ! ='function') { dom.setAttribute(key, value); }}Copy the code

At this point, vDOM’s rendering logic is complete.

Use the vDOM rendering above to test the effect:

render(vdom, document.getElementById('root'));
Copy the code

Vdom render successful!

To summarize:

Vdom renders recursively. Depending on the type, createTextNode and createElement are used to recursively create the DOM and assemble it together. Style, event listeners, and other attributes are set using apis like addEventListener and setAttribute, respectively.

Creating dom and setting properties through different apis is the rendering flow of VDOM.

However, VDOM is too cumbersome to write. No one writes VDOM directly. It is usually written through more friendly DSLS (Domain-specific languages) and compiled into VDOM, such as JSX and Template.

Here we use JSX because we can compile directly with Babel.

JSX compiles to VDOM

Vdom = JSX;

const jsx = <ul className="list">
    <li className="item" style={{ background: 'blue', color: 'pink'}}onClick={()= > alert(2)}>aaa</li>
    <li className="item">bbbb</li>
    <li className="item">cccc</li>
</ul>

render(jsx, document.getElementById('root'));
Copy the code

It’s obviously a lot more compact than writing vDOM directly, but you need to compile it once.

Configure Babel to compile JSX:

module.exports = {
    presets: [['@babel/preset-react',
            {
                pragma: 'createElement'}}]]Copy the code

The compilation product looks like this:

const jsx = createElement("ul", {
  className: "list"
}, createElement("li", {
  className: "item".style: {
    background: 'blue'.color: 'pink'
  },
  onClick: () = > alert(2)},"aaa"), createElement("li", {
  className: "item"
}, "bbbb"), createElement("li", {
  className: "item"
}, "cccc"));
render(jsx, document.getElementById('root'));
Copy the code

Why not just vDOM instead of some function?

Because there’s going to be an execution, you can put in some dynamic logic,

For example, the value from data:

const data = {
    item1: 'bbb'.item2: 'ddd'
}
const jsx = <ul className="list">
    <li className="item" style={{ background: 'blue', color: 'pink'}}onClick={()= > alert(2)}>aaa</li>
    <li className="item">{data.item1}</li>
    <li className="item">{data.item2}</li>
</ul>
Copy the code

Will compile to:

const data = {
  item1: 'bbb'.item2: 'ddd'
};
const jsx = createElement("ul", {
  className: "list"
}, createElement("li", {
  className: "item".style: {
    background: 'blue'.color: 'pink'
  },
  onClick: () = > alert(2)},"aaa"), createElement("li", {
  className: "item"
}, data.item1), createElement("li", {
  className: "item"
}, data.item2));
Copy the code

This is called the Render function, and the return value it executes is vDOM.

The render function is named createElement because we specified pragma as createElement in our Babel configuration above.

Render function generates vDOM, so implementation is simple:

const createElement = (type, props, ... children) = > {
  return {
    type,
    props,
    children
  };
};
Copy the code

Let’s test rendering with JSX:

Render successful!

We took the VDOM one step further by writing some dynamic logic via JSX, compiling it into render Function, and generating vDOM after execution.

This is simpler than writing vDOM directly and allows for more flexible VDOM generation logic.

The code is uploaded to github: github.com/QuarkGluonP…

conclusion

Writing a front-end framework is the most direct way to better grasp it, and we will gradually implement a fully functional front-end framework.

In this article we have implemented vDOM rendering. CreateElement, createTextNode, vDOM, vDOM, vDOM, vDOM, vDOM, vDOM, vDOM Properties like style, Event Listener, and so on are set using different apis.

Although ultimately vDOM rendering, but the development will not write vDOM directly, but through JSX to describe the page, and then compile into the Render function, after execution to generate VDOM. This is simpler to write and supports dynamic logic. (JSX compiles use Babel to specify the name of render function)

Vdom rendering and JSX are the basis of the front-end framework, and other functions such as components are implemented on this basis. In the next article, we will implement component rendering.