The inner workings of Virtual DOM
The Inner Workings Of Virtual DOM - rajaraodv - Medium
Flow-chart showing how VDOM works in PreactVirtual DOM (VDOM aka VNode) is magical ✨ but is also complex and hard to understand😱. React, Preact and similar JS libraries use them in their core. Unfortunately I couldn’t find any good article or doc that explains it in a detailed-yet-simple-to-understand fashion. So I thought of writing one myself.Note: This is a LONG post. I’ve added tons of pictures to make it simple but it also makes the post appear even longer.I’m using Preact’s code and VDOM as it is small and you can look at it yourself with ease in the future. But I think most of the concepts applies to React as well.My hope is that once you read this, you’ll be able to better understand and hopefully contribute to libraries like React and Preact.In this blog, I’ll take a simple example and go over various scenarios to give you an idea as to how they actually work. Specifically, I’ll go over:Babel and JSXCreating VNode — A single virtual DOM elementDealing with components and sub-componentsInitial rendering and creating a DOM elementRe-renderingRemoving DOM element.Replacing a DOM element.The app:The app a simple filterable Search app that contains two components “FilteredList” and “List”. The List renders a list of items (default: “California” and “New York”). The app has a search field that filters the list based on the characters in the field. Pretty straight forward.Picture of the app (Click to zoom and see the details)Live app: http://codepen.io/rajaraodv/pen/BQxmjjThe Big PictureAt a high-level, we write components in JSX(html in JS), that gets converted to pure JS by CLI tool Babel. Then Preact’s “h” (hyperscript) function, converts it into VDOM tree (aka VNode). And finally Preact’s Virtual DOM algorithm, creates real DOM from the VDOM that creates our app.The big pictureBefore we get into the weeds of the VDOM lifecycle, let’s understand JSX as it provides the starting point for the library.1. Babel And JSXIn React, Preact like libraries, there is no HTML and instead everything is JavaScript. So we need to write even the HTML in JavaScript. But writing DOM in pure JS is a nightmare!😱For our app we’ll have to write HTML like below:Note: I’ll explain “h” soonThat’s where JSX comes in. JSX essentially allows us to write HTML in JavaScript! And also allows us to use JS within that by curly braces{}.JSX helps us easily write our components like below:Converting JSX tree to JavaScriptJSX is cool but it’s not a valid JS, but ultimately we need REAL DOM. JSX only helps in writing a representation of real DOM and otherwise it’s useless.So we a way need to convert into a corresponding JSON object (VDOM, which is also a tree) so we can eventually use it as an input to create real DOM. We need a function to do that.And that function is the “h” function in Preact. It’s the equivalent to “React.createElement” in React.“h” stands for hyperscript — one of the first libs to create HTML in JS (VDOM)But how to convert JSX into “h” function calls? And that’s where Babel comes in. Babel simply goes through each JSX node and converts them to “h” function calls.Babel JSX (React Vs Preact)By default, Babel converts JSX to React.createElement calls because it defaults to React.LEFT: JSX RIGHT: React version of JS (click to Zoom)But we can easily change the name of the function to anything we want (like “h” for Preact) by adding “Babel Pragma” like below:Option 1://.babelrc{   "plugins": [      ["transform-react-jsx", { "pragma": "h" }]     ] }Option 2://Add the below comment as the 1st line in every JSX file/** @jsx h */“h” —With Babel Pragma (Click to Zoom)Main Mount To real DOMNot only the code in “render” methods of the components are converted to “h” functions, but also the starting mount!And this is where the execution start and everything begins!//Mount to real DOMrender(<FilteredList/>, document.getElementById(‘app’));//Converted to "h":render(h(FilteredList), document.getElementById(‘app’));The Output of “h” functionThe “h” function takes the output of JSX and creates something called a “VNode” (React’s “createElement” creates ReactElement). A Preact’s “VNode” (or a React’s “Element”) is simply a JS object representation of a single DOM node with it’s properties and children.It looks like this:{   "nodeName": "",   "attributes": {},   "children": []}For example, VNode for our app’s Input looks like this:{   "nodeName": "input",   "attributes": {    "type": "text",    "placeholder": "Search",    "onChange": ""   },   "children": []  }Note: “h” function doesn’t create the entire tree! It simply creates JS object for a given node. But since the “render” method already has the DOM JSX in a tree fashion, the end result will be a VNode with children and grand children that looks like a tree.Reference Code:“h” : https://github.com/developit/preact/blob/master/src/h.jsVNode: https://github.com/developit/preact/blob/master/src/vnode.js“render”: https://github.com/developit/preact/blob/master/src/render.js“buildComponentFromVNode: https://github.com/developit/preact/blob/master/src/vdom/diff.js#L102OK, let’s see how Virtual DOM works.Virtual DOM Algorithm Flowchart For PreactIn the flowchart below shows how components (and child components) are created, updated and deleted by Preact. It also shows when lifecycle events like “componentWillMount” and so on are called.Note: We’ll go over each section in a step-by-step manner so don’t worry if it looks complicatedYes, it’s hard to understand all at once. So let’s go over various sections of the flowchart by going through various scenarios in a step-by-step manner.Note: I’ll highlight sections of the lifecycle in “yellow” when discussing specific steps.Scenario 1: Initial Creation Of The App1.1 — Creating VNode (Virtual DOM) For A Given ComponentThe highlighted section shows the initial loop that creates VNode (Virtual DOM) tree for a given component. Note that this doesn’t create VNode for sub-components (that’s a different loop).Yellow highlight shows the VNode creationThe picture below shows what happens when our app loads for the first time. The library ends up creating a VNode with children and attributes for the main FilteredList component.Note: Along the way it also calls “componentWillMount” and “render” lifecycle methods (see the green blocks in the picture above).(click to zoom)At this point, we have a VNode that has a “div” parentNode that has an “input” and a “List” child nodes.Reference code:Most lifecycle events like: componentWillMount, render and so on: https://github.com/developit/preact/blob/master/src/vdom/component.js1.2 — If Not A Component, create a REAL DOMIn this step, it’ll simply create real DOM for the parent node (div) and repeat process for child nodes (“input” and “List”).Highlighted loop shows REAL DOM creation for child components1.4 — Process Child And Append To Parent.In this step, we’ll process leaf. Since “input” has a parent (“div”), we’ll append input as a child to div. Then the control stops and return to create “List” (which is the 2nd child of “div”).At this point, our app looks like below:Note: that after “input” is created, since it doesn’t have any children, it doesn’t immediately loop and create “List”! Instead it’ll first append “input” to the parent “div” and then goes back to process “List”Reference code:appendChild: https://github.com/developit/preact/blob/master/src/vdom/diff.js1.5 Process child component(s)The control goes back to step 1.1 and starts all 0ver again for “List” component. But since “List” is a component, it calls the render method of the “List” component to get new set of VNodes that look like below.Repeat everything for a child “component”That loop completes for the List component and returns List’s VNode that looks like below:Reference Code:“buildComponentFromVNode: https://github.com/developit/preact/blob/master/src/vdom/diff.js#L1021.6 Repeat steps 1.1 through 1.4 for all the Child NodesIt’ll repeat the above steps again for each node. Once it reaches the leaf node, it appends it to the node’s parent and repeats the process.Keep doing the same until all child and parent are created and appendedThe below picture shows how each node is added (hint: depth-first).How REAL DOM tree is created by the VDOM algorithm1.7 Finish processingAt this point, It’s done processing. It simply calls “componentDidMount” for all the components (starting from child components to parent components) and stops.Important Note: Once everything is done, a reference to the real DOM is added to each of the component instances. This reference is used for remaining updates (create, update, delete) to compare and avoid recreating the same DOM nodes.SCENARIO 2: Delete Leaf NodeSay we typed “cal” and hit enter. This will remove the 2nd list node, a leaf node (New York) while keeping all other parent nodes.Let’s see how the flow looks for this scenario.2.1 Create VNodes like before.After initial rendering, every change in the future is an “update”. When it comes to creating VNodes, the update cycle works very similar to create cycle and creates VNodes all over again.But since it’s an update (and not creation) of the component, it makes “componentWillReceiveProps”, “shouldComponentUpdate”, and “componentWillUpdate” calls to each component and sub-component.In addition, update cycle, doesn’t recreate DOM elements if those elements are already there.Updating component lifecycleShowing references b/w previous DOM and each componentAnd when VNodes created, each VNode’s attributes are compared w/ the attributes of the REAL DOM at that node. If real DOM exists, the loop moves on to the next node.Real DOM “already exists” lifecycle (during update)Reference CodeinnerDiffNode: https://github.com/developit/preact/blob/master/src/vdom/diff.js#L1852.3 Remove node if there are extra nodes in the REAL DOMThe picture below shows the difference in REAL DOM V/s VNode.(click to zoom)And since there is a difference, the “New York” node in REAL DOM is removed by the algorithm as shown in the workflow below. The algorithm also calls “componentDidUpdate” lifecycle event once everything is done.Remove DOM node lifecycleSCENARIO 3 — Unmounting Entire ComponentUse case: Let’s say if we typed blabla in the filter, since it doesn’t match “California” or “New York”, we won’t render the child component “List” at all. This means, we need to unmount the entire component.List component is not removed if there are no results“render” method of FilteredListDeleting a component is similar to Deleting a single node. Except, when we delete a node that has a reference to a component, then the framework calls “componentWillUnmount” and then recursively deletes all the DOM elements. After all the elements are removed from the real DOM, it calls “componentDidUnmount” method of the referenced component.The picture below shows the reference to “List” component on the real DOM “ul”.sadfThe below picture highlights the section in the flowchart to show how deleting/unmounting a component works.Remove and unmount component