Vue.js - Renderer Module
Deep Dive into Renderer Modules of Vue.js
This article is mainly about how vue renderer module work, and a lightweight renderer is given as an example.
In this article, will introduce VDOM
and the structure of the VDOM
, then will explain how to convert the VDOM
to a Real DOM
, next will show how vue diff the new and old VDOM
to patch the DOM
. Finally will give the benefits of using VDOM.
You can get the source code with the Online Demo: CodePen - Renderer Module of Vue.js
VDOM
?
What is VDOM
means Virtual DOM. Let's compare real DOM and virtual DOM.
It's a real dom
<div class="red">
<h1 id="title">Hello, world!</h1>
<p>How are you</p>
</div>
and the virtual dom equivalent of this real dom is
{
tag: 'div',
props: {
class: 'red',
},
children: [
{
tag: 'h1',
props: {
id: 'title',
},
children: 'Hello, world!'
}, {
tag: 'p',
children: ['How are you']
}
]
}
So how vue transform this VDOM
to a real dom?
VDOM
The structure of the The VDOM could be as simple as a string.
'div'
VDOM also can be a object with three keys
tag
: String(required)props
: Object | nullchildren
: List<Object|String>
Tag
A tag is required. And the type of tag should be a String.
tag: 'div'
Props
Props has two situation
instance of Object
props: { class: 'red', }
not exist
props: null
Children
Children has three type of situations
The value can be a string
children: 'Hello, world!'
The value also can be a array
children: [ 'Hello, world!', { tag: 'p', children: ['How are you'] } ]
not exist
children: null
VDOM
to the real DOM
?
How to convert the As we discussed before, we need to discuss all possible scenarios, including the different types of the three keys listed above.
VDOM
in Vue.js
Here is a vdom in vue
⚠️ Please notice that this
VDOM
is different from the previousVDOM
, it's vnode wrapped withh
function.
const vdom = h(
"div", // tag
{ // props
class: "red"
},
[ // children
h("div", null, ["div one"]),
h("div", null, [
"div two",
h("h1", null, "headline one")
]
),
]);
And the HTML body is now like this
<div id="app"></div>
vdom node
is wrapped in a h
function ?
Why h
means Hyperscript
, stands for "script that generates HTML structures"
It will help us to create the VDOM
object, so we don't have to write tag
, props
, children
key name over and over again.
The h
function is like
function h(tag, props, children) {
// if the tag or children is number, change them to string
if (typeof tag === "number") {
tag = String(tag)
} else if (typeof children === "number") {
children = String(children)
}
return {
tag,
props,
children,
};
}
VDOM
to DOM
Mount We need a root dom node to mount dom that created by us.
const root = document.getElementById("app")
Next we need a mount
function to help us create real dom node and mount them to their container dom node.
function mount(vnode, container) {
let element;
// is vnode a string
if (typeof vnode === "string") {
container.textContent = vnode;
return;
} else {
// if not, create a real dom with value of vnode.tag
// and store the dom to the vnode object (keep for patch)
element = (vnode.element = document.createElement(vnode.tag));
}
// is props exist
if (vnode.props) {
// if exist, set attribute to the dom
for (let attr in vnode.props) {
const value = vnode.props[attr];
// if the attr is function(start with 'on'), add a event listener
if (attr.startsWith('on')) {
element.addEventListener(attr.slice(2).toLowerCase(), value)
} else {
element.setAttribute(attr, value);
}
}
}
// is children exist
if (vnode.children) {
if (typeof vnode.children === "string") {
// The value of children is string
element.textContent = vnode.children;
} else if (Array.isArray(vnode.children)) {
// The value of children is array
for (let child of vnode.children) {
// Every item of children is a vnode, recurse them with mount function
mount(child, element);
}
}
container.appendChild(element);
}
}
DOM
while VDOM
update
Patch the Diff the new vdom to the old vdom and patch the real dom.
Compare the tag
If the tag of new vdom and old vdom is different, replace the entire dom node.
Compare the props
- find out the newly added prop, and add it to the dom
- find out the prop no longer exists, and remove it from the dom
Compare the children
There are four type combinations of new and old vdom children
new children | old children |
---|---|
String | String |
String | Array |
Array | Array |
Array | String |
We need to handle all possible combinations.
patch
function
function patch(older, newer) {
let element = newer.element = older.element;
// compare the tag
if (older.tag !== newer.tag) {
// update the old dom node
element.innerHTML = ''
mount(newer, element)
return
}
// compare the props
const oldProps = older.props || {};
const newProps = newer.props || {};
for (let prop in newProps) {
// find out the newly added prop
oldValue = oldProps[prop];
newValue = newProps[prop];
if (oldValue !== newValue) {
// if the prop is changed in newer DOM or the prop is newly added
element.setAttribute(prop, newValue);
}
}
for (let prop in oldProps) {
// find out the prop no longer exists
if (!(prop in newProps)) {
// if the prop is not existed in the newer DOM
element.removeAttribute(prop);
}
}
// compare the children
const oldChildren = older.children;
const newChildren = newer.children;
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
// both new children and old children are string
if (oldChildren !== newChildren) {
// they are not equal
element.textContent = newChildren;
}
} else if (Array.isArray(oldChildren)) {
// the new children is string but old children is array
element.textContent = newChildren; // overwrite the DOM
}
} else if (Array.isArray(newChildren)) {
if (typeof oldChildren === "string") {
// the new children is array but old children is string
element.innerHTML = '' // reset this dom node
for (let child of newChildren) {
mount(child, element); // recreate the dom nodes with the vnodes in new children
}
} else if (Array.isArray(oldChildren)) {
// both new children and old children are array
const commonLength = Math.min(oldChildren.length, newChildren.length);
for (let i = 0; i < commonLength; i++) {
// Iterate the part that they all have
patch(oldChildren[i], newChildren[i]);
}
if (newChildren.length > oldChildren.length) {
// if the new children is longer, add the rest vdom node to the dom node
newChildren.slice(oldChildren.length).forEach(child => {
mount(child, element);
})
} else {
oldChildren.slice(newChildren.length).forEach(child => {
element.removeChild(child);
})
}
}
}
}
VDOM
Benefits of using Why Vue3 still using VDOM?
Vue3 has made a lot of optimizations to the patch function.
Compare to our lightweight demo, Vue3 optimize vnode with the :key
attribute in v-for
template syntax (corresponding to lines 56 to 71 of our patch function)
And Vue3 can also skip the props
, children
part if it's not necessary. At the same time, the block optimization essentially avoids having to call this on most of the nodes.
Evan You: In reality, the update is so performant
Otherwise, vdom can let us directly use render function syntax, which is much more flexible that the template syntax. This capability has proven to be really useful and important for library authors.
So Vue3 decided to stick to virtual DOM because of the benefits that it provides, and at the same time vue team is still trying to leverage the compiler to make the diffing as efficient as possible.
In conclusion, compare to generate direct imperative DOM operations, Vue provide the same class of performance, and the same time still provide the ability to drop down to use a more flexible language to express logic.
Evan You: The goal to have the best of both worlds is achieved in Vue3 that into a certain degree.
Get source code
You can get the final code with this Online Demo
CodePen - Renderer Module of Vue.js
All knowledge points are extracted from Evan You's Vue Mastery Course, so if there are errors in this article,
it must be Evan You's mistakeplease point out errors in the comments section.
- What is VDOM?
- The structure of the VDOM
- Tag
- Props
- Children
- How to convert the VDOM to the real DOM?
- VDOM in Vue.js
- Why vdom node is wrapped in a h function ?
- Mount VDOM to DOM
- Patch the DOM while VDOM update
- Compare the tag
- Compare the props
- Compare the children
- patch function
- Benefits of using VDOM
- Why Vue3 still using VDOM?
- Get source code