Understanding JSX and Virtual DOM Implementation
What is JSX?
JSX represents a syntax extension for JavaScript. The official definition describes it as:
JSX is a JavaScript syntax extension that retains all of JavaScript's capabilities.
In React projects, we write JSX like this:
const Application = <div>
sample content
</div>
While React uses virtual DOM for rendering, the virtual DOM structure isn't immediately visible when writing JSX. First, Babel transforms JSX syntax into React.createElement() calls. You can verify this transformation using the Babel online compiler.
Let's validate this by writing the compiled React.createElement function directly. Create a basic React project with create-react-app and modify index.js:
import React from 'react'
import ReactDOM from 'react-dom'
const Application = () => {
return React.createElement(
"div",
{
className: "application"
},
"parent",
React.createElement(
"div",
null,
"nested"
)
)
}
ReactDOM.render(<Application />, document.getElementById('root'))
To understand the difference, let's log both Application and Application() in index.js:
console.log('Application:', Application)
console.log('Application():', Application())
The output reveals that Application by itself is just a regular function (component), while Application() returns the actual virtual DOM object, known as a React element.
This React element is essentially a JavaScript object that describes the DOM structure - this is what we call the virtual DOM.
From the virtual DOM structure shown above, we can deduce the corresponding real DOM:
<!-- outermost div -->
<div>
<!-- first child node -->
parent
<!-- second child node wrapped in div with content nested -->
<div>nested</div>
</div>
Therefore, developing without JSX is entirely possible. You could use React.createElement for all tags, methods, styles, and custom attributes. However, this approach would be extreme cumbersome.
Converting Virtual DOM to Real DOM
Using our existing create-react-app demo project, let's modify index.js to implement our own rendering function:
import React from 'react'
// React component using JSX
const Application = () => <div>
<div>Question: What's your star sign?</div>
<div>Response: I'm customized for you.</div>
</div>
// Custom virtual DOM to real DOM renderer
// vdom: virtual DOM node; container: parent node to insert into
const customRenderer = (vdom, container) => {
// Skip execution if no container provided
if (!container) {
return
}
let domElement // Variable to store node information
if (typeof vdom === 'string' || typeof vdom === 'number') {
// Handle text nodes
domElement = document.createTextNode(vdom);
} else {
// Handle element nodes
domElement = document.createElement(vdom.type);
}
// Append to parent container
container.appendChild(domElement)
// Process children recursively
if (vdom.props && vdom.props.children) {
const childNodes = vdom.props.children
if (Array.isArray(childNodes)) {
childNodes.forEach((child) => {
customRenderer(child, domElement)
})
} else {
customRenderer(childNodes, domElement)
}
}
}
// Execute custom renderer with virtual DOM
customRenderer(Application(), document.getElementById('root'))
Each line of the code is explained through comments. The core concept involves transforming the virtual DOM into real DOM elements using JavaScript methods and inserting them into the root node.
Running npm run start, we can verify if the browser renders the real DOM correctly.
We can enhance this further by adding styling and event handling:
const Application = () => <div>
<div cssClass='question' styles={{ color: 'blue' }}>Question: What's your star sign?</div>
<div onActivate={() => console.log('Stop joking!')}>Response: I'm customized for you.</div>
</div>
...
let domElement
if (typeof vdom === 'string' || typeof vdom === 'number') {
domElement = document.createTextNode(vdom)
} else {
domElement = document.createElement(vdom.type)
// Add click event
if (vdom.props.onActivate) {
domElement.addEventListener('click', () => {
vdom.props.onActivate()
})
}
// Add inline styles
if (vdom.props.styles) {
Object.keys(vdom.props.styles).forEach(property => {
domElement.style[property] = vdom.props.styles[property]
})
}
// Add CSS class
if (vdom.props.cssClass) {
domElement.className = vdom.props.cssClass
}
}
...
It's important to note that ReactDOM.render implementation is much more complex than this simplified example. The actual source code is extensive and includes sophisticated event handling mechanisms that differ from the simplified version shown here.